为一个现在代表的DateTime制作一个包装器是一个好主意吗?

我最近注意到,使用表示’now’的DateTime作为方法的输入参数,用于模拟和测试目的非常好。 而不是每个调用DateTime.UtcNow方法本身,我在上层方法中执行一次,然后在下层方法中执行。

所以很多需要’now’的方法现在都有一个输入参数DateTime now

(我正在使用MVC,并尝试检测一个名为now的参数和modelbind DateTime.UtcNow到它)

所以代替:

 public bool IsStarted { get { return StartTime >= DateTime.UtcNow; } } 

我通常有:

 public bool IsStarted(DateTime now) { return StartTime >= now; } 

所以我的约定是,如果一个方法有一个名为nowDateTime参数,你必须用当前时间来提供它。 当然这归结为惯例,而其他人可以很容易地在其中抛出一些其他DateTime作为参数。

为了使它更加可靠和静态类型,我正在考虑将DateTime包装在一个新对象中,即DateTimeNow。 因此,在最上面的一个层中,我将DateTime转换为DateTime ,当有人试图在正常的DateTime中进行操作时,我们将得到编译错误。

当然你仍然可以解决这个问题,但至少如果你觉得你做错了什么的话。 有没有人进过这条道路? 从长远来看,我没有考虑过任何好的或坏的结果吗?

您可以创建具有必要属性的接口。 即IClock并将其作为dependency injection。

 interface IClock { DateTime Now { get; } DateTime UtcNow { get; } } class SystemClock : IClock { public DateTime Now { get { return DateTime.Now; } } public DateTime UtcNow { get { return DateTime.UtcNow ; } } } class TestDoubleClock : IClock { public DateTime Now { get { return whateverTime; } } public DateTime UtcNow { get { return whateverTime ; } } } 

这样,您可以轻松地对依赖于DateTime代码进行unit testing。 传递DateTime.Now随处可见参数听起来很糟糕。 如果您需要Now以及UtcNow和其他内容怎么办? 你会为此单独添加三个参数吗?

我建议使用这种接口技术来避免使用过多参数的丑陋代码,这些参数对您没有多大帮助。

我建议创建一个提供Now值的接口:

 public interface IDateTimeProvider { DateTime Now { get; } } 

然后,如果要在MVC应用程序中使用当前日期,只需实现如下所示的类:

 public class CurrentDateTimeProvider : IDateTimeProvider { public DateTime Now { get { return DateTime.Now; } } } 

然后,您可以将其注入控制器,在unit testing中进行模拟,甚至替换为其他实现(例如,如果您决定在代码中使用UtcNow而不是Now

是的,如您所述,将自定义类型的不同计时器实现包装起来是个好主意。

我看到传递时间对象的三个缺点:

  • 准确性:

请记住,给定函数的时间精度将取决于自第一次调用DateTime.Now以来执行代码所需的时间。现在:

 var date = DateTime.Now func_1(date) // took 1 second to execute func_2(date) // took 1 second to execute funf_3(date) // date is now late by 3 seconds. 
  • 性能:

您为每个函数添加一个参数,并在获取时间(通过包装器)时添加间接。 在我的情况下,这种开销在某些情况下是显而易见的。

  • 安全:

除非您只允许一种具体类型封装时间,否则您将无法阻止客户端代码编写自己的时间包装器,仍然可以为您的函数提供他们想要的任何时间。

我通常通过让类接受一个nowProvider作为nowProvider一部分来解决这个问题,如下所示:

 public class ClassToTest { private readonly Func _nowProvider; public ClassToTest(Func nowProvider) { _nowProvider = nowProvider; } public ClassToTest() { _nowProvider = () => DateTime.Now; } //snip } 

所以在我的测试中我可以这样做:

 var knownDate = new DateTime(2000, 1, 1); var testObject = new ClassToTest(() => knownDate);