构造函数注入和默认重载

时间:2008-11-18 21:58:02

标签: c# .net dependency-injection

假设我们有

public interface ITimestampProvider
{
    DateTime GetTimestamp();
}

和一个消耗它的类

public class Timestamped
{
    private ITimestampProvider _timestampProvider

    public Timestamped(ITimestampProvider timestampProvider)
    {
        // arg null check

        _timestampProvider = timestampProvider;
    }

    public DateTime Timestamp { get; private set; }

    public void Stamp()
    {
        this.Timestamp = _timestampProvider.GetTimestamp();
    }
}

和默认实现:

public sealed class SystemTimestampProvider : ITimestampProvider
{
    public DateTime GetTimestamp()
    {
        return DateTime.Now;
    }
}

引入此构造函数是有用还是有害?

public Timestamped() : this(new SystemTimestampProvider())
{}

这是一个普遍的问题,即时间戳不是有趣的部分。

6 个答案:

答案 0 :(得分:7)

我认为这取决于场景,并且基本上取决于代码所在的消费者(库与应用程序)以及您是否正在使用IoC容器。

  • 如果您正在使用IoC容器,并且这不是公共API的一部分,那么让容器执行繁重的工作,并且只使用单个构造函数。添加no-args构造函数只会让事情变得混乱,因为你永远不会使用它。

  • 如果这是公共API的一部分,那么请保留两者。如果您正在使用IoC,请确保您的IoC找到“最贪婪”的构造函数(具有最多参数的构造函数)。不使用IoC,但使用您的API的人将不会构建整个依赖图以便使用您的对象。

  • 如果您没有使用IoC容器,但只想使用模拟进行单元测试,请保留no-args构造函数,并使内部的贪婪构造函数成为可能。为您的单元测试程序集添加InternalsVisibleTo,以便它可以使用贪婪的构造函数。如果您只是单元测试,那么您不需要额外的公共API表面。

答案 1 :(得分:4)

我不会提供那个构造函数。这样做可以很容易地调用新的TimeStamped,并在您的IoC配置为使用OtherTimestampProvider()时获取具有新SystemTimestampProvider()的实例。

一天结束时,你最终会花一点时间试图调试你为什么得到错误的时间戳。

如果您只提供第一个构造函数,则可以使用SystemTimestampProvider进行简单的查找,以找出(错误地)使用该提供程序而不是IoC配置的提供程序。

答案 2 :(得分:3)

总的来说,我不这么认为......这取决于你使用的依赖注入。当我使用DI进行单元测试时,我通过在注入的实例为null时实例化依赖对象的生产版本来做同样的事情(或多或少)...然后我有一个不带参数和委托的重载对于那个...我使用无参数的生产代码,并为单元测试方法注入一个测试版...

如果您正在谈论IOC容器应用程序,otoh,您需要注意干扰配置设置告诉容器以不明确的方式执行的操作......

   public class EventsLogic
   { 
       private readonly IEventDAL ievtDal;
       public IEventDAL IEventDAL { get { return ievtDal; } }

       public EventsLogic(): this(null) {}
       public EventsLogic(IIEEWSDAL wsDal, IEventDAL evtDal)
       {
          ievtDal = evtDal ?? new EventDAL();
       }
    }

答案 3 :(得分:0)

我尽量避免这种情况 - 有几个地方我发现它是一个有用的设计,但更多时候我发现它只会导致我犯错误,这可能有点令人费解

通过使用依赖注入容器(我使用StructureMap)来管理所有这些连接,大大减少了对默认注入对象的需求 - DI容器确保您始终获得可以使用的具体实例。

我仍然想要使用你建议的构造函数的唯一地方是在我的单元测试中,但最近我使用假冒或模拟对象获得了更大的价值。

有些地方的默认依赖对象是正确而有用的设计,但总的来说,我会说你只是引入了不会增加很多价值的紧耦合。

答案 4 :(得分:0)

既没有帮助也没有害处。它带来了一个美学问题,因为只有当你的设计允许注册属性时,你才能将DI​​限制为构造函数注入。

另一种选择是实现一个返回默认实现的getter:

public DateTime Timestamp
{
    get { return _timestampProvider??new SystemTimestampProvider(); }
    set { _timestampProvider = value; }
}

或者,如果您担心在堆中创建太多对象,则可以使用单例实现上述内容。

答案 5 :(得分:0)

我的团队使用此方法取得了很大成功。我建议一个更改:
使_timestampProvider只读。这迫使提供者在构建时具有确定性并将消除错误。

public class Timestamped
{
    private readonly ITimestampProvider _timestampProvider;

    public Timestamped(ITimestampProvider timestampProvider)
    {
        _timestampProvider = timestampProvider;
    }

    public Timestamped(): this(new SystemTimestampProvider())
    { }
}

也就是说,我们始终正在研究新技术,包括DI框架。如果我们放弃这种技术以获得更好的效果,我会告诉你。