【问题标题】:Is there a pattern for initializing objects created via a DI container是否有用于初始化通过 DI 容器创建的对象的模式
【发布时间】:2010-12-28 22:50:45
【问题描述】:

我正在尝试让 Unity 管理我的对象的创建,并且我想要一些在运行时才知道的初始化参数:

目前我能想到的唯一方法是在接口上有一个 Init 方法。

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

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

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

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

这会产生一些问题,即Initialize 方法在接口上可用并且可以多次调用。在实现中设置标志并在重复调用 Initialize 时抛出异常似乎很笨拙。

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

编辑:多描述一下界面。

【问题讨论】:

  • 您错过了使用 DI 容器的意义。应该为您解决依赖关系。
  • 您从哪里获得所需的参数? (配置文件,数据库,??)
  • runTimeParam 是在运行时根据用户输入确定的依赖项。是否应该将其拆分为两个接口——一个用于初始化,另一个用于存储值?
  • IoC 中的依赖,通常是指对其他 ref 类型的类或对象的依赖,可以在 IoC 初始化阶段确定。如果你的类只需要一些值来工作,那么你的类中的 Initialize() 方法就派上用场了。
  • 我的意思是假设您的应用中有 100 个类可以应用这种方法;那么你必须为你的类创建额外的 100 个工厂类 + 100 个接口,如果你只是使用 Initialize() 方法,你可以侥幸逃脱。

标签: dependency-injection inversion-of-control unity-container ioc-container interface-design


【解决方案1】:

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

在接口上使用 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 关键字来保护类的不变量。不需要臭的 Initialize 方法。

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

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

在您需要IMyIntf 实例的所有消费者中,您只需通过构造函数注入 请求IMyIntfFactory 即可。

如果您正确注册,任何值得称道的 DI 容器都可以为您自动连接 IMyIntfFactory 实例。

【讨论】:

  • 问题在于方法(如 Initialize)是 API 的一部分,而构造函数不是。 blog.ploeh.dk/2011/02/28/InterfacesAreAccessModifiers.aspx
  • 此外,Initialize 方法表示时间耦合:blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling.aspx
  • @Darlene 您可能可以使用延迟初始化的装饰器,如my book 的第 8.3.6 节所述。我还在我的演示文稿Big Object Graphs Up Front 中提供了一个类似的例子。
  • @Mark 如果工厂创建MyIntf 实现需要的不仅仅是runTimeParam(阅读:希望由IoC 解决的其他服务),那么您仍然面临解决这些问题工厂中的依赖项。我喜欢 @PhilSandler 将这些依赖项传递给 factory's 构造函数来解决这个问题的答案 - 你也是这样吗?
  • 也是很棒的东西,但your answer 对这个其他问题真的很明白我的意思。
【解决方案2】:

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

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

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>();

然后你只需在 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 上的项目网站上有更多文档。

【讨论】:

    【解决方案3】:

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

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

    拥有 setter 或 Initialize() 方法通常被认为是糟糕的设计,因为您需要“记住”调用它们并确保它们不会打开太多的实现状态(即是什么阻止某人重新调用初始化或设置器?)。

    【讨论】:

      【解决方案4】:

      我想我解决了它,感觉很健康,所以它一定是对的:))

      我将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 paradigm 的位我们可以很好地包装它,这样IMyIntfSetter 几乎是一个与IMyIntf 不同的内部接口。

      【讨论】:

      • 这不是一个特别好的解决方案,因为它依赖于 Initialize 方法,这是一种泄漏抽象。顺便说一句,这看起来不像服务定位器,而更像是接口注入。无论如何,请参阅我的答案以获得更好的解决方案。
      【解决方案5】:

      我无法回答具体的 Unity 术语,但听起来您只是在学习依赖注入。如果是这样,我敦促您阅读简短、清晰且信息丰富的 user guide for Ninject

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

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

      即使您不使用 Ninject,演练也会为您提供适合您目的的功能的概念和术语,并且您应该能够将这些知识映射到 Unity 或其他 DI 框架(或说服您使用 Ninject试一试)。

      【讨论】:

      猜你喜欢
      • 2018-02-10
      • 2016-03-16
      • 2010-12-06
      • 2012-09-23
      • 2019-07-15
      • 1970-01-01
      • 2023-03-31
      • 2015-06-03
      • 1970-01-01
      相关资源
      最近更新 更多