【问题标题】:Constructor Injection Alternatives (Castle Windsor)构造函数注入替代方案(温莎城堡)
【发布时间】:2011-05-23 00:53:27
【问题描述】:

我喜欢依赖注入的构造函数注入。它强制从类型中明确声明关注点,并有助于可测试性。

我喜欢构造函数注入,在大多数地方...

以日志为例,我不喜欢它。如果我有一个许多其他类继承自的基类,并且我希望所有这些类都使用我的 ILogger 的实例(或其他),并且我不想要静态工厂(Logger.Instance)......我不想在每个接受 ILogger 的子类上声明构造函数。

所以,我可以让我的基类将记录器声明为属性并以这种方式注入

public class MyBaseClass 
{
   public ILogger Logger { get; set; }
}

...但是

  1. 这并不能保证 Logger 实际上被注入并且不为空。
  2. 我不喜欢 ILogger 带有公共集

那么...我还有什么其他选择? (我使用的是温莎城堡)。

我已经考虑过做一个界面

public interface IInitializable<T>
{
    void Initialize(T instance); 
}

public class MyBaseClass : IInitializable<ILogger>, ...could have other IInitializables too...
{
   protected ILogger Logger { get; private set; }

   public void Initialize(ILogger instance) 
   { 
         Logger = instance;
   }
}

然后在我的容器上拥有一个工具,可以在类型构造时自动调用 IInitializable&lt;T&gt; 的所有实现...

但在我走这条路之前,我想知道其他人的想法是什么......

【问题讨论】:

  • 为什么不想使用静态工厂模式?
  • Ctor 注入等,对于不同的测试实现非常有用,等等,但你真的需要这个来记录吗?你真的需要在实例级别改变日志实现的能力吗?静态工厂模式仍然可以让你改变整体日志实现,这通常是需要的。
  • Logger 返回什么的实现需要是动态的。具体而言,Logger.Instance 应根据上下文(即 WCF 操作、WPF 客户端、SL 客户端等)而有所不同。我想我可以在 .Current 中做一个服务定位器,但据我所知,这是一种反模式......
  • 看来您需要根据运行时环境(例如 WPF 客户端、SL 客户端等)而不是类型实例级别来改变日志记录。我认为此时与 DI 结合使用的静态工厂非常适合。

标签: .net dependency-injection inversion-of-control castle-windsor


【解决方案1】:

在你的情况下,我会使用属性注入。

属性注入可以切换为强制,如下所述: http://www.mail-archive.com/castle-project-users@googlegroups.com/msg08163.html

【讨论】:

  • 对如何防止修改有什么建议吗?
【解决方案2】:

你把这件事复杂化了。 recommended and documented pattern to inject ILoggerNullLogger.Instance 作为默认值(即null object pattern)并使Logger 成为可选依赖项。为记录器设置公共设置器并没有错。使用自定义的IInitializable 就像您展示的那样可能只会使事情复杂化并且不会贡献任何实际价值。

我将从此处的文档中复制示例以方便参考:

using Castle.Core.Logging;

public class CustomerService
{
   private ILogger logger = NullLogger.Instance;

   public CustomerService()
   {
   }

   public ILogger Logger
   {
      get { return logger; }
      set { logger = value; }
   }

   // ...
}

编辑:似乎问题实际上是关于根据上下文使用不同的记录器实现(这与原始问题几乎没有关系)。如果是这种情况,请使用服务覆盖或处理程序选择器。

【讨论】:

  • 关于如何防止修改的任何建议?如果我希望继承类能够利用服务但不更改其实现怎么办?
  • @JeffN825:为什么会有人首先修改它?
  • @JeffN825:顺便说一句,你不能同时拥有它。它要么是private readonly 和构造函数注入,要么是可变属性。据我所知,.net 中没有其他选项。
  • 基类应该可以修改,所以不需要只读(即public ILogger MyLogger { get; private set; } )
  • 不是他们要修改,而是这是一个API,我不想让他们修改。
【解决方案3】:

如果您的基类本身不依赖于ILogger,您应该从基类中删除该属性。这样你就可以保持基类构造函数的干净。

否则,您可以创建一个能够创建MyBaseClass 后代的工厂。这可能看起来像这样:

public interface IMyBaseClassFactory
{
    MyBaseClass CreateNew<TMyBaseClass>() where TMyBaseClass : MyBaseClass;
}

现在您可以创建一个注册 IMyBaseClassFactory 的实现,它能够创建新实例并注册可选依赖项:

public MyBaseClassFactoryImpl : IMyBaseClassFactory
{
    public MyBaseClass CreateNew<TMyBaseClass>()
    {
        // Call your IoC container to get a descendant.
        var instance = ServiceLocator.GetInstance<TMyBaseClass>();

        // Register missing dependencies
        instance.Logger = ServiceLocator.GetInstance<ILogger>();

        return instance;
    }
}

大多数 IoC 容器允许您使用属性来装饰可注入属性。然而,使用工厂的好处是,您的应用程序将完全不了解所使用的 IoC 框架。不利的一面是,还有更多工作要做(创建工厂,注入工厂而不是实例本身,并调用工厂来创建新实例)。

当定义一个可注入属性时,你需要让它读/写。当然,您不希望任何人事后意外重置该属性。如果没有构造函数注入,则很难通过编译时支持来实现这一点。但是,很容易进行运行时检查:

private ILogger logger;

public ILogger Logger
{
    get { return this.logger; }
    set
    {
        if (this.logger != null)
        {
            throw new InvalidOperationException("Logger has already been set.");
        }

        this.logger = value;
    }
}

我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-27
    • 1970-01-01
    相关资源
    最近更新 更多