每当我想在一个简单的类中存根方法时,我经常提取一个接口。 现在,如果该类的构造函数是公共的并且不是太复杂或依赖于复杂类型,那么它将具有相同的效果,只需使该方法成为虚拟和继承。 这比提取界面更可取吗?如果是这样,为什么?
编辑:
class Parser
{
public IDictionary<string, int> DoLengthyParseTask(Stream s)
{
// is slow even with using memory stream
}
}
有两种方法:提取界面或使方法成为虚拟。我实际上更喜欢接口,但这可能导致IParser
Parser
元组爆炸......
答案 0 :(得分:5)
您需要考虑在单元测试之外要完成的任务。 不要让您的工具决定您的设计。
在接口中处理可以帮助解耦代码,但这些应该是代码中自然的分离点(例如业务逻辑或数据访问)。如果你要继承并覆盖这些方法,那么使方法成为虚拟是有意义的。
在你的案例中,我会尝试测试使用DoLengthyParseTask
的行为,而不是直接测试方法。这将提供更强大的测试套件。您需要仔细考虑此方法是否真的需要public(意味着它可以 在其自己的程序集之外引用)。
答案 1 :(得分:4)
接口只是为你签订合同,基本上是一个承诺,你的实现将提供对一组指定联系点(方法,属性等)的访问,没有行为规范。只要你履行承诺,你就可以自由地做任何你想做的事。
另一方面,除了契约之外,基类还指定了至少一些在类中编码的行为(除非一切都是抽象的,但这是另一个故事)。使方法虚拟仍然允许您调用基础的实现,并仍然提供您自己的代码。这种行为继承基本上是多继承在现代OOP中是禁忌的原因,并且多接口实现相对常见。
也就是说,你需要权衡是否只想提取合同,或者你想要提取某些行为,对于特定情况,答案应该是显而易见的。
对于IParser
/ Parser
对,首先它们非常适合单元测试和依赖注入,其次,它们不会向您收取类创建费用,因此可以随意创建如你所愿。
答案 2 :(得分:3)
通过对接口进行编程,您可以轻松地在单元测试中轻松进行模拟/存根以及松散耦合的代码(因此,更高的灵活性),字面上免费(唯一的缺点是更多工件来管理)。
接口和继承是两个独立的事情,尽管它是可能的,但是交替使用它们并不是一个好主意。通过标记方法virtual
,你实际上告诉别人他们不仅可以自由地改变(覆盖)他们的实现中的方法,而是你实际上期望他们强>(你呢?)。
这样的设计会产生相当严重的后果,所以除非你明确需要它,否则你不应该使用它。尝试坚持编程接口。
良好的面向对象设计原则之一表明您应该编程到接口(按合同设计,Liskov Substitution Principle)并且更喜欢composition over inheritance(不仅您的类应该实现接口/抽象类,但也包括这样的实现)。
值得注意的是,你的Parser
示例是完美的候选者,隐藏在抽象背后(无论是接口还是基类)。从消费者的角度来看,数据的创建方式并不重要 - 现在您可能认为它只是XML流,但需求往往会改变(和/或增长),您很快就会发现自己实现了二进制文件解析器,数据流挖掘解析器和什么不是其他。现在就做好,以后节省时间和麻烦。