多重继承的常见误用

来源:互联网 发布:手机淘宝如何消除差评 编辑:程序博客网 时间:2024/05/02 00:37

6.2  多重继承的常见误用

既然面向对象设计中多重继承的误用如此广泛,那么就有必要调查一下误用的原因,并且看一下为何这么危险。让我们假定我们有3个类:翅膀、机身、驾驶舱。我们想要构建一个飞机类。我们可以让飞机类继承翅膀、机身、驾驶舱,也可以包含它们。因为翅膀是用铝和铆钉做的,我们可以扩展我们的设计来包含它们(见图6.1)。

 图6.1  设计飞机的两种方法

哪种方法更好?很多设计者会说,使用多重继承是错误的,因为飞机不是机身的特殊类型。你听取了我的建议,我曾说过继承应当只用来为特化层次结构建模。为什么这很重要?让我们来检查一下两种设计中飞机的数据。这两种设计有什么不同?结果你会发现,对于典型的实现多继承的语言而言,并没有不同。在C++中惟一的不同是在第一种设计中系统为翅膀、机身和驾驶舱对象取名,而在第二种设计中开发者为它们取名。如果数据是一样的,那么不同之处肯定在于飞机的行为。在第二种设计中,飞机可以做什么呢?或者说,它会响应哪些消息呢?如果你的猜测是飞行和估价,那么考虑一下铆钉,铆钉知道如何给自己加热。现在飞机能做什么呢?当然,在第二种设计中,飞机不会获得加热的消息。为什么?因为包含一个类的对象并不意味着包含类的接口获得了被包含的类的接口。这是包含关系的一个很重要的特征。第二种设计是一种黑盒设计。你(飞机的使用者)并不介意我(飞机的实现者)如何创建内部设计。你并不介意我是把飞机创建为一个窄而深的继承层次结构(如上图所示)还是把飞机设计成一个宽而浅的包含了6000个数据成员(比如仪表盘盖、氧气面罩、氧气管、氧气面罩控制杆、仪表盘指针、人造革、座位安全带扣、座位安全带钩等等)的类。只要飞机能飞并且能计算自己的成本,那么飞机的用户就会很高兴,他们不介意这些工作是如何完成的。

那么,第一个设计中的飞机能响应哪些消息呢?这是使用多重继承来模拟包含的主要问题。多重继承的派生类(飞机类)的使用者必须知道它是如何实现的,这样才能使用它。这是不自然的关系,意味着只要它的零件可以执行的功能,飞机也可以执行。继承本质上就是一种白盒设计构造,而包含本质上就是一种黑盒设计构造。每当可以在白盒和黑盒之间做出设计选择,请选择黑盒构造。那些声称多重继承破坏了他们的设计并破坏了他们创建可复用组件的机会的C++开发者们在误用多重继承,把它用在了本应使用包含关系的场合。如果我觉得为了实现包含关系语言中需要有多重继承,那么我也会热切盼望C++ 2.0版本的。

经验原则6.2

只要在面向对象设计中用到了继承,问自己两个问题:(1)派生类是否是它继承的那个东西的一个特殊类型?(2)基类是不是派生类的一部分?

飞机是一种特殊类型的机身吗?不是。

机身是飞机的一部分吗?是的。

飞机是一种特殊类型的飞行器吗?是的。

飞行器是飞机的一部分吗?不是。

这两个问题可以让你在大多数时候避免麻烦。答案分别是"是的/不是"或者"不是/是的"。对第一个问题回答"是的"并对第二个问题回答"不是"意味着你需要使用继承。相反的答案则意味着包含关系是更好的选择。要小心不当的多重继承。考虑一下下面的用于检查多重继承的语句。

桔子是一种特殊类型的柑橘类水果吗?是的。

柑橘类水果是桔子的一部分吗?不是。

桔子是一种特殊类型的食品吗?是的。

食品是桔子的一部分吗?不是。

这是多重继承的正当例子吗?不是的,因为柑橘类水果是一种特殊类型的食品(假设不存在不可食用的柑橘类水果)。要小心忘记检查继承关系的传递性而试图将其建模成多重继承(见图6.2)。记得每次都要多问自己一个问题:"我的这些基类有没有哪个是其他基类的派生类?"

 图6.2  不当的多重继承

经验原则6.3

如果你在一个面向对象设计中发现了多重继承关系,确保没有哪个基类实际上是另一个基类的派生类。

违反这条经验原则的继承层次体系称为不当的多重继承(accidental multiple inheritance)。

 

转载:http://book.51cto.com/art/201111/302542.htm