2014-03-12 25 views
5

虽然我一直在使用Ninject,但我一般都很喜欢DI,所以我是使用Simple Injector的全新品牌。吸引我想要使用Simple Injector的一件事是装饰者的易用性。如何装饰依赖运行时值创建的类

我已经能够在所有正常情况下使用Simple Injector成功使用装饰器,在请求服务时解决了依赖关系。然而,我很难弄清楚在服务必须使用运行时值构建的情况下是否有办法让我的装饰器应用于案例中。

在Ninject中,我可以将ConstructorArgument传递给kernel.Get<IService>请求,该请求可以沿着N个装饰器链一路继承到“真正的”实现类。我无法想出一个方法来复制使用简单注射器。

我已经在下面说明了一些非常基本的代码。我想在现实世界中做的事情是将IMyClassFactory实例传递给我的应用程序中的其他类。那些其他类可以使用它们创建IMyClass实例,使用它们提供的IRuntimeValue。他们从IMyClassFactory得到的IMyClass实例将被注册的装饰器自动装饰。

我知道我可以在我的IMyClassFactory或任何Func<IMyClass>中手动应用我的装饰器,但我希望它能“正常工作”。

我一直在试图抽象出MyClass构造,但我无法弄清楚如何使用IRuntimeValue构造函数参数来解决它并进行装饰。

我忽略了一个明显的解决方案吗?

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:

好,感谢史蒂芬为伟大的答案。它给了我一些想法。

虽然不是我的情况,但更多的是“经典”。假设我有一个ICustomer,它是在运行时通过读取数据库或从磁盘反序列化或创建的东西创建的。所以我想这将被认为是一个“新”引用史蒂文连接articles之一。我想创建一个ICustomerViewModel的实例,以便我可以显示和操作我的ICustomer。我的具体CustomerViewModel类在其构造函数中接受了ICustomer以及可以由容器解析的另一个依赖项。

所以我有一个ICustomerViewModelFactory有一个.Create(ICustomer customer)定义方法返回ICustomerViewModel。我总能得到这个工作之前,我问这个问题,因为我在执行的ICustomerViewModelFactory我能做到这一点(厂组成根实现):

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

我的问题是,我想我的ICustomerViewModel由容器进行装饰并且将它推到新的地位。现在我知道如何解决这个限制。

所以我想我的后续问题是:我的设计是否在第一个地方错了?我真的觉得ICustomer应该被传递到CustomerViewModel的构造函数中,因为它演示了它的意图,它是必需的,得到验证等。我不想在事后添加它。

回答

6

简单的喷油器显然不支持通过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); 

,但在验证对象图中不允许的,因为简单的喷油器将确保注册有去无回null,但默认情况下context.Value将为空。因此,另一种选择是做到以下几点:

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

这使得IMyClass登记待考证,但在验证过程中仍然创造注射有一个空值的新MyClass实例。如果在MyClass构造函数中有一个保护子句,则这将失败。但是,此注册不允许MyClass由容器自动连线。例如,当你有更多的依赖关系注入MyClass时,该类可以派上用场。

+1

此外,如果服务取决于运行时值,那么您已经将一些知识用于服务的实现方面,这是您尝试避免的一点。如果一个实现需要传递数字42,那么这个数字是否与不同的实现有关? 42是什么意思? –

+0

@ LasseV.Karlsen我完全同意你的价值类型和类似的东西。但我试图让我的脑袋里说,运行时的价值是ICustomer刚刚从数据库中读入,我需要将它(以及一些其他容器解析的依赖关系)传递给一个构造函数说一个CustomerViewModel。虽然史蒂文给了我很多想法,但是这是否是正确的设计。 –

+0

这是我消化和思考的很多信息(并且感谢您的链接并帮助我陷入成功之坑)。我将添加一个编辑以作进一步评论。实际上我自己想出了你自己的第二个解决方案,但是有点害怕它,因为1)我从来没有使用ThreadLocal存储(整洁),2)我担心可能存在一些其他的副作用,在上下文中引用对象。但那可能是我走的路。 –