6

假设我们有

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())
{}

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

4

6 回答 6

7

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

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

  • 如果这是公共 API 的一部分,则保留两者。如果您使用 IoC,只需确保您的 IoC 找到“最贪婪”的构造函数(具有最多参数的构造函数)。不使用 IoC,但使用您的 API 的人会喜欢不必为了使用您的对象而构建整个依赖关系图。

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

于 2008-11-18T22:35:18.120 回答
4

我不会提供那个构造函数。当您的 IoC 可能配置为使用 OtherTimestampProvider() 时,这样做会使调用 new TimeStamped 并使用 new SystemTimestampProvider() 获取实例变得非常容易。

一天结束时,您将花费大量时间尝试调试为什么您得到错误的时间戳。

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

于 2008-11-18T22:03:42.287 回答
3

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

如果您在谈论 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();
       }
    }
于 2008-11-18T22:04:09.757 回答
0

我尽量避免这种情况——在一些地方我发现它是一个有用的设计,但我发现它常常会导致我犯错误,这可能会让我有些困惑。

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

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

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

于 2008-11-18T22:08:15.240 回答
0

它既无用也无害。它提出了一个美学问题,因为只有当您的设计允许属性设置器注入时,您才将 DI 限制为构造函数注入。

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

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

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

于 2008-11-18T22:08:40.483 回答
0

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

public class Timestamped
{
    private readonly ITimestampProvider _timestampProvider;

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

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

也就是说,我们一直在研究新技术,包括 DI 框架。如果我们为了更好的东西而放弃这种技术,我会告诉你的。

于 2008-11-18T23:01:56.147 回答