【问题标题】:Is it an good idea to make a wrapper specifically for a DateTime that respresents Now?专门为代表 Now 的 DateTime 制作一个包装器是个好主意吗?
【发布时间】:2014-07-21 18:54:35
【问题描述】:

我最近注意到,使用代表“现在”的 DateTime 作为方法的输入参数非常好,用于模拟和测试目的。我不是每个方法都自己调用DateTime.UtcNow,而是在上层方法中执行一次,然后在下层方法中转发。

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

(我正在使用 MVC,并尝试检测一个名为 now 的参数并将模型绑定 DateTime.UtcNow 到它)

所以而不是:

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

我通常有:

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

所以我现在的约定是,如果一个方法有一个名为nowDateTime 参数,则必须为其提供当前时间。当然,这归结为惯例,其他人可以轻松地将其他一些 DateTime 作为参数放入其中。

为了使其更加稳固和静态类型,我正在考虑将 DateTime 包装在一个新对象中,即 DateTimeNow。因此,在最上层之一中,我会将DateTime 转换为DateTimeNow,当有人试图摆弄正常的日期时间时,我们会遇到编译错误。

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

【问题讨论】:

  • 您正在从属性更改为方法。你为什么不直接使用return StartTime > DateTime.UtcNow?这是很多不必要的复杂性。
  • 因为它使一些东西很难测试和耦合。在某种程度上,DateTime.UtcNow 只是另一种静态方法,其中包含不断变化的“状态”。
  • @JeroenVannevel 如果他这样做,他将无法测试它。

标签: c# dependencies


【解决方案1】:

是的,正如您所提到的,将不同的计时器实现包装在自定义类型中可能是个好主意。

我发现传递时间对象的三个缺点:

  • 准确度:

请记住,给定函数的时间精度将取决于自第一次调用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.
  • 性能:

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

  • 安全:

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

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

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

      public interface IDateTimeProvider
      {
         DateTime Now { get; }
      }
      

      如果你想在你的 MVC 应用程序中使用当前日期,只需实现这样的类:

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

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

      【讨论】:

        【解决方案4】:

        您可以创建具有必要属性的接口。即IClock 并将其作为依赖注入。

        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 的代码进行单元测试。到处传递DateTime.Now 作为参数听起来很糟糕。如果您需要NowUtcNow 和其他东西怎么办?您会为此目的单独添加三个参数吗?

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

        【讨论】:

          猜你喜欢
          • 2011-05-14
          • 1970-01-01
          • 1970-01-01
          • 2011-02-03
          • 2017-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多