【问题标题】:Default values for constructor arguments in a library project库项目中构造函数参数的默认值
【发布时间】:2012-04-12 12:21:55
【问题描述】:

我正在编写一个库,它将为其消费者提供公共类型的集合。

我想让这个库依赖注入中的类型变得友好。这意味着每个类都需要有一个构造函数,通过它可以指定被初始化对象的每个依赖项。我还希望库遵守配置原则的约定。这意味着如果消费者想要默认行为,他可以使用无参数构造函数,并且对象会以某种方式为自己构建依赖关系。

在示例 (C#) 中:

public class Samurai {

    private readonly IWeapon _weapon;

    // consumers will use this constructor most of the time
    public Samurai() {
        _weapon = ??? // get an instance of the default weapon somehow
    }

    // consumers will use this constructor if they want to explicitly
    //   configure dependencies for this instance
    public Samurai(IWeapon weapon) {
        _weapon = weapon;
    }
}

我的第一个解决方案是使用服务定位器模式。

代码如下所示:

...
public Samurai() {
    _weapon = ServiceLocator.Instance.Get<IWeapon>();
}
...

不过,我对此有疑问。服务定位器已被标记为反模式 (link),我完全同意这些论点。另一方面,Martin Fowler 提倡在这种情况下(图书馆项目)使用服务定位器模式(link)。我想小心并消除在发现服务定位器确实是个坏主意后重写库的可能必要性。

因此,总而言之 - 您认为服务定位器在这种情况下是否合适?我应该以完全不同的方式解决我的问题吗?欢迎任何想法......

【问题讨论】:

    标签: dependency-injection inversion-of-control default-value service-locator


    【解决方案1】:

    还有一种替代方法,即注入一个特定的提供者,假设你的情况是 WeaponProvider 到你的类中,这样它就可以为你进行查找:

    public interface IWeaponProvider
    {
        IWeapon GetWeapon();
    }
    
    public class Samurai
    {
        private readonly IWeapon _weapon;
    
        public Samurai(IWeaponProvider provider)
        {
            _weapon = provider.GetWeapon();
        }
    }
    

    现在您可以为武器提供本地默认提供程序:

    public class DefaultWeaponProvider : IWeaponProvider
    {
        public IWeapon GetWeapon()
        {
            return new Sword();
        }
    }
    

    由于这是本地默认值(与来自不同程序集的默认值相反,因此它不是“混蛋注入”),您也可以将其用作 Samurai 类的一部分:

    public class Samurai
    {
        private readonly IWeapon _weapon;
    
        public Samurai() : this(new DefaultWeaponProvider())
        {
        }
    
        public Samurai(IWeaponProvider provider)
        {
            _weapon = provider.GetWeapon();
        }
    }
    

    【讨论】:

    • 这看起来是一个非常优雅的选择。我还想再问一个问题。
    • 现在我必须在 Samurai 类的构造函数中硬编码 IWeaponProvider 到 DefaultWeaponProvider 的绑定。您是否看到了如何将此绑定重构为某个组合模块的方法?与 IOC 容器一样,您有一个配置文件,您可以在其中定义类似“Bind().To();”的内容(-来自 Ninject 的示例)。即使没有办法,我也会接受你的回答。只是想澄清一下...谢谢
    • 澄清一下:“约定优于配置”是指您仍然想要配置这些默认值?但是,是的,您可以实现您的DefaultWeaponProvider 以将其IWeapon 绑定到任何真正的东西,即基于配置文件或IoC 容器的内部使用 - 但您的Samurai 类将没有任何这些依赖项跨度>
    • 我希望能够从一个地方配置默认值,因为我觉得它是应用程序的一个连贯部分。但我想在图书馆里做。库的使用者一定不知道这样一个事实,即正在进行一些默认值管理(接口到实现绑定的映射)。至少不能要求他自己明确定义默认值。我认为@wim-coenen 的解决方案很好,因为它使我能够从一个地方管理默认值,并且用户不需要进行任何类型的配置 - 如果他不想这样做。
    • 我觉得我应该道歉,因为我开始觉得这个问题已经含糊不清,可能没有一个正确的答案。它甚至可以成为一个讨论,这在 SO 上是不行的,我知道这一点。在这个时候,我认为@wim-coenen 的答案更适合我正在寻找的内容。
    【解决方案2】:

    如果您想让不使用 DI 容器的用户的生活更轻松,您可以通过专用的 Defaults 类提供默认实例,该类具有如下方法:

    public virtual Samurai CreateDefaultSamurai()
    {
       return new Samurai(CreateDefaultWeapon());
    }
    
    public virtual IWeapon CreateDefaultWeapon()
    {
       return new Shuriken();
    }
    

    这样您就不需要使用默认构造函数污染类本身,并且您的用户不会有无意中使用这些默认构造函数的风险。

    【讨论】:

    • 保持类 DI 友好很重要。 +1
    【解决方案3】:

    我在我的 C# 项目中使用了以下方法。目标是实现依赖注入(用于单元/模拟测试),同时不削弱“正常用例”的代码实现(即通过执行流程级联的大量 new())。

    public sealed class QueueProcessor : IQueueProcessor
    {
        private IVbfInventory vbfInventory;
        private IVbfRetryList vbfRetryList;
    
        public QueueProcessor(IVbfInventory vbfInventory = null, IVbfRetryList vbfRetryList = null)
        {
            this.vbfInventory = vbfInventory ?? new VbfInventory();
            this.vbfRetryList = vbfRetryList ?? new VbfRetryList();
        }
    }
    

    这允许 DI,但也意味着任何消费者都不必担心“默认实例流”应该是什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-10
      • 1970-01-01
      • 2012-06-30
      • 2014-07-09
      • 1970-01-01
      • 2017-04-01
      • 2012-02-26
      • 2011-11-05
      相关资源
      最近更新 更多