21

我正在阅读有关非虚拟接口模式的内容:Herb Sutter 正在谈论为什么虚拟功能在大多数情况下必须是私有的,在某些情况下受到保护并且从不公开。

但在文章的最后,他写道:

不要从具体类派生。或者,正如 Scott Meyers 在更有效的 C++ 的第 33 条中所说的那样,[8]“使非叶类抽象化”。(诚​​然,它可能在实践中发生——当然是由其他人编写的代码,而不是你!——在这种情况下,你可能必须有一个公共的虚拟析构函数来适应已经很糟糕的设计。最好重构并修复设计,如果可以的话。)

但我不明白为什么这是一个糟糕的设计

4

5 回答 5

20

您可以购买一份更有效的 C++的副本,或者查看您当地的图书馆以获取副本,并阅读第 33 条以获取完整说明。那里给出的解释是它使您的课程容易出现部分分配,也称为切片:

这里有两个问题。首先,在最后一行调用的赋值运算符是Animal类的,即使涉及的对象是类型Lizard。因此,只有 的Animal部分liz1会被修改。这是部分任务。分配后,liz1Animal成员具有从 获得的值liz2,但liz1Lizard成员保持不变。

第二个问题是真正的程序员会这样写代码。通过指针对对象进行赋值并不少见,尤其是对于已经转向 C++ 的有经验的 C 程序员。在这种情况下,我们想让作业以更合理的方式表现。正如条款 32 所指出的,我们的类应该容易正确使用,难于错误使用,上面层次结构中的类容易错误使用。

有关C++ 中对象切片问题的描述,请参阅此问题和其他问题。

第 33 条也这样说,稍后:

Animal用抽象基类替换具体的基类所AbstractAnimal产生的好处远远超过简单地使行为operator=更易于理解。它还减少了您尝试多态处理数组的机会,其不愉快的后果将在第 3 项中进行检查。然而,该技术的最重要的好处发生在设计级别,因为用抽象基替换具体基类类迫使您明确识别有用抽象的存在。也就是说,它使您可以为有用的概念创建新的抽象类,即使您不知道有用的概念存在的事实。

于 2013-05-23T22:42:55.693 回答
6

一般来说,很难严格维护两个不同的具体对象之间的契约。

当我们处理泛型行为时,继承变得非常容易和健壮。

想象一下,我们要创建一个名为的类Ferrari和一个名为 的子类SuperFerrari。合同是:turnTheKey(), goFast(),skid()

乍一看,听起来很相似,没有冲突。让我们继承这两个具体的类。

但是,现在我们要向SuperFerrari:中添加一个功能turnOnBluRayDisc()

问题:继承需要组件之间的 IS-A 关系。有了这个新特性,SuperFerrari就不再是简单的Ferrari有了自己的行为;它现在添加行为。这将导致一些丑陋的代码,需要一些强制转换,以便在最初处理Ferrari引用时选择新功能(多态性)。

使用两个类共有的抽象/接口类,这个问题将消失,Ferrari并且SuperFerrari将是两个叶子,并且从现在开始它更具逻辑性FerrariSuperFerrari不应被视为相似(不再是 IS-A 关系)。

简而言之,从具体类派生在许多情况下会导致代码差/丑陋,灵活性较差,并且难以阅读和维护。

制作由抽象类/接口驱动的层次结构允许具体的子类单独发展而不会出现任何问题,特别是当您实现一些专用于特定数量的具体类的子层次结构而不会厌烦其他叶子,并且仍然受益于多态性时。

于 2013-05-23T22:43:01.397 回答
4

我认为这是因为具体类具有具体行为。从它派生时,您承诺保留相同的“合同”,但实际的合同是由基类的具体实现定义的,实际上会有许多您可能会在不知不觉中破坏的微妙之处。

免责声明:我不是经验丰富的开发人员;这只是一个建议。

于 2013-05-23T22:35:34.880 回答
4

从具体类型继承的一个问题是,它会产生一些歧义,即指定特定类型的代码是否真的想要特定具体类型的对象,或者想要以具体类型的行为方式行为的类型的对象. 这种区别在 C++ 中至关重要,因为在许多情况下,对某种类型的对象正确工作的操作在派生类型的对象上会严重失败。在 Java 和 .NET 中,可以使用特定类型的对象但不能使用派生类型的对象的情况要少得多。因此,从具体类型继承几乎没有问题。然而,即使在那里,具有从抽象类继承的密封具体类,

于 2013-05-23T22:59:52.170 回答
0

当您从具体的基类派生时,您继承了功能,如果您没有源代码,则可能不会向您透露这些功能。当您对接口(或抽象类)进行编程时,您不会继承功能,这意味着它是一种更好的方式来执行诸如公开 API 之类的事情。话虽如此,我认为很多时候从具体类继承是可以接受的

于 2013-05-23T22:40:41.620 回答