构造函数注入和默认重载

假设我们有

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

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

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

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

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

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

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

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

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

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

如果您正在讨论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(); } } 

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

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

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

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

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

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

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

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

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

 public class Timestamped { private readonly ITimestampProvider _timestampProvider; public Timestamped(ITimestampProvider timestampProvider) { _timestampProvider = timestampProvider; } public Timestamped(): this(new SystemTimestampProvider()) { } } 

也就是说,我们一直在关注新技术,包括DI框架。 如果我们放弃这项技术以获得更好的东西,我会告诉你。