关于.net:构造函数注入替代方案(Castle Windsor)

Constructor Injection Alternatives (Castle Windsor)

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

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

作为一个我不喜欢的例子记录。如果我有一个基类,许多其他类都从这个基类继承,我希望所有这些类都使用我的i logger(或其他类)的一个实例,并且我不希望使用静态工厂(logger.instance)…我不希望在每个接受ilogger的子类上声明构造函数。

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

1
2
3
4
public class MyBaseClass
{
   public ILogger Logger { get; set; }
}

但…

  • 这并不能保证记录器确实被注入并且不是空的。
  • 我不喜欢在公共场合自言自语
  • 那么……我还有别的选择吗?(我在使用温莎城堡)。

    我打算做一个界面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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的所有实现的工具…

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


    你太复杂了。注入ilogger的推荐和文档化模式是将NullLogger.Instance作为默认模式(即空对象模式),并使Logger成为可选的依赖项。为记录器设置公共设置器没有任何问题。像你展示的那样使用一个自定义的IInitializable可能只会使事情复杂化,不会产生任何实际价值。

    我将从这里的文档中复制样本,以便于参考:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    using Castle.Core.Logging;

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

       public CustomerService()
       {
       }

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

       // ...
    }

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


    在您的情况下,我将使用属性注入。

    属性注入可以切换为强制,如下所述:http://www.mail-archive.com/[email protected]/msg08163.html


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

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

    1
    2
    3
    4
    public interface IMyBaseClassFactory
    {
        MyBaseClass CreateNew<TMyBaseClass>() where TMyBaseClass : MyBaseClass;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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框架。作为缺点,还有更多的工作要做(创建一个工厂,注入该工厂而不是实例本身,并调用工厂来创建一个新的实例)。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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;
        }
    }

    我希望这有帮助。