【问题标题】:Empty virtual method on base class VS abstract methods [closed]基类VS抽象方法上的空虚方法[关闭]
【发布时间】:2014-04-10 23:00:09
【问题描述】:

我找不到对某些情况不太具体的问题,所以我会尽量让这个问题非常笼统。

例如,我们需要一组文档的提取器基类。每个文档都有其特定的属性,但它们最终都是文档。所以我们想为所有这些提供通用的提取操作。

尽管它们都是文档,正如我所说,它们还是有些不同。有些可能有一些属性,但有些可能没有。

假设我们有Document 基抽象类,以及从它继承的FancyDocumentNotSoFancyDocument 类。 FancyDocument 有一个 SectionANotSoFancyDocument 没有。

也就是说,您认为最好的实施方式是什么?这是两个选项:

  • 基类上的空虚方法

基类上的空虚方法将允许程序员仅覆盖对不同类型文档有意义的方法。然后我们将在抽象基类上有一个默认行为,它将为方法返回 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");
    }
}

就个人而言,我确实认为抽象方法方法更好,因为它的意思是“嘿,我需要你能够将 SectionA 作为文档。我不在乎如何。”而虚拟方法的意思是“嘿,我这里确实有这个 SectionA。如果它对你来说不够好,请随意改变我获取它的方式。”。

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

您对此有何看法?

【问题讨论】:

  • 我相信这个问题更适合programmers.stackexchange.com
  • @AaronPalmer 谢谢 Aaron,我看到它关门了。那我把它贴在程序员上。谢谢!

标签: c# oop inheritance abstract-class abstraction


【解决方案1】:

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

对于某些操作,无论文档类型如何,另一个类都需要提取信息,您将希望基类上的该方法理想地具有基本实现,并允许派生类在需要时覆盖它(例如ToPlainText将只输出文档的所有部分将在DocumentFancyDocument 可以覆盖实现也输出SectionA)。

对于另一个类不关心文档类型但关心它是否具有某些属性的情况,请使用接口。 IDocument 将拥有所有公共部分,Document 将实现它。 IDocumentWithSectionA 将继承 IDocumentFancyDocument 将实现它。然后,您可以派生另一个 NeitherFancyNorNotFancyDocument,该 NeitherFancyNorNotFancyDocument 具有 SectionA,它也可以实现 IDocumentsWithSectionA

显然,您会拥有比 IDocumentWithSectionA 更多有用的名称,但这取决于用例。

TL;DR 将抽象类用于所有文档应该共有的内容和一些通用功能,使用接口作为说明文档具有什么内容的合同。

【讨论】:

    【解决方案2】:

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

    这两个选项都违反了Liskov Substitution Principle,它规定如果BA 的子类型,那么A 的任何实例都可以替换为B 的实例。

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

    您自己说过,并非所有文档都有部分。那么为什么Document 会有一个检索部分的方法呢?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      • 2015-01-27
      • 2020-03-12
      • 1970-01-01
      • 1970-01-01
      • 2019-08-31
      相关资源
      最近更新 更多