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

Igo*_*aka 145 interface-design dependency-injection ioc-container inversion-of-control unity-container

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

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

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}
Run Code Online (Sandbox Code Playgroud)

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

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
Run Code Online (Sandbox Code Playgroud)

在这种情况下,runTimeParamparam是在运行时根据用户输入确定的.这里的简单案例只返回值,runTimeParam但实际上参数将类似于文件名,初始化方法将对文件执行某些操作.

这会产生许多问题,即该Initialize方法在界面上可用并且可以多次调用.在实现中设置一个标志并在重复调用时抛出异常Initialize似乎很笨重.

在解析我的界面时,我不想知道有关实现的任何信息IMyIntf.但是,我想要的是知道这个接口需要一定的一次初始化参数.有没有办法以某种方式注释(属性?)具有此信息的接口,并在创建对象时将它们传递给框架?

编辑:更多地描述了界面.

Mar*_*ann 270

在任何需要运行时值来构造特定依赖项的地方,Abstract Factory就是解决方案.

在接口上初始化方法有漏洞抽象的气味.

在你的情况下,我会说你应该IMyIntf根据你需要如何使用它来建模界面- 而不是你打算如何创建它的实现.这是一个实现细节.

因此,界面应该只是:

public interface IMyIntf
{
    string RunTimeParam { get; }
}
Run Code Online (Sandbox Code Playgroud)

现在定义抽象工厂:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}
Run Code Online (Sandbox Code Playgroud)

您现在可以创建一个具体的实现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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意这是如何允许我们通过使用关键字来保护类的不变量readonly.没有臭臭的Initialize方法是必要的.

一个IMyIntfFactory实现可能是这么简单:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

任何值得盐的DI容器都可IMyIntfFactory以为你自动连接一个实例,如果你正确注册它.

  • 问题是方法(如Initialize)是API的一部分,而构造函数则不是.http://blog.ploeh.dk/2011/02/28/InterfacesAreAccessModifiers.aspx (12认同)
  • 此外,Initialize方法指示时间耦合:http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling.aspx (12认同)
  • @Darlene你可能能够使用一个懒惰的初始化装饰器,如[我的书](http://bit.ly/b5Vir7)的8.3.6节所述.我还在我的演示文稿[Big Object Graphs Up Front](https://vimeo.com/68378923)中提供了类似的示例. (2认同)
  • @Mark 如果工厂创建 `MyIntf` 实现需要的不仅仅是 `runTimeParam`(阅读:人们希望由 IoC 解决的其他服务),那么您仍然需要解决工厂中的这些依赖项。我喜欢@PhilSandler 将这些依赖项传递给 *factory's* 构造函数来解决这个问题的答案——这也是你的看法吗? (2认同)
  • 同样很棒的东西,但[你的答案](http://stackoverflow.com/a/1686887/533958)这个问题真的到了我的意思. (2认同)

Phi*_*ler 14

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

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

设置setter或Initialize()方法通常被认为是糟糕的设计,因为你需要"记住"调用它们并确保它们不会打开太多的实现状态(即什么阻止某人重新启动 - 调用初始化或设置者?).


小智 5

我也在我基于Model对象动态创建ViewModel对象的环境中遇到过这种情况几次(这个其他Stackoverflow帖子很好地概述了).

我喜欢Ninject扩展,它允许您基于接口动态创建工厂:

Bind<IMyFactory>().ToFactory();

我在Unity中直接找不到任何类似的功能; 所以我编写了自己的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>();
Run Code Online (Sandbox Code Playgroud)

然后,您只需在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));
}
Run Code Online (Sandbox Code Playgroud)

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

显然,这不会解决所有问题,但到目前为止它对我有好处,所以我认为我应该分享它.GitHub上有关该项目网站的更多文档.