条款32:确定你的public继承塑模出is-a关系
(Make sure public inheritance model is "is-a".)
内容:
我想对于类之间的public继承关系,每一本C++语言教程都会花费不少的篇幅去阐述,而本款所说的内容也许你
早已是耳熟能详了,但在实际编码涉及中你是否从本条款出发去认真地考虑过你的设计的合理性呢?我觉得大多数的
人没有真正做到这一点的.什么教"is-a"关系?如果你另class D以public形式继承class B,你便告诉编译器(以及你的
代码阅读者)说,你D的每一个对象同时也是类型为B的一个对象.这听起来好像挺简单,但有的时候你必须注意你的直
觉可能误导你.比如说,南极的企鹅其实是一种鸟类(这时你要是瞪大了疑惑眼睛就说明你生物没学好,呵呵),鸟是可
以飞的,这是事实,要让你用C++描述这层关系的话,你毫不犹豫地写了出来,结果如下:
class Bird{
public:
virtual void fly(); //bird can fly
...
};
class Penguin:public Bird{ //Penguin is a kind of bird
...
};
嘟嘟嘟,这里遇到了点问题,按照这个继承体系来说企鹅可以fly,而我们知道现实当中的企鹅是不能飞的,晕,在你
为你的不严谨的设计而成为了同行的笑柄之前,你略加思考就咔咔两下就改了你的设计,结果如下:
class Bird{
...//没有声明fly函数
};
class FlyingBird:public Bird{
public:
virtual void fly();
...
};
class Penguin:public Bird{
... //没有声明fly函数
};
这样的设计比原先的设计更能反映出我们真实的意思,多少为你挽回了一些面子,但是如果我们的软件系统可能根本
就不需要区分会飞的鸟和不会飞的鸟,那你就不需要fly方法来提供,原先的"双classes继承体系"或许就相当令人满意了.
这反映出这样一个事实,世界上并不存在一个"适用于所有软件"的完美设计.所谓的最佳设计取决于系统希望做什么事
情,包括现在与未来.请不要去为程序所不关心的行为耗费你的脑细胞,这样不失为一个完美而有效的设计.
令一种有思想的派系对于这种"企鹅是鸟但不会飞"的问题进行了下面的设计.
void error(const std::string& msg); //定义于另外某处
class Penguin:public Bird{
public:
virtual void fly(){error("Attempt to make a penguin fly!");}
};
呵呵,这里并不是说"企鹅不会飞",而是说"企鹅会飞,但尝试那么做是一种错误",当然这只有在运行期才能检测
出来,其实为了遵循最佳设计原则,我们完全没有必要定义fly函数,就可以达到目的:
class Bird{
... //没有声明fly函数
};
class Penguin:public Bird{
... //没有声明fly函数
};
如果你这样用它:
Penguin p;
p.fly();//喔欧,压根就没有fly方法,被编译器骂了吧!
前面的条款18提到:好的接口可以防止无效的代码编译,因此你宁可采取"编译期就拒绝"的设计,而不是
"直到运行期才侦测它们"的设计.好,我们再来看一个几何学方面的例子,我这里有个问题"请用类关系描述正方形
与长方形之间的关系",这也太简单了吧?莎莎几声以后你的设计呈现出来了.
class Rectangle{
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height()const;
virtual int width()const;
...
};
class Square:public Rectangle{
...
};
正当你为你的设计而自鸣得意的时候,遇到了这样的一个客户,他(她)写出了下面这个函数和调用代码:
void makeBigger(Rectangle& r){ //这个函数用以增加r的面积
int oldHeight = r.height;
r.setWidth(r.width() + 10); //宽度加10
assert(r.height() == oldHeight); //r的高度是否改变
}
//test.cpp
Square s;
...
assert(s.width() == s.height());
makeBigger(s);
assert(s.width() == s.height()); //对所有正方形应该为真,但事实却不是.
怎么样?某些可施行与矩形身上的事情(例如宽度可独立于高度被外界修改)却不可施加于正方形身上(宽度和高度总是
一样),但public继承关系主张能够施行与base class身上的每件事都能施行与derived class,在矩形与方形问题上该主张
不能保持,故以public塑模它们之间的关系并不正确.
请记住:
★ "public继承"因为is-a.适用于base class身上的每一件事情一定也适用与derived class身上,因为每一个
derived class对象也都是一个base class对象.