为什么从一个具体的类派生出来是一个糟糕的设计

时间:2013-05-23 22:29:42

标签: c++ inheritance

我正在阅读NonVirtual Interface pattern:Herb Sutter正在谈论为什么虚拟功能在大多数情况下必须是私有的,在某些情况下受到保护而从不公开。

但在文章的最后他写道:

  

不要从具体类派生。或者,正如Scott Meyers在更有效的C ++第33项中所述,[8]“使非叶类抽象化”。 (不可否认,它可能在实践中发生 - 在其他人编写的代码中,当然不是由您自己编写! - 在这种情况下,您可能必须拥有一个公共虚拟析构函数,以适应已经很糟糕的设计。更好地重构但是,如果可以的话,修复设计。)

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

5 个答案:

答案 0 :(得分:15)

您可以购买更有效的C ++ 的副本,或查看当地图书馆的副本,并阅读第33项以获取完整说明。给出的解释是它使你的类倾向于部分赋值,也称为切片:

  

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

     

第二个问题是真正的程序员编写这样的代码。通过指针对对象进行赋值并不罕见,特别是对于已经转移到C ++的有经验的C程序员。既然如此,我们希望以更合理的方式进行任务。正如第32项指出的那样,我们的类应该易于正确使用并且难以正确使用,并且上面层次结构中的类很容易错误地使用。

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

第33项也在后面说:

  

使用像liz2之类的抽象基类替换像liz1这样的具体基类,不仅可以使Lizard的行为更容易理解。它还降低了您尝试多态处理数组的机会,其中令人不快的后果在第3项中进行了检查。然而,该技术的最重要的好处发生在设计层面,因为用抽象基础替换具体的基类类迫使您明确地认识到有用的抽象的存在。也就是说,它使您可以为有用的概念创建新的抽象类,即使您没有意识到存在有用的概念这一事实。

答案 1 :(得分:4)

我认为这是因为具体的行为具体。从它衍生出来时,你承诺保留相同的“契约”,但实际的契约是由基类的具体实现来定义的,实际上会有很多细微之处,你可能会在不知情的情况下破解。

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

答案 2 :(得分:3)

一般而言,很难严格保持两个不同具体对象之间的合同。

当我们处理泛型行为时,继承变得更加容易和强大。

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

乍一看,听起来非常相似,没有冲突。让我们继续这些具体的类。

但是,现在我们要为SuperFerrari添加一项功能:turnOnBluRayDisc()

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

对于两个类都有一个共同的抽象/接口类,这个问题就会消失,因为FerrariSuperFerrari将是两个叶子,而且从现在开始Ferrari和{{ 1}}不应该被认为是相似的(不再是IS-A关系)。

简而言之,从具体类派生出来会在很多情况下导致糟糕/丑陋的代码,缺乏灵活性,难以阅读和维护。

创建由抽象类/接口驱动的层次结构允许具体的子类在没有任何问题的情况下单独进化,特别是当您实现一些专用于特定数量的具体类的子层次结构而不会厌倦其他叶子时,仍然可以从多态中受益。

答案 3 :(得分:3)

从具体类型继承的一个问题是它产生了一些歧义,即指定某种类型的代码是否真的需要特定具体类型的对象,或者想要一种类型的对象,其行为方式与具体类型表现。这种区别在C ++中至关重要,因为在许多情况下,在某种类型的对象上正常工作的操作将在派生类型的对象上严重失败。在Java和.NET中,可以使用特定类型的对象但不能使用派生类型的对象的情况更少。因此,继承具体类型并不是一个问题。但是,即使在那里,密封的具体类继承自抽象类,并且除了在构造函数调用(必须使用具体类型)之外的任何地方使用抽象类类型,这样可以更容易地更改类层次结构的各个部分而不会破坏代码。 / p>

答案 4 :(得分:0)

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