C/C++编程:确定你的public继承塑模出is-a关系

- 日理万妓 2023-01-20 04:57 40阅读 0赞

规则

以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味着”is-a”的关系

如果你令class D(“Derived”)以public形式继承class B(“Base”),就意味着,每一个类型为D的对象同时也是一个类型为B的对象,反之则不成立。也意味着”B对象可以派上用场的任何地方,D对象一样可以派上用场“,因为每一个D对象都是一个B对象,反之不成立。

C++对于”public继承“严格指向上面见解,比如:

  1. class Person{
  2. };
  3. class Student : public Person {
  4. };
  5. void eat(const Person &p){
  6. }; // 任何人都会吃
  7. void study(const Student &s){
  8. }; // 只有学生会学习
  9. int main(){
  10. Person p;
  11. Student s;
  12. eat(p); // 人会吃
  13. eat(s); // 学生也会吃
  14. study(p); // error!!!不是所有的人都会学习
  15. study(s); // 只有学生会学习
  16. }

每个学生都是人,但不是每一个人都是学生。(一般)人能成立的事情[比如生日],对学生也成立。对学生成立的事情[比如学校]对人不一定成立。

于是,在C++中,任何函数如果期望获得一个类型为Person(或者pointer-to-Person或者reference-to-Person)的实参,也接受一个Student对象(或者pointer-to-Student或者reference-to-Student))

示例一

有时候你的直觉可能会误导public继承与is-a之间的等价关系这个概念。举个例子,企鹅是一种鸟,这是事情。鸟可以飞,这也是事实。从而,你可以会这样描述:

  1. class Bird{
  2. public:
  3. virtual void fly();
  4. };
  5. class Pengin : public Bird{
  6. //企鹅是一种鸟
  7. }

但是实际上企鹅不会飞。这是怎么回事。事实上,不是所有的鸟都会飞,真正的应该这样写:

  1. class Bird{
  2. // 没有声明fly
  3. };
  4. class FlyingBird : public {
  5. // 会飞的鸟
  6. public:
  7. virtual void fly();
  8. };
  9. class Pengin : public Bird{
  10. // 没有声明fly
  11. }

但这个方法不一定使用于所有,比如对于某些软件系统来说,不需要区分会飞的鸟和不会飞的鸟,从而第一个实现版本更好些。也就是说,世界上不存在一个”适用于所有软件系统“的设计,应该应地制宜。

另一种处理”所有的鸟都会飞,企鹅是鸟,但是企鹅不会飞“的方法是为企鹅重新定义fly函数,令它产生一个运行期错误:

  1. class Pengin : public Bird{
  2. public:
  3. virtual void fly() {
  4. error("Attempt to make a pengin fly!")};
  5. }

还有一种方法是不为Pengin 定义fly函数【推荐,因为好的接口可以防止无效的代码通过编译】:

  1. class Bird{
  2. // 没有声明fly
  3. };
  4. class Pengin : public Bird{
  5. // 没有声明fly
  6. }

示例二

Square类应该以public形式继承Rectangle吗?
在这里插入图片描述

  1. class Rectangle{
  2. public:
  3. virtual void setHeight(int newHeight);
  4. virtual void setWidth(int newWidth);
  5. virtual int height() const;
  6. virtual int width() const;
  7. }
  8. void makeBigger(Rectangle &r){
  9. int oldHeight = r.height();
  10. r.setWidth(r.width() + 10);
  11. assert(r.height() == oldHeight );
  12. }

显然,assert永远为真。因为makeBigger只改变r的宽度不改变高度

  1. class Square : public Rectangle{
  2. };
  3. Square s;
  4. assert(s.width() == s.height()); // 这对所有的正方形一定为真
  5. makeBigger(s); //所有继承,s是一种矩形,所以可以增加面积
  6. 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++中最好的塑模它们

发表评论

表情:
评论列表 (有 0 条评论,40人围观)

还没有评论,来说两句吧...

相关阅读