本节书摘来自异步社区出版社《C++编程风格(修订版)》一书中的第3章,第3.5节,作者:【美】Tom Cargill,更多章节内容可以访问云栖社区“异步社区”公众号查看。
3.5 接口与实现
C++编程风格(修订版)
为什么要使用继承?如果对继承关系作进一步的分析,我们会发现程序中的继承其实可以完全去掉。因此,第二种解决方案就是使用成员对象而不是继承。
在第2章中已经讨论过,一个C++类有着两个重要的方面:用于描述类行为的公有接口,以及行为的私有实现。大多数继承所采用的都是公有继承的形式:派生类同时继承了基类的接口和实现。不过,我们还可以有选择性地进行继承,即派生类可以只继承接口或者只继承实现。在私有基类中,派生类继承了所有的实现,但没有继承任何接口。而在继承公有的抽象基类时,派生类继承了所有的接口,但所继承的实现可能是不完整的或者不存在的。
我们需要对每一个继承都进行谨慎的评估。例如,究竟是需要继承接口,还是需要继承实现,抑或是对二者都需要进行继承。在本程序中,CharStack和IntStack仅需要继承实现。虽然CharStack的接口类似于IntStack的接口,但二者还是不同的。虽然它们各自的成员函数都有着相同的名字,但其中一个成员函数的参数是char类型的值,而另一个成员函数的参数是int类型的值。CharStack和IntStack并没有共同的接口;客户代码既不能把它们各自的对象进行统一处理,也不能交换使用。CharStack和IntStack的抽象都堆栈抽象的特化,但它们并不是StackIndex抽象的特化,IntStack对象并不是一种StackIndex对象。从客户代码的角度来看,StackIndex、IntStack和CharStack提供的是不同的服务。StackIndex处理的是索引,IntStack管理的是一个整数值的堆栈,而CharStack管理的是一个字符堆栈。从客户代码的角度来看,这种“是一种(Is Kind Of A)”的关系是没有意义的,因此在这些类中的公有继承就是不合适的。
派生类与其私有基类之间的关系其实类似于客户类与服务器类的关系。将StackIndex作为一个私有基类,并将IntStack和CharStack从StackIndex继承下来的主要目的,是为了使用StackIndex的索引管理服务,因此派生类可以被看作是StackIndex的客户。我们还有另外一种不用继承来表达这种关系的方法,那就是在IntStack和CharStack的对象中,分别使用一个StackIndex实例作为私有成员。
现在,StackIndex是CharStack和IntStack的一个私有成员对象,而并不是被作为基类。如程序清单3.3所示,我们对程序结构的改动是非常小的,同样类型的对象依然提供着同样的服务。区别仅在于现在提供服务的是成员对象,而并非私有基类,因此我们通过成员变量的名字来使用服务器类,而不是类的名字。在本例中,选择私有基类的形式与选择私有成员对象的形式相比,只是一种个人喜好——这两种方法在功能上是完全等价的。不过,为了简单起见,成员对象通常是一种更好的选择,因为与继承相比,成员对象的语义要更加清晰。
识别出对实现的继承;可以使用私有基类或者(更好的方法是)使用成员对象。
程序清单3.3 用成员对象代替继承
重载与默认参数
在结束这个示例之前,我们要注意在CharStack和IntStack中重载构造函数的相似性。在第2章讨论string类的重载构造函数时,就提到过应该使用默认参数的形式来代替函数重载的形式,如程序清单3.4中的CharStack所示。对于每个类来说,将多个构造函数合为一个构造函数可以简化代码的维护工作。虽然在有些时候,将函数重载的形式转换为默认参数的形式并不是很直观的,但我们应该尽可能地去考虑这种转换。我们来回顾一下以下这条规则:
考虑使用默认参数的形式来代替函数重载的形式。
最后,注意在程序清单3.4的CharStack::CharStack中,循环在每次迭代时仍然调用了函数strlen()。如果这个构造函数被证明是一个性能瓶颈,那么可以很容易改正这个问题。
程序清单3.4 用默认参数来代替函数重载
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。