如何修饰依赖于运行时值进行创建的类

时间:2014-03-12 23:40:37

标签: c# dependency-injection decorator simple-injector

我是使用Simple Injector的新手,虽然我已经使用Ninject很长时间了,所以我对DI很满意。吸引我使用Simple Injector的一件事是装饰器的易用性。

我已经能够在所有正常情况下成功使用带有Simple Injector的装饰器,在请求服务时解析依赖关系。但是,我很难弄清楚是否有办法在必须使用运行时值构造服务的情况下应用装饰器。

在Ninject中,我可以将ConstructorArgument传递给kernel.Get<IService>请求,该请求可以从N个装饰器的链中继承到&#34; real&#34;实施课程。我无法找到使用Simple Injector复制它的方法。

我在下面放了一些非常基本的代码来说明。我想在现实世界中做的是将IMyClassFactory实例传递给我的应用程序中的其他类。然后,其他类可以使用它们使用它们提供的IMyClass来创建IRuntimeValue个实例。他们从IMyClass获得的IMyClassFactory实例将由注册的装饰者自动修饰。

我知道我可以在我的IMyClassFactory或我想出的任何Func<IMyClass>中手动应用我的装饰者,但我希望能够#34;只是工作&#34;。

我一直在四处寻找MyClass构造,但我无法弄清楚如何使用IRuntimeValue构造函数参数来解析它并进行修饰。< / p>

我是否忽视了一个明显的解决方案?

using System;
using SimpleInjector;
using SimpleInjector.Extensions;

public class MyApp
{
    [STAThread]
    public static void Main()
    {
        var container = new Container();
        container.Register<IMyClassFactory, MyClassFactory>();
        container.RegisterDecorator(typeof (IMyClass), typeof (MyClassDecorator));

        container.Register<Func<IRuntimeValue, IMyClass>>(
                 () => r => container.GetInstance<IMyClassFactory>().Create(r));

        container.Register<IMyClass>(() =>  ?????));  // Don't know what to do

        container.GetInstance<IMyClass>(); // Expect to get decorated class
    }
}

public interface IRuntimeValue
{
}

public interface IMyClass
{
    IRuntimeValue RuntimeValue { get; }
}

public interface IMyClassFactory
{
    IMyClass Create(IRuntimeValue runtimeValue);
}

public class MyClassFactory : IMyClassFactory
{
    public IMyClass Create(IRuntimeValue runtimeValue)
    {
        return new MyClass(runtimeValue);
    }
}

public class MyClass : IMyClass
{
    private readonly IRuntimeValue _runtimeValue;

    public MyClass(IRuntimeValue runtimeValue)
    {
        _runtimeValue = runtimeValue;
    }

    public IRuntimeValue RuntimeValue
    {
        get
        {
            return _runtimeValue;
        }
    }
}

public class MyClassDecorator : IMyClass
{
    private readonly IMyClass _inner;

    public MyClassDecorator(IMyClass inner)
    {
        _inner = inner;
    }

    public IRuntimeValue RuntimeValue
    {
        get
        {
            return _inner.RuntimeValue;
        }
    }
}

修改1:

好的,感谢史蒂文的出色答案。它给了我一些想法。

也许是为了让它更具体一点(虽然不是我的情况,更多&#34;经典&#34;)。假设我在运行时通过读取数据库或从磁盘或其他东西反序列化来创建ICustomer。所以我想这将被认为是一个新的&#34;引用其中一个articles Steven链接。我想创建一个ICustomerViewModel的实例,这样我就可以显示和操作我的ICustomer。我的具体CustomerViewModel类在其构造函数中引入ICustomer以及可由容器解析的另一个依赖项。

所以我有ICustomerViewModelFactory定义了.Create(ICustomer customer)方法,返回ICustomerViewModel。在我提出这个问题之前,我总是可以开始工作,因为在ICustomerViewModelFactory的实现中,我可以这样做(工厂在组合根中实现):

return new CustomerViewModel(customer, container.GetInstance<IDependency>());

我的问题是,我希望我的ICustomerViewModel能够被容器装饰,然后通过容器对其进行修改。现在我知道如何解决这个限制。

所以我想我的后续问题是:我的设计首先是错误的吗?我真的觉得应该将ICustomer传递给CustomerViewModel的构造函数,因为这表明它是必需的,得到验证的等等。我不想在事后添加它。

1 个答案:

答案 0 :(得分:7)

Simple Injector明确缺乏对通过GetInstance方法传递运行时值的支持。原因是在构造对象图时不应使用运行时值。换句话说,injectables的构造函数不应该依赖于运行时值。这样做有几个问题。首先,您的注射剂可能需要比那些运行时值更长的寿命。但也许更重要的是,您希望能够verifydiagnose容器的配置,当您开始在对象图中使用运行时值时,这会变得更加麻烦。

所以一般来说有两种解决方案。您可以通过方法调用图传递运行时值,也可以创建一个“上下文”服务,该服务可以在请求时提供此运行时值。

当您练习thisthis等体系结构时,通过调用图传递运行时值是一种有效的解决方案,您可以通过系统传递消息,或者运行时值很明显服务合同的一部分。在这种情况下,使用消息或方法传递运行时值很容易,并且此运行时值也将通过任何装饰器。

在您的情况下,这意味着工厂创建IMyService而不传递IRuntimeValue,并且您的代码使用其指定的方法将此值传递给IMyService

var service = _myServiceFactory.Create();
service.DoYourThing(runtimeValue);

通过调用图传递运行时值并不总是一个好的解决方案。特别是当此运行时值不应该是发送的消息的合同的一部分时。这尤其适用于上下文信息用作有关当前登录用户的信息,当前系统时间等。您不希望传递此信息;你只是希望它可用。我们不希望这样,因为这会给消费者带来额外的负担,即每次传递正确的值,而他们甚至可能甚至无法更改此信息(将用户放在执行请求的上下文中)实例)。

在这种情况下,您应该定义可以注入的服务并允许检索此上下文。例如:

public interface IUserContext {
    User CurrentUser { get; }
}

public interface ITimeProvider {
    DateTime Now { get; }
}

在这些情况下,当前用户和当前时间不直接注入构造函数,而是这些服务。需要访问当前用户的组件可以简单地调用_userContext.CurrentUser,这将在构造对象之后完成(在构造函数中读取:)。因此:以懒惰的方式。

但这确实意味着必须在IRuntimeValue被调用之前的某处设置MyClass。这可能意味着您需要在工厂内进行设置。这是一个例子:

var container = new Container();
var context = new RuntimeValueContext();
container.RegisterSingle<RuntimeValueContext>(context);
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof(IMyClass), typeof(MyClassDecorator));
container.Register<IMyClass, MyClass>();

public class RuntimeValueContext {
    private ThreadLocal<IRuntimeValue> _runtime;
    public IRuntimeValue RuntimeValue {
        get { return _runtime.Value; }
        set { _runtime.Value = value; }
    }
}

public class MyClassFactory : IMyClassFactory {
    private readonly Container _container;
    private readonly RuntimeValueContext context;
    public MyClassFactory(Container container, RuntimeValueContext context) {
        _container = container;
        _context = context;
    }

    public IMyClass Create(IRuntimeValue runtimeValue) {
        var instance = _container.GetInstance<IMyClass>();
        _context.RuntimeValue = runtimeValue;
        return instance;
    }
}

public class MyClass : IMyClass {
    private readonly RuntimeValueContext _context;
    public MyClass(RuntimeValueContext context) {
        _context = context;
    }
    public IRuntimeValue RuntimeValue { get { return _context.Value; } }
}

您也可以让MyClass接受IRuntimeValue并进行以下注册:

container.Register<IRuntimeValue>(() => context.Value);

但是不允许验证对象图,因为Simple Injector将确保注册永远不会返回null,但默认情况下context.Value将为null。所以另一个选择是执行以下操作:

container.Register<IMyClass>(() => new MyClass(context.Value));

这允许验证IMyClass注册,但在验证期间仍会创建一个注入空值的新MyClass实例。如果MyClass构造函数中有一个保护子句,则会失败。但是,此注册不允许容器自动连接MyClass。例如,当您有更多依赖注入MyClass时,自动连接该类可以派上用场。