使用生命周期范围时如何处理运行时参数?

Kug*_*gel 31 c# dependency-injection inversion-of-control autofac object-lifetime

警告,提前很长时间.

我最近一直在思考这个问题,我很难在这里找到令人满意的解决方案.我将使用C#和autofac作为示例.

问题

IoC非常适合构建无状态服务的大型树.我解析服务并仅将数据传递给方法调用.大.

有时,我想将数据参数传递给服务的构造函数.这就是工厂的用途.而不是解析服务我解析它的工厂,并使用参数调用create方法来获取我的服务.多一点工作但还可以.

有时,我希望我的服务在一定范围内解析为同一个实例.Autofac提供了InstancePerLifeTimeScope()非常方便的功能.它允许我总是在执行子树中解析到同一个实例.好.

有时候我想要结合两种方法.我想在构造函数中的数据参数,并具有作用域的实例.我还没有找到一种令人满意的方法来实现这一目标.

解决方案

1.初始化方法

而不是将数据传递给构造函数,只需将其传递给Initialize方法.

接口:

interface IMyService
{
    void Initialize(Data data);
    void DoStuff();
}
Run Code Online (Sandbox Code Playgroud)

类:

class MyService : IMyService
{
    private Data mData;
    public void Initialize(Data data)
    {
        mData = data;
    }

    public void DoStuff()
    {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

注册:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
Run Code Online (Sandbox Code Playgroud)

用法:

var myService = context.Resolve<IMyService>();
myService.Init(data);

// somewhere else
var myService = context.Resolve<IMyService>();
Run Code Online (Sandbox Code Playgroud)

在第一次解析服务并调用Initialize后,我可以愉快地在同一个上下文中解析并获得相同的初始化实例.我不喜欢在调用之前Initialize我有一个无法使用的对象的事实.在调用Initialize()之前,存在实例将被解析并在其他地方使用的危险.

2.持有人模式

这是一个包含对数据对象的引用的模式,而不是注入数据对象本身,我注入了holder对象.

接口:

interface IMyService
{
    void DoStuff();
}
Run Code Online (Sandbox Code Playgroud)

类:

class MyService : IMyService
{
    private Data mData;
    public MyService(IDataHolder dataHolder)
    {
        mData = dataHolder.Data;
    }

    public void DoStuff()
    {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

注册:

builder.RegisterType<MyService>().As<IMyService>();
builder.RegisterType<DataHolder>().As<IDataHolder>().InstancePerLifetimeScope();
Run Code Online (Sandbox Code Playgroud)

用法:

var holder = context.Resolve<IDataHolder>();
holder.Data = data;

// somewhere else
var myService = context.Resolve<IMyService>();
Run Code Online (Sandbox Code Playgroud)

这更好一些,因为我将实例的责任移到了另一个类.我现在也可以在其他服务中使用持有者.其他优点是,如果需要,我可以在持有者中热插拔数据.我不喜欢它混淆代码并在测试期间添加另一个我必须模拟的接口这一事实.

3.让容器持有实例

接口:

interface IMyService
{
    void DoStuff();
}
Run Code Online (Sandbox Code Playgroud)

类:

class MyService : IMyService
{
    private Data mData;
    public MyService(Data data)
    {
        mData = dataHolder.Data;
    }

    public void DoStuff()
    {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

注册:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
Run Code Online (Sandbox Code Playgroud)

用法:

var myServiceFactory = context.Resolve<Func<Data, IMyService>>();
myServiceFactory(data);

// somewhere else
var myService = context.Resolve<IMyService>();
Run Code Online (Sandbox Code Playgroud)

那就对了.我不会在任何地方存储工厂调用的结果,因为autofac会为我存储它.对于任何阅读代码的人来说,这都是非常令人惊讶的.我不确定autofac是否意味着像这样使用.关于这一点的好处是我既不需要额外的初始化方法也不需要额外的类来保存实例.

你对此有什么看法?如何处理运行时数据参数和生命周期范围的情况?我错过了更好的方法吗?

Mar*_*ens 5

Autofac 现在支持开箱即用,并扩展了生命周期范围。该BeginLifetimeScope()方法有一个重载,Action<ContainerBuilder>它允许添加特定于该生命周期范围的新注册。所以对于给定的例子,它看起来像:

var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope(
  builder =>
  {
    builder.RegisterInstance(new Data(....));
  }))
{
  // References to 'IMyService' will always be resolved to the same instance within this lifetime scop
  // References to 'Data' will be resolved to the instance registered just for this lifetime scope.
  var svc = scope.Resolve<IMyService>();
}
Run Code Online (Sandbox Code Playgroud)