条款34:区分接口继承和实现继承
Differentiate between inheritance of interface and inheritance of implementation
内容:
当你开始设计你的class时候,有时候你希望它的Derived class只继承它的接口(声明);有时候你希望Derived class同时继承它的接口和实现,而且能够重新改写其继承的实现;有时候你又希望继承接口和实现的同时,但不能重新改写其继承的实现.以上这些情况当然在我们实际的开发中是很常见的,对于上述的选择我们该选择怎么样的class设计呢?下面我们来举个例子对这些情况进行分析它们之间的差异,这个例子是基于几种基本图形class的继承体系: 我们先看pure virtual 函数draw,这类函数的显著的特性是:它们必须被任何继承它们的子类重新声明和定义,它本身通常没有定义实现体,也就是说它存在的意义就是为了让derived class继承它的接口,注意我这里说的是通常它没有实现体,而你也可以为它提供实现体,C++对此也不会发脾气的,一般这种做法的用途很有限,主要是用在为子类提供一份默认的行为实现版本,稍后还要提到.
接下来看impure virtual函数error,它的目的很明显就是让derived classes继承该函数的接口和缺省实现,当然也可以自己覆写继承而来的实现.通常情况下运行很好,可不幸的是在某些情况下,这种"为子类提供默认实现体"的方案却可能带给你危险.举个例子,某航空公司设计的飞机有A,B两种,而这两种都是以同一种方式进行飞行,所以该公司设计了如下的飞机继承体系:
A和B飞机共享一份相同的飞行方式,设计方案很好, 运行良好!公司盈余大增,就决定购买一种新式C型飞机.C型的新颖之处在于它的飞行方式与A或B两种型号的飞行方式都不同,该公司程序员于是为其添加了一个新class,而由于急着让新飞机上线,他们竟忘记了重新定义fly函数:
而代码中却还义无反顾的这样写:
该段代码让C型飞机以A或B型号飞机的飞行方式起飞,oh,my god!如果该公司真的这么做了,那么旅游业务业绩的下滑几乎是成为了可以肯定的事实.对于该事情的发生,公司高层非常恼火,做出立即解决该问题的决策,否则走人的指示,处于这样一种压力之下,一种解决方案立即就出现了:
呵呵,问题解决,这种为不同的函数分别提供接口可缺省实现可能带来由于过度雷同的函数名称而引起class命名空间污染问题,其实我们可以用之前提到的"pure virtual 函数必须在derived classes中重新声明,但也可以拥有自己的实现"这一事实来解决掉这个缺点:
这里对于ModelC这个类,如果程序员忘记实现fly接口的话,编译器可就要发飙啰喔,绝对不会让这段代码编译过的,当然ModelC也可以用提供的默认实现,也可以自行实现,灵活性多大啊,喔呼呼呼.
最后我们来看一下Shape的non-virtual函数,声明它就是为了给子类继承接口及其的一份强制实现,由于non-virtual函数代表意义不变性凌驾于特异性,所以它绝对不该在derived class中被重新定义(我们将在条款36中重点讨论这类函数).
pure virtual函数、simple virtual函数、non-virtual函数之间的差异,使得你能以精确指定你要的子类继承的东西:只继承接口,或是继承接口和一份缺省实现,或是继承接口和一份强制实现.由于这些不同类型的声明意味着根本意义并不相同的事情,当你声明你的成员函数时,必须谨慎选择.
请记住:
■ 接口继承和实现继承不同.在public继承下,derived classes总是继承base class的接口.
■ pure virtual函数只具体指定接口继承.
■ impure virtual函数具体指定接口继承及其缺省实现继承.
■ non-virtual函数具体指定接口继承以及强制性实现继承.