为表示Now的DateTime专门制作一个包装器是个好主意吗?

本文关键字:包装 一个 好主意 Now 表示 DateTime | 更新日期: 2023-09-27 18:06:18

我最近注意到,为了模拟和测试的目的,使用代表'now'的DateTime作为方法的输入参数真的很好。不是每个方法都调用DateTime.UtcNow本身,而是在上层方法中调用一次,然后在下层方法中转发。

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

(我使用MVC,并尝试检测一个名为现在和modelbind DateTime的参数。

所以不是:

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

我通常有:

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

所以我的惯例是,如果一个方法有一个叫做nowDateTime参数,你必须给它提供当前时间。当然,这归结为惯例,其他人可以很容易地将其他DateTime作为参数扔进去。

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

当然,你仍然可以解决这个问题,但至少它感觉更像是你做错了什么。还有其他人走过这条路吗?从长远来看,有没有什么好的或坏的结果是我没有想到的?

为表示Now的DateTime专门制作一个包装器是个好主意吗?

您可以创建具有必要属性的接口。即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作为参数听起来很糟糕。如果你需要Now, UtcNow和其他东西呢?您是否会为此添加三个参数?

我建议使用这种接口技术来避免带有太多参数的丑陋代码。

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

public interface IDateTimeProvider
{
   DateTime Now { get; }
}

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

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

然后你可以把它注入到你的控制器中,在单元测试中模拟它,甚至用其他实现来替换它(例如,如果你决定在你的代码中使用UtcNow而不是Now

是的,它可以是一个好主意包装不同的定时器实现自定义类型,正如你所说的。

我认为传递time对象有三个缺点:

    精度
  • :

请记住,给定函数的时间精度取决于从第一次调用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作为构造函数的一部分来解决这个问题,像这样:

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);