为表示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;
}
所以我的惯例是,如果一个方法有一个叫做now
的DateTime
参数,你必须给它提供当前时间。当然,这归结为惯例,其他人可以很容易地将其他DateTime作为参数扔进去。
为了使它更加坚实和静态类型,我正在考虑将DateTime包装在一个新对象中,即DateTimeNow。因此,在最上层的一层中,我将把DateTime
转换为DateTimeNow
,当有人试图在正常的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);