第五章 继承性和派生类
在第四章,讨论了类的构造设计,主要是解决信息封装单元的程序设计问题。这一章则要进一步讨论类之间的相互关系,集中解决继承机制的程序表达问题。
本章目的:
.了解继承隆在面向对象中的重要地位
.了解类的层次关系对于继承性的需要
.了解软件设计模块化对于继承性的需要
.掌握派生类的构造
.掌握成员访问机制
.掌握多继承和虚基类
5.1 类层次、数据抽象和模块化
客观世界中的对象既具有共性,也具有个性。人类认识复杂世界的一件强有力的工具就是抽象。通过抽象,获得了主要的、起控制作用的特征,而摒弃了次要的无足轻重的特性,使得认识可以有效而完整地进行下去。通过抽象,人们发现某些事物的相似性远大于他们之间的差异,因此这些事物可以归为一类。分类(classification)大降低了认识对象的复杂程序。
抽象的程度不同,分类的层次就会不同。抽象的程序是根据要研究的问题的范围和复杂程度来确定的。自然界的生物分类结构如图5.1所示。(P138)
不同层次的类之间体现了概括(generalize)和特化(specify)的关系。概括是从某些具有共性的对象或类中提取抽象出高一层的类。反之由高层次类可以衍生出低层次的对象或子类。它们之间是“is-a“的关系。即低层次类的每个实例也是高层次的类的实例。
把高层次的类称为父类、超类或基类,把由该类特化产生的低层次类称为子类或派生类。派生类不仅具有基类的属性和行为,还具有自己特有的属性和行为。
这种普遍存在的类层次关系必须在系统中有充分体现,并在语言机制上给予支持,继承机制就是体现这一关系的工具,在许多面向对象的语言中,都提供了反映继承模型的编程方法和工具。
需要说明的是,可以按照客观世界中存在的类层次关系,也可以不按照这种层次关系来设计系统。如将本来没有明显层次关系的类设计为基类与派生类的关系。也就是说,类层次关系的设计取决于具体问题。
根据父类与子类的关系,继承可分为四类:替代继承、包含继承、限制继承和特化继承。
在不同类之间,除了概括和特化的关系之外,还有聚合和分解的关系。即“is-part-of,的关系。聚合不宜用继承表示,在C++中提供了一个很好的机制——类的属性描述。
5.1.2 软件设计模块化
软件质量包括外部和内部质量,其中最重要的是外部质量。
外部质量是指与用户和维护人员相关的因素,其中最重要的5点是:正确性(correctness)、健壮性(robustness)、可扩充性(extendibility)、可复用性(reusability)和协调性(compatibility)。可扩充性和可复用性是当今软件工程强调的重点。
获得满意的软件质量的途径就是“模块化”。模块化的完整定义由5个标准和5个原则组成。这5个标准是:模块可分解性(modular decomposability)、模块可结合性(modular composability)、模块可理解性(modular understandability)、模块连续性(modular continuity)、模块保护性(modular protecttion)。这5个原则是语言模块单元(linguistic modular units)、少接口(few interfaces)、接口弱耦合性(small interfaces)、显式的接口(explicit interface)、信息隐藏(information hiding)。
一个好的模块分解技术应该使生成的模块既是开发的又是闭合的。
模块化的主要目的是实现软件的可复用性。这是软件开发可以称为“工程”的关键。在各式各样的项目中,重复使用相同的算法、相同的数据结构,借用相似的设计思想和模式。于是软件工程中的“集成电路”成了众多软件工程师的梦想。
软件复用的一些简单形式,如代码复用、人员复用、设计思想复用和例行库,在一定程度取得了成功,但它们不能真正提供一个系统性的复用途径。对象和类的提出在复用性上迈出了一大步,但仍不够。在实现复用性当中,必须面对的是重复与变化的问题。为了避免浪费,避免引入不确定因素,必须找到一种方法来描述相似结构之间的惊人的共同点,同时考虑它们之间不可勿视的差异。
可复用性的要求启发我们把握住类之间密切的概念关系:一个类可以是其他类的扩充、特化或组合。需要在方法和语言上提供对这些关系的支持,而继承性正是这个支持者。
继承机制允许类从一个或更多的简单和普遍的类中继承其特性和行为,允许根据需要进行更具体的定义。继承性也是C++与C语言之间最大的不同。
5.1.3 继承性的含义
对象类之间的相互关系,若存在继承性,由具有如下性质:
(1)类之间具有共享特性。
(2)类之间具有衍生个性差别。
(3)类之间具有层次关系结构。
一个模块是一组向外界提供的服务。如果没有继承,每个新的模块都必须定义所有它要提供的服务。
继承的好处在于它符合模块设计的开放——闭合原则。开放与闭合原则看起来是矛盾的。但是继承机制很好地解决了这个问题。一个是闭合的,因为它可以被存储在库当中,被其他类使用。它同时又是开放的,因为它可以被继承、它的派生类可以增加新的属性和行为,而它本身不需要被改动。继承的这一特性是建立可复用的软件的基础。抽象数据类型的实现就体现了继承的强大作用。
继承机制使得具有逻辑关系的各种数据结构构成了一个类结构图,如图5.3(P140)。
可复用性可以具体表述为将特征尽可能地放在高层次的类中定义,从而使得尽可能多的子类能共享这一特征。