基类VS抽象方法的空虚方法

时间:2014-03-07 17:01:56

标签: c# oop inheritance abstract-class abstraction

我找不到一个对某些案例不太具体的问题,所以我会尝试使这个非常通用。

例如,我们需要一组提取器基类来处理一组文档。每个文档都有其特定的属性,但它们最终都是文档。因此,我们希望为所有这些提供常见的提取操作。

即使他们是所有文件,正如我所说,他们有点不同。有些可能有一些属性,但有些属性可能没有。

让我们假设我们有Document基本抽象类,以及从中继承的FancyDocumentNotSoFancyDocument类。 FancyDocumentSectionANotSoFancyDocument没有。{/ p>

那就是说,你会捍卫什么作为实施这个的最佳方式?这是两个选项:

  • 基类上的空虚拟方法

基类上的空虚拟方法将允许程序员仅覆盖对不同类型的文档有意义的方法。然后,我们将在抽象基类上有一个默认行为,它将返回方法的default,如下所示:

public abstract class Document
{
    public virtual SectionA GetDocumentSectionA()
    {
        return default(SectionA);
    }
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation            
    }
}

public class NotSoFancyDocument : Document
{
    // Does not implement method GetDocumentSectionA because it doesn't have a SectionA
}
  • 抛出NotImplementedException
  • 的具体空方法或具体方法

由于NotSoFancyDocument SectionA,但其他人有NotImplementedException,我们可以直接返回该方法的默认它,或者我们可以扔//// Return the default value public abstract class Document { public abstract SectionA GetDocumentSectionA(); } public class FancyDocument : Document { public override SectionA GetDocumentSectionA() { // Specific implementation } } public class NotSoFancyDocument : Document { public override SectionA GetDocumentSectionA() { return default(SectionA); } } 。这取决于程序的编写方式和其他一些内容。我们可以想出这样的事情:

//// Throw an exception

public abstract class Document
{
    public abstract SectionA GetDocumentSectionA();
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation
    }
}

public class NotSoFancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        throw new NotImplementedException("NotSoFancyDocument does not have a section A");
    }
}

{{1}}

就个人而言,我确实认为抽象方法方法更好,因为它意味着“嘿,我需要你能够让SectionA成为一个文件。我不在乎如何。”虽然虚拟方法的意思是“嘿,我确实在这里有这个SectionA。如果它对你来说不够好,请随意改变我得到它的方式。”。

我认为第一个是面向对象编程气味的标志。

您对此有何看法?

2 个答案:

答案 0 :(得分:2)

在这种情况下,基类应该不知道SectionA。派生类应该实现该类型所需的额外属性。

对于某些操作,其中另一个类需要提取信息而不管文档类型如何,您希望基类上的该方法理想地使用基本实现虚拟,并允许派生类在需要时覆盖它(例如{{1}只输出文档的所有部分将在ToPlainText上,Document可以覆盖实现以输出FancyDocument)。

对于另一个类不关心文档类型但是关心它是否具有某些属性使用接口的情况。 SectionA会有所有常见部分,而IDocument会实现它。 Document会{i} IDocumentWithSectionA,而IDocument会实现这一点。然后,您可以导出另一个具有SectionA的FancyDocument,它也可以实现NeitherFancyNorNotFancyDocument

显然,您的名称比IDocumentsWithSectionA更有用,但这取决于用例。

TL; DR 使用抽象类来了解所有文档和一些常用功能应该是什么,使用接口作为合同说明文档的内容。

答案 1 :(得分:1)

我不会选择任何这些选项。

这两个选项都会违反Liskov Substitution Principle,其中指出如果BA的子类型,那么A的任何实例都可以替换为B的实例{1}}。

使用抛出异常的抽象方法或返回null的虚方法会破坏每个Document在调用SectionA时能够返回有效GetDocumentSectionA的期望。

你自己说过,并非所有文件都有部分。那么为什么Document会有一个方法来检索一个部分?