由于我开始以测试/行为驱动的方式开发,我很欣赏能够模拟每个依赖项。
由于像Moq这样的模拟框架在被告知模拟接口时效果最好,我现在为几乎每个创建b / c的类实现一个接口,最有可能我最终必须在测试中模拟它。 好吧,无论如何,编程接口都是很好的做法。
有时,我的类依赖于.Net类(例如FileSystemWatcher,DispatcherTimer)。在这种情况下,拥有一个接口会很棒,所以我可以依赖于IDispatcherTimer,以便能够传递一个模拟并模拟其行为,以查看我的被测系统是否正确反应。
不幸的是,上面提到的两个类都没有实现这样的接口,所以我不得不求助于创建适配器,除了继承原始类并且符合接口之外什么也不做,然后我可以使用它。
以下是DispatcherTimer和相应接口的适配器:
using System;
using System.Windows.Threading;
public interface IDispatcherTimer
{
#region Events
event EventHandler Tick;
#endregion
#region Properties
Dispatcher Dispatcher { get; }
TimeSpan Interval { get; set; }
bool IsEnabled { get; set; }
object Tag { get; set; }
#endregion
#region Public Methods
void Start();
void Stop();
#endregion
}
/// <summary>
/// Adapts the DispatcherTimer class to implement the <see cref="IDispatcherTimer"/> interface.
/// </summary>
public class DispatcherTimerAdapter : DispatcherTimer, IDispatcherTimer
{
}
虽然这不是世界末日,但我想知道,为什么.Net开发人员没有花时间让他们的类从get开始实现这些接口。这让我很困惑,因为现在微软内部正在大力推行良好实践。
有没有人有任何(可能是内部)信息为何存在这种矛盾?
答案 0 :(得分:11)
接口可能非常有用,当然,遗憾的是.NET类库中存在一些遗漏,其中一个接口(或两个接口)可以使事情变得更清晰或更简单。
但是,您必须从另一个角度考虑它。 接口是合同。接口代表协议,合同的消费者和实施者定义他们想要如何进行交互。这当然也适用于类,但接口被视为更正式且不可变的合同形式。 您不希望接口不断变化。接口因其稳定性而非常有用。
话虽如此,创建一个简单地复制类的公共接口的接口并不一定能创造价值。特别是如果不可能有多个接口的实现者,或者接口的解耦不会产生明确的价值。实际上,您可能会认为过早创建接口可能是有害的,因为它会锁定可能很难理解的接口或者不能完全捕获抽象的接口。仅仅因为嘲笑,作为一种练习,适用于接口并不足以为每个类创建一个接口。
在你引用的例子中,不清楚界面是否会产生超出你想要更容易嘲笑的有意义的价值。请记住,添加到.NET BCL的每个其他类型都会使学习曲线更加陡峭 - 更多类型意味着需要学习和理解更多内容。
最后,为了直接解决您的问题, Microsoft必须决定在每个版本上投入.NET投入多少时间和精力。必须设计,实现,记录,测试,维护每个功能和每种类型。由于功能不是免费的,因此必须有一个令人信服的理由来实现它们以克服巨大的成本障碍。延迟.NET的发布以添加可能永远不会广泛使用(甚至可能有害)的接口并不是大多数开发人员所希望的。
答案 1 :(得分:6)
这是一个古老的讨论,TDDers认为BCL中的 Seams 太少,而微软往往对他们提供的Seams非常保守。许多BCL实施严重依赖内部和密封类,这些会损害可测试性。
我真诚地相信,可测试性从未在.NET的第一版中受到关注,但后来,微软已经意识到这个问题。但是,意识到这个问题并不等于做足够的事情。
虽然它已经变得更好,但微软反对提供更开放的BCL的最大理由是它给开发工作带来了几个负担:
我并不是说我完全同意这些论点,但那些是微软最常提供的论点。
答案 2 :(得分:2)
接口表示每个实现类都同意的合同。它们是一种管理抽象的方法,也是用于构建面向对象系统的语言的一部分工具。如果引入接口的唯一原因是能够轻松地模拟一个类,那么接口就不应该存在。模拟是一个仅对系统开发人员有意义的概念。一个类不能被嘲笑的事实并不是糟糕设计的标志。如果我们遵循相同的逻辑,那么为什么我们需要sealed关键字?为什么你需要阻止某人继承,你为什么要让一个非虚拟的方法来阻止覆盖呢?因为这是面向对象设计的一部分。而且你不是基于模拟框架的能力来创建设计。简而言之,为每个班级设置一个界面是荒谬的。
我仍然无法理解为什么你不能直接模拟一个类,你需要一个接口。除非它是密封的或静态的,否则应该有效。
P.S。如果您使用TypeMock,那么您甚至可以模拟静态和密封类。
答案 3 :(得分:1)
默认情况下不使用接口的一个主要原因是将来验证您的API;扩展接口是不可能的。您只能创建新接口。这意味着您将获得IFileWatcher1,IFileWatcher2,IFileWatcher3(只需查看COM以查看此操作)。这极大地污染了API。可以扩展类(抽象和非抽象)。
此外,如果您不希望依赖于例如一个FileSystemWatcher,无论如何最初创建这样的依赖可能不是一个好主意(无论是通过FileSystemWatcher还是IFileSystemWatcher4)。为此提供您自己的适配器,您可以自由地为其提供接口。
答案 4 :(得分:-1)
接口确实有弱点 - 虚函数表。对于每个接口方法/属性,系统在特殊的内存块中保留一个指针 - 类的虚拟表。这会使函数调用变慢并消耗内存。所以你应该只使用可以存在不同实现的接口。否则,你会影响性能。