C/C++编程:确定你的public继承塑模出is-a关系
规则
以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味着”is-a”的关系
如果你令class D(“Derived”)以public形式继承class B(“Base”),就意味着,每一个类型为D的对象同时也是一个类型为B的对象,反之则不成立。也意味着”B对象可以派上用场的任何地方,D对象一样可以派上用场“,因为每一个D对象都是一个B对象,反之不成立。
C++对于”public继承“严格指向上面见解,比如:
class Person{
};
class Student : public Person {
};
void eat(const Person &p){
}; // 任何人都会吃
void study(const Student &s){
}; // 只有学生会学习
int main(){
Person p;
Student s;
eat(p); // 人会吃
eat(s); // 学生也会吃
study(p); // error!!!不是所有的人都会学习
study(s); // 只有学生会学习
}
每个学生都是人,但不是每一个人都是学生。(一般)人能成立的事情[比如生日],对学生也成立。对学生成立的事情[比如学校]对人不一定成立。
于是,在C++中,任何函数如果期望获得一个类型为Person(或者pointer-to-Person或者reference-to-Person)的实参,也接受一个Student对象(或者pointer-to-Student或者reference-to-Student))
示例一
有时候你的直觉可能会误导public继承与is-a之间的等价关系这个概念。举个例子,企鹅是一种鸟,这是事情。鸟可以飞,这也是事实。从而,你可以会这样描述:
class Bird{
public:
virtual void fly();
};
class Pengin : public Bird{
//企鹅是一种鸟
}
但是实际上企鹅不会飞。这是怎么回事。事实上,不是所有的鸟都会飞,真正的应该这样写:
class Bird{
// 没有声明fly
};
class FlyingBird : public {
// 会飞的鸟
public:
virtual void fly();
};
class Pengin : public Bird{
// 没有声明fly
}
但这个方法不一定使用于所有,比如对于某些软件系统来说,不需要区分会飞的鸟和不会飞的鸟,从而第一个实现版本更好些。也就是说,世界上不存在一个”适用于所有软件系统“的设计,应该应地制宜。
另一种处理”所有的鸟都会飞,企鹅是鸟,但是企鹅不会飞“的方法是为企鹅重新定义fly函数,令它产生一个运行期错误:
class Pengin : public Bird{
public:
virtual void fly() {
error("Attempt to make a pengin fly!")};
}
还有一种方法是不为Pengin 定义fly函数【推荐,因为好的接口可以防止无效的代码通过编译】:
class Bird{
// 没有声明fly
};
class Pengin : public Bird{
// 没有声明fly
}
示例二
Square类应该以public形式继承Rectangle吗?
class Rectangle{
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const;
virtual int width() const;
}
void makeBigger(Rectangle &r){
int oldHeight = r.height();
r.setWidth(r.width() + 10);
assert(r.height() == oldHeight );
}
显然,assert永远为真。因为makeBigger只改变r的宽度不改变高度
class Square : public Rectangle{
};
Square s;
assert(s.width() == s.height()); // 这对所有的正方形一定为真
makeBigger(s); //所有继承,s是一种矩形,所以可以增加面积
assert(s.width() == s.height()); // 对所有的正方形应该仍然为真
但是,显然我们遇到了问题:
- 调用makeBigger()之前,s的高度和宽度相同
- makeBigger()函数内,s的宽度改变,高度不变
- 调用makeBigger()之后,s的高度和宽度应该还是相同(注意这里是传引用,所以改变的是原本,不是s的副本)
也就是说,某些可以施行于矩形身上的事情却不可以施行于正方形上。但是public继承主张,能够施行于基类对象的每件事情,也可以施行于派生类对象上。所以,用public继承塑模正方形和矩形的关系不正确,编译器通过并不代表程序正确。
总结
- “public继承”意味着is-a。适用于基类身上的每一件事情也一定适用于派生类身上,因为每一个派生类对象也都是一个基类对象
- is-a并不是唯一存在于类之间的关系,另两个常见的关系是has-a(有一个)和is-implemented-in-tems-of(根据某物实现出)。将上面这些重要关系的任何一个塑模为is-a都是错误的,所以你应该确定你确实了解这些类相互关系之间的差异,并指导如何在C++中最好的塑模它们
还没有评论,来说两句吧...