是否有初始化通过DI容器创build的对象的模式

我想让Unity来pipe理我的对象的创build,我想有一些初始化参数,直到运行时才知道:

目前唯一能想到的方法是在界面上使用Init方法。

interface IMyIntf { void Initialize(string runTimeParam); string RunTimeParam { get; } } 

然后使用它(在统一)我会这样做:

 var IMyIntf = unityContainer.Resolve<IMyIntf>(); IMyIntf.Initialize("somevalue"); 

在这种情况下, runTimeParam参数是在运行时根据用户input确定的。 这里的小事情只是返回runTimeParam的值,但实际上这个参数是类似文件名的,初始化方法会对文件做一些事情。

这就产生了一些问题,即Initialize方法在接口上可用,可以多次调用。 在实现中设置一个标志,并抛出exception重复调用Initialize似乎是笨重的。

在我解决我的界面的时候,我不想了解IMyIntf的实现。 我所需要的是,这个接口需要一定的初始化参数。 有没有办法用这个信息注释(属性?)接口,并在创build对象时将它们传递给框架?

编辑:描述更多的接口。

在任何需要运行时值来构造特定依赖项的地方, 抽象工厂就是解决scheme。

初始化泄漏抽象接口的方法。

在你的情况下,我会说,你应该如何build模IMyIntf接口如何使用它 – 而不是你如何创build它的实现。 这是一个实现细节。

因此,界面应该简单地是:

 public interface IMyIntf { string RunTimeParam { get; } } 

现在定义Abstract Factory:

 public interface IMyIntfFactory { IMyIntf Create(string runTimeParam); } 

现在可以创build一个IMyIntfFactory的具体实现,创buildIMyIntfFactory具体实例,如下所示:

 public class MyIntf : IMyIntf { private readonly string runTimeParam; public MyIntf(string runTimeParam) { if(runTimeParam == null) { throw new ArgumentNullException("runTimeParam"); } this.runTimeParam = runTimeParam; } public string RunTimeParam { get { return this.runTimeParam; } } } 

注意这是如何使我们能够通过使用readonly关键字来保护类的不variables 。 没有臭味初始化方法是必要的。

IMyIntfFactory实现可能如此简单:

 public class MyIntfFactory : IMyIntfFactory { public IMyIntf Create(string runTimeParam) { return new MyIntf(runTimeParam); } } 

在您需要IMyIntf实例的所有消费者中,只需通过构造函数注入来请求对IMyIntfFactory的依赖IMyIntfFactory

如果你正确地注册它,任何值得它的salt的DI容器都能够为你自动连接一个IMyIntfFactory实例。

通常当遇到这种情况时,您需要重新访问您的devise,并确定是否将有状态/数据对象与纯服务混合在一起。 在大多数(不是全部)的情况下,你会想把这两种types的对象分开。

如果您确实需要在构造函数中传递的特定于上下文的参数,则一种select是创build一个工厂,通过构造函数parsing您的服务依赖关系,并将您的运行时参数作为Create()方法(或Generate ),Build()或者你的工厂方法的名称)。

有setter或Initialize()方法通常被认为是不好的devise,因为你需要“记住”调用它们,并确保它们不打开太多的实现状态(即什么是阻止某人重新启动 – 初始化或setter?)。

我也遇到过这种情况几次,我dynamic创build基于模型对象的ViewModel对象(由其他Stackoverflowpost很好地概述)。

我喜欢Ninject扩展 ,它允许你基于接口dynamic地创build工厂:

Bind<IMyFactory>().ToFactory();

我无法直接在Unity中find任何类似的function; 所以我写了我自己的扩展到IUnityContainer ,它允许你注册工厂,根据现有对象的数据创build新的对象,从一个types层次结构到另一个types层次结构: UnityMappingFactory @ GitHub

为了简单和可读性的目标,我最终得到了一个扩展,允许您直接指定映射,而无需声明单独的工厂类或接口(实时保护程序)。 您只需在正常的引导过程中注册类的地方添加映射…

 //make sure to register the output... container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>(); container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>(); //define the mapping between different class hierarchies... container.RegisterFactory<IWidget, IWidgetViewModel>() .AddMap<IImageWidget, IImageWidgetViewModel>() .AddMap<ITextWidget, ITextWidgetViewModel>(); 

然后,您只需在CI的构造函数中声明映射工厂接口,并使用其Create()方法…

 public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { } public TextWidgetViewModel(ITextWidget widget) { } public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory) { IList<IWidgetViewModel> children = new List<IWidgetViewModel>(); foreach (IWidget w in data.Widgets) children.Add(factory.Create(w)); } 

作为额外的好处,映射类的构造函数中的其他依赖项也将在对象创build期间得到解决。

显然,这并不能解决所有的问题,但到目前为止,我已经做得相当好了,所以我想我应该分享一下。 在GitHub上的项目网站上有更多的文档。

我无法用特定的Unity术语回答,但听起来你只是在学习dependency injection。 如果是这样,我build议您阅读Ninject的简要,清晰和信息包装的用户指南 。

这将引导您了解使用DI时的各种选项,以及如何说明您将要面对的具体问题。 在你的情况下,你很可能想使用DI容器来实例化你的对象,并让这个对象通过构造函数获得对它的每个依赖的引用。

该演练还详细介绍了如何使用属性注释方法,属性甚至参数以在运行时区分它们。

即使你不使用Ninject,演练会给你适合你的目的的function的概念和术语,你应该能够将这些知识映射到Unity或其他DI框架(或者说服你试试Ninject) 。

我觉得我解决了这个问题,感觉很健康,所以它一定是对的:))

我将IMyIntf分为“getter”和“setter”接口。 所以:

 interface IMyIntf { string RunTimeParam { get; } } interface IMyIntfSetter { void Initialize(string runTimeParam); IMyIntf MyIntf {get; } } 

然后执行:

 class MyIntfImpl : IMyIntf, IMyIntfSetter { string _runTimeParam; void Initialize(string runTimeParam) { _runTimeParam = runTimeParam; } string RunTimeParam { get; } IMyIntf MyIntf {get {return this;} } } //Unity configuration: //Only the setter is mapped to the implementation. container.RegisterType<IMyIntfSetter, MyIntfImpl>(); //To retrieve an instance of IMyIntf: //1. create the setter IMyIntfSetter setter = container.Resolve<IMyIntfSetter>(); //2. Init it setter.Initialize("someparam"); //3. Use the IMyIntf accessor IMyIntf intf = setter.MyIntf; 

IMyIntfSetter.Initialize()仍然可以被多次调用,但是使用Service Locator范例的位,我们可以很好地包装它,这样IMyIntfSetter几乎是一个与IMyIntf不同的内部接口。