是否应该将抽象类中的所有公共方法标记为虚拟?


12

最近,我不得不在正在使用的某些OSS上更新抽象基类,以便通过将它们虚拟化来使其更具可测试性(我不能使用将两个接口组合在一起的接口)。这让我开始思考是否应该标记我需要虚拟的所有方法,还是应该标记每个公共方法/属性都是虚拟的。我通常同意Roy Osherove的观点,即每种方法都应该虚拟化,但是我遇到了这篇文章,使我开始思考是否需要这样做。为了简单起见,我将限于抽象类,但是(我确定所有具体的公共方法都应该是虚拟的都是值得商de的)。

我可以看到您可能希望在哪里允许子类使用方法,而不希望它覆盖实现。但是,只要您相信将遵循Liskov的替代原则,那为什么不容许它被取代呢?通过将其标记为抽象,无论如何都在强制某种重写,因此,在我看来,抽象类内部的所有公共方法都应确实标记为虚拟。

但是,我想问一下是否有我可能没有想到的事情。应该将抽象类中的所有公共方法设为虚拟吗?



1
也许您应该研究一下为什么在C#中默认不是虚拟的,而在Java中却是默认的。之所以可能会给您一些答案的原因。
丹尼尔·

Answers:


9

但是,只要您相信将遵循Liskov的替代原则,那为什么不容许它被取代呢?

例如,因为我希望算法的基本实现是固定的,并且仅允许特定部分由子类(重新)定义。这被广泛称为模板方法模式(我在下面强调):

因此,模板方法管理着更大的任务语义图,并管理了方法选择和方法序列的更完善的实现细节。这张大图调用了手头任务的抽象和非抽象方法。非抽象方法完全由模板方法控制,但是抽象方法(在子类中实现)提供了模式的表达能力和自由度。某些或所有抽象方法可以专用于子类,从而允许子类的编写者以对较大语义的最小修改来提供特定的行为。模板方法(非抽象方法)在此模式下保持不变,从而确保从属的非抽象方法和抽象方法按最初打算的顺序调用。

更新资料

我一直在从事的项目的一些具体示例:

  1. 通过各种“屏幕”与旧式大型机系统通信。每个屏幕都有一堆固定名称,位置和长度的字段,其中包含特定的数据位。请求用特定数据填充某些字段。响应返回一个或多个其他字段中的数据。每个事务遵循相同的基本逻辑,但是每个屏幕上的细节都不相同。我们在几个不同的项目中使用了模板方法,以实现屏幕处理逻辑的固定框架,同时允许子类定义屏幕特定的细节。
  2. 将数据库表中的配置数据导出到Excel文件/从Excel文件导出。同样,每个表的处理Excel文件和插入/更新DB记录或将记录转储到Excel的基本架构是相同的,但是每个表的详细信息是不同的。因此,模板方法是消除代码重复并使代码更易于理解和维护的非常明显的选择。
  3. 生成PDF文档。每个文档具有相同的结构,但是它们的内容每次都不同,这取决于许多因素。同样,使用模板方法可以轻松地将生成算法的固定框架与特定于案例的可变细节分开。事实上。它在这里甚至适用于多个级别,因为该文档由几个部分组成,每个部分都包含零个或多个字段。在这里,模板方法适用于3个不同的级别。

在前两种情况下,原始的旧版实现使用Strategy,从而导致大量重复的代码,当然,这些年来,这些代码之间到处都是细微的差异,包含许多重复的或稍有不同的错误,并且很难维护。重构为Template Method(以及其他一些增强功能,例如使用Java批注)可将代码大小减少约40-70%。

这些只是我想到的最新示例。到目前为止,几乎所有我从事的项目都可以引用更多案例。


简单地引用GoF并不是答案。您需要给出执行此操作的实际原因。
DeadMG

@DeadMG,我在职业生涯中一直定期使用Template Method,因此我认为这显然是一种非常实用和有用的模式(因为大多数GoF模式都是...这些不是理论上的学术练习,而是收集的来自现实世界的经验)。但显然并非所有人都对此表示赞同...所以我在回答中添加了一些具体示例。
彼得Török

2

这是完全合理的,有时希望在抽象基类中使用非虚拟方法。仅仅因为它是一个抽象类,并不一定意味着它的每个部分都应该是多态的。

例如,您可能想使用“非虚拟多态性”习惯用法,即从非虚拟成员函数中多态调用一个函数,以确保在调用虚拟函数之前满足某些先决条件或后置条件

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};

我认为,只要总体行为保持不变,就可以将其虚拟化。您可以使用不同的实现方式(数据库与内存中)填充前提条件。否则,应该是私有功能?
贾斯汀·皮洪尼

2

一个类包含一个虚拟方法就足够了,以便该类成为抽象的。根据您打算使用的多态类型,您可能需要注意哪些方法是虚拟的,哪些方法不是。


1

问问自己,抽象类中非虚拟方法的用途是什么。这种方法必须具有实现才能使其有用。但是,如果该类具有实现,那么它仍可以称为抽象类吗?即使语言/编译器允许,这是否有意义?就我个人而言,我不这么认为。您将拥有一个普通类,该类带有预期后代可以实现的抽象方法。

我的主要语言是Delphi,而不是C#。在Delphi中,如果标记方法抽象,则还必须将其标记为虚拟,否则编译器会抱怨。尚未紧跟最新的语言更改,但是如果Delphi中包含或将要使用抽象类,我希望编译器会抱怨标记为abstract的类的任何非虚拟方法,任何私有方法以及任何方法实现在课堂上。


2
我认为拥有一个抽象类具有一些抽象方法,一些虚拟方法和一些非虚拟方法是很有意义的。我认为这始终取决于您要做什么。
svick

3
首先,我将介绍您的C#q。C#中的抽象方法是隐式虚拟的,无法实现。关于您的第一点,C#中的抽象类可以并且应该具有某种实现(否则,您应该仅使用接口)。类的抽象点是必须将其子类化,但是它包含(理论上)所有子类都将使用的逻辑。这减少了代码重复。我要问的是,是否应该关闭所有这些实现中的任何一个(基本上说基本方法是唯一方法)。
贾斯汀·皮洪尼

@JustinPihony:谢谢。在Delphi中,当您标记一个方法抽象时,如果您为其提供实现,则编译器会抱怨。有趣的是,不同的语言如何以不同的方式实现概念,从而在用户中产生不同的期望。
Marjan Venema

@svick:是的,它的确很合理,我只是不将其称为抽象类,而是具有抽象方法的类...但是我想这可能只是我的解释。
Marjan Venema 2012年

@MarjanVenema,但这不是C#使用的术语。如果在类中标记了任何方法abstract,则还必须标记整个类abstract
svick

0

但是,只要您相信将遵循Liskov的替代原则,那么>为什么您不容许它被覆盖?

您不会将某些方法虚拟化,因为您不相信这种情况。此外,通过使某些方法不是虚拟的,您正在向继承者发信号通知应实施哪种方法。

就个人而言,我经常会把为方便起见而存在的方法重载而不是虚拟的,以使该类的用户可以具有一致的默认值,并且实现者甚至无法犯破坏该隐含行为的错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.