是否有一种模式用于初始化通过 DI 容器创建的对象

我试图让 Unity 管理我的对象的创建,我想有一些初始化参数,直到运行时才知道:

目前,我能想到的唯一方法是在接口上使用 Init 方法。

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

然后使用它(在 Unity 中)我会这样做:

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

在这种情况下,runTimeParam参数是在运行时根据用户输入确定的。这里的例子只是返回 runTimeParam的值,但实际上参数类似于 file name,initialize 方法将对文件执行某些操作。

这会产生许多问题,即 Initialize方法在接口上是可用的,并且可以被多次调用。在实现中设置一个标志并在重复调用 Initialize时抛出异常看起来很笨拙。

在解析接口的时候,我不想知道关于 IMyIntf实现的任何信息。但是,我真正想要的是了解这个接口需要某些一次性的初始化参数。是否有方法以某种方式注释(属性?)这些信息的接口,并传递给框架时,对象创建?

编辑: 对接口进行了更多的描述。

77410 次浏览

我不能用具体的 Unity 术语来回答这个问题,但是听起来你好像才刚刚开始学习依赖注入。如果是这样,我敦促您阅读简短,明确,信息包装 注入用户指南

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

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

即使你不使用 Njet,这个演练也会给你适合你的功能的概念和术语,你应该能够把这些知识映射到 Unity 或者其他 DI 框架(或者说服你尝试一下)。

我想我解决了这个问题,而且感觉相当健康,所以肯定有一半是对的:)

我将 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()仍然可以被多次调用,但是使用 服务定位器范例的比特,我们可以很好地将它包装起来,使得 IMyIntfSetter几乎是一个与 IMyIntf不同的内部接口。

通常,当您遇到这种情况时,您需要重新审视您的设计,并确定是否将有状态/数据对象与纯服务混合在一起。在大多数情况下(并非所有情况下) ,您需要将这两种类型的对象分开。

如果确实需要在构造函数中传递特定于上下文的参数,一种选择是创建一个工厂,通过构造函数解析服务依赖关系,并将运行时参数作为 Create ()方法(或 Generate ()、 Build ()或任何您命名的工厂方法)的参数。

使用 setter 或 Initialize ()方法被认为是糟糕的设计,因为你需要“记住”调用它们,并确保它们不会打开太多的实现状态(例如,如何阻止某人重新调用 Initialize 或 setter?).

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

抽象漏洞定律的接口上使用 Initialize 方法。

在您的情况下,我会说您应该在 你需要如何使用它上建模 IMyIntf接口——而不是您打算如何创建它的实现。这是一个执行细节。

因此,界面应该简单明了:

public interface IMyIntf
{
string RunTimeParam { get; }
}

现在定义抽象工厂:

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

您现在可以创建 IMyIntfFactory的具体实现,它创建 IMyIntf的具体实例,如下所示:

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关键字来实现 保护类的不变量的。

IMyIntfFactory的实现可能就这么简单:

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

在所有需要 IMyIntf实例的使用者中,只需通过 构造函数注入请求对 IMyIntfFactory的依赖关系。

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

我也曾在基于 Model 对象动态创建 ViewModel 对象的环境中遇到过几次这种情况(另一个 堆栈溢出后很好地描述了这种情况)。

我喜欢 注射扩展,它允许你基于接口动态地创建工厂:

绑定 < IMyFactory > () . ToFactory () ;

我无法在 团结中直接找到任何类似的功能; 所以我编写了自己的 IUnityContainer扩展,它允许您注册工厂,这些工厂将基于现有对象的数据创建新对象,基本上从一个类型层次结构映射到另一个类型层次结构: 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));
}

另外,映射类的构造函数中的任何附加依赖关系也将在对象创建期间得到解决。

显然,这并不能解决所有的问题,但是到目前为止它对我很有帮助,所以我认为我应该分享它。在 GitHub 的项目站点上有更多的文档。