构造函数注入替代方案(温莎城堡)

发布于 2024-10-07 01:10:39 字数 977 浏览 0 评论 0原文

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

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

以日志记录为例,我不喜欢它。如果我有一个基类,许多其他类都继承自该基类,并且我希望所有这些类都使用我的 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;
   }
}

,然后在我的容器上建立一个设施,在类型构造时自动调用 IInitialized 的所有实现...

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

I like Constructor injection for dependency injection. It forces a clear declaration of concerns from a type and helps with testability.

I like Constructor injection, in most places...

Logging as an example where I do not like it. If I have a base class from which many other classes inherit, and I want all of those classes to use an instance of my ILogger (or whatever), and I don't want a static factory (Logger.Instance)...I don't want to have to declare a constructor on every sub-class that takes an ILogger.

So, I could have my base class declare the logger as a Property and have it injected that way

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

...but

  1. That doesn't assure me that Logger actually gets injected and is not null.
  2. I don't like having ILogger with a public set

So...what other options do I have? (I'm using Castle Windsor).

I've contemplated making an interface

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

Then having a facility on my container that automatically calls all implementations of IInitializable<T> upon type construction...

But I'm wondering what other peoples' thoughts are before I go that route...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

凝望流年 2024-10-14 01:10:40

你把这件事想得太复杂了。 注入 ILogger 的推荐和记录模式是使用 NullLogger .Instance 作为默认值(即 空对象模式)并创建 Logger 一个可选的依赖项。为记录器提供公共设置器没有任何问题。使用像您展示的那样的自定义 IInitialized 可能只会使事情变得复杂,而不会贡献任何实际价值。

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

using Castle.Core.Logging;

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

   public CustomerService()
   {
   }

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

   // ...
}

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

You're overcomplicating this. The recommended and documented pattern to inject ILogger is to have NullLogger.Instance as default (i.e. a null object pattern) and make the Logger an optional dependency. There is nothing wrong with having a public setter for the logger. Using a custom IInitializable like the one you show will likely only complicate things and not contribute any real value.

I'll copy the sample from the documentation here for easy reference:

using Castle.Core.Logging;

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

   public CustomerService()
   {
   }

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

   // ...
}

EDIT: it seems the question is actually about having different logger implementations depending on the context (which has little to do with the original question). If that's the case, use service overrides or handler selectors.

蓬勃野心 2024-10-14 01:10:40

In your case i would use property injection.

Property injection can be switched to mandatory as explained here:
http://www.mail-archive.com/[email protected]/msg08163.html

转瞬即逝 2024-10-14 01:10:40

如果您的基类本身不依赖于 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;
    }
}

我希望这会有所帮助。

If your base class itself is not depending on the ILogger, you should remove the property from the base class. This way you keep the base class constructor clean.

Otherwise you can create a factory that is able to create descendants of MyBaseClass. This might look like this:

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

Now you can create an register an implementation of IMyBaseClassFactory that is able to create new instances and register optional dependencies:

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

Most IoC containers allow you to decorate injectable properties with attributes. The advantage of using a factory however is, that your application will stay completely ignorant of the used IoC framework. As a downside, there's more work to do (creating a factory, injecting that factory instead of the instance itself, and calling the factory to create a new instance).

When defining a injectable property, you need to make it read/write. Of course you don't want anyone to accidentally reset that property afterwards. Without constructor injection it is hard to achieve this with compile time support. However, a runtime check is easily made:

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

I hope this helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文