构造函数注入替代品(Castle Windsor)

时间:2010-12-09 17:59:07

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

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

我喜欢构造函数注入,在大多数位置......

记录我不喜欢的例子。如果我有一个基类,许多其他类继承,我希望所有这些类都使用我的ILogger(或其他)的实例,我不想要一个静态工厂(Logger.Instance)...我不希望必须在每个带有ILogger的子类上声明构造函数。

所以,我可以让我的基类将logger声明为Property并让它以这种方式注入

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

......但是

  1. 这并不能保证Logger 实际被注入并且不是null。
  2. 我不喜欢让ILogger使用公共场所
  3. 那么......我还有其他选择吗? (我正在使用Castle Windsor)。

    我打算制作一个界面

    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<T>的所有实现......

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

3 个答案:

答案 0 :(得分:3)

你这太复杂了。 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; }
   }

   // ...
}

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

如果您的基类本身不依赖于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框架。作为一个缺点,还有更多工作要做(创建一个工厂,注入该工厂而不是实例本身,并调用工厂来创建一个新实例)。

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

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;
    }
}

我希望这会有所帮助。