2

每当我想在一个平凡的类中存根一个方法时,我最常提取一个接口。现在,如果该类的构造函数是公共的并且不是太复杂或不依赖于复杂类型,那么将相关方法设置为虚拟并继承将具有相同的效果。这比提取接口更可取吗?如果是这样,为什么?

编辑:

class Parser
{
    public IDictionary<string, int> DoLengthyParseTask(Stream s)
    {
        // is slow even with using memory stream
    }
}

有两种方法:提取接口或使方法虚拟化。我实际上更喜欢接口,但这可能会导致IParser Parser元组爆炸......

4

3 回答 3

5

您需要考虑在单元测试之外要完成的工作。不要让你的工具支配你的设计。

处理接口有助于解耦代码,但这些应该是代码中的自然分离点(例如业务逻辑或数据访问)。如果您要继承和覆盖这些方法,则将方法设为虚拟是有意义的。

你的情况下,我会尝试直接测试使用的行为DoLengthyParseTask而不是方法。这也将提供更强大的测试套件。您需要仔细考虑此方法是否真的需要公开(这意味着它可以并且应该在其自己的程序集之外引用)。

于 2013-01-10T22:11:36.343 回答
4

接口只是为你订立一个契约,基本上是一个承诺,即你的实现将提供对一组指定的接触点(方法、属性等)的访问,而没有行为规范。只要你信守承诺,你就可以为所欲为。

另一方面,除了契约之外,基类至少指定了类中编码的一些行为(除非一切都是抽象的,但那是另一回事了)。使方法虚拟化仍然使您能够调用基的实现,并且仍然提供您自己的代码。

这种行为的继承基本上是现代 OOP 中禁止多继承的原因,多接口实现比较普遍。

也就是说,您需要权衡您是否只是想提取合同,或者您也想提取一些行为,并且对于特定情况,答案应该是显而易见的。

至于IParser/Parser对,首先它们非常适合单元测试和依赖注入,其次,它们不会向您收取创建类的费用,因此您可以随意创建任意数量。

于 2013-01-10T22:20:08.653 回答
3

通过对接口进行编程,您可以获得单元测试中易于模拟/存根和松散耦合代码的好处(因此,更高的灵活性),实际上是免费的(唯一的缺点是要管理更多的工件)。

接口和继承是两个独立的东西,即使可以互换使用它们也不是一个好主意。通过标记方法virtual,您实际上是在告诉其他人,他们不仅可以在他们的实现中自由更改(覆盖)此方法,而且您实际上希望他们这样做(并且您是吗?)。

这样的设计带来了相当严重的后果,所以除非你明确需要它 - 你不应该使用它。尝试坚持编程接口。

良好的面向对象设计原则之一表明您应该对接口进行编程(按合同设计Liskov 替换原则)并且更喜欢组合而不是继承(不仅您的类应该实现接口/抽象类,而且还应该包含这样的实现)。

值得注意的是,您的Parser示例非常适合隐藏在抽象后面(无论是接口还是基类)。从消费者的角度来看,如何创建数据并不重要——现在您可能认为它只是 XML 流,但需求往往会发生变化(和/或增长),您可能很快就会发现自己正在实现二进制文件解析器,数据流挖掘解析器和其他什么。现在就做好,以后省时省事。

于 2013-01-10T22:19:20.537 回答