开宗明义:不是抽象类的基类不是好基类。为什么这么说?

基类和派生类的关系有如下几种:

基类可以是具体类、虚类和抽象类三种,对派生类没有要求。其中具体类是没有虚函数的类,其所有方法都提供了具体实现;派生类方法如果和基类方法同名,则派生类方法隐藏(overwrite)了基类方法。虚类是包含虚函数的类,所有方法都提供具体实现;派生类如果要提供不同于基类虚方法的实现,则在派生类中提供同名方法,该方法将覆盖(override)基类虚方法。抽象类是包含抽象方法(或称为纯虚方法)的类,抽象方法不提供具体实现,抽象类只用于表示概念,不能直接构造抽象类的对象,抽象类的极端化就是“接口”。


首先,从语义上理解。

派生类和基类一定要满足“is-a”关系,即派生类和基类有类属关系,或者说派生类是基类的一种具体化。基类表示某种概念,派生类表示该概念下的某类具体事物。

让一个类继承自另一个具体类明显是不合理的,即使他们表示的概念很相近,或者他们的关系很紧密,这相当于说事物A是一种事物B。比如让直升机继承自固定翼飞机,明显,直升机并不是一种固定翼飞机,虽然它们有“fly”这个方法。正确的抽象方式是,提取出飞机这个概念作为基类,然后让直升机和固定翼飞机都从基类飞机继承。

让派生类从虚类继承也是不合理的,却是常见的错误思路,在很多OOP入门教材上用滥了的例子。即,虚基类提供默认实现,如果派生类的行为和基类不同,则在派生类中覆盖基类虚方法。

其实,由于虚基类提供了所有方法的实现,说明虚基类并不虚,是一个表示具体事物的具体类。在语义上的问题,同样可以用前述例子来说明。


其次,从程序设计角度理解。

让派生类继承自具体基类的动机在于,派生类的某些行为和具体基类相同,派生类想要重用基类的这部分代码。而在另一些行为上派生类和基类又有差别,于是在派生类实现了和基类同名的方法(为了保持接口一致,所以同名)来定义自己的行为。从虚基类获得派生类的动机同上,同时还享受了多态性的好处。

但是上述方式的问题在于:

1、没有遵循“依赖倒置”原则,应对变化的能力不足。OO设计里的一条重要原则就是:针对抽象编程,而不是针对具体对象编程,这条原则也叫做“依赖倒置”原则。基类充当了类继承树和外部世界之间的界面角色,用户通过基类接口使用这个类继承树。如果用具体基类或虚基类作为界面,当类继承树内部发生变化时,就会影响到用户代码,可能要求用户代码修改或者重新编译。

2、可能造成代码重复。假设派生类重新实现了基类的方法foo,其他方法都相同。如果派生类::foo的实现和基类::foo完全不同,正说明了派生类和基类并没有类属关系,而是在概念上和基类处于同一层次的另一事物。如果派生类::foo的实现和基类::foo相似,只是细节不同,那么它们中必然存在大量实现相同功能的代码,这违反了同一份数据或代码只出现一次的要求,正是bug的主要来源之一;解决方法是提取出抽象类,运用模板方法(template method)模式。

还是以飞机的例子来说明。

方案一,直升机和固定翼飞机的飞行方式完全不同,所以直升机::fly需要完全重新改写固定翼飞机::fly,在概念和实现上都是urgly的。于是有方案二:

方案二,提取抽象类飞机,定义抽象方法fly,然后在其派生类固定翼飞机和直升机中分别实现fly方法。

现在变化来了,要将陆基战斗机和舰载战斗机加入这个体系结构中。我们知道,陆基和舰载飞机在在空中的飞行方式是一样的,不同的是舰载机在起飞和降落时有特殊要求。这意味着,陆基战斗机和舰载战斗机可以重用固定翼飞机::fly方法的大部分代码。

方案三,运用模板方法模式,将飞机起飞方式作为抽象方法,将固定翼飞机提取为抽象类,在陆基战斗机和舰载战斗机中分别实现起飞方法。飞机类和固定翼飞机类都成为了抽象类。

那么,如果按照从具体基类或虚基类发展类继承体系的思路,最后将会得到什么样的设计呢?很可能是下面这样的。

固定翼飞机::fly实现陆基飞行方法;舰载战斗机::fly实现copy固定翼飞机::fly的大部分代码,然后添加舰载起飞方式;直升机::fly完全重写fly方法。它还是可以工作的,不过概念不清,可扩展性差。


结论:

1、如果以具体类或者虚类作为基类,说明抽象得还不够,概念没理清,对象模型需要进一步分析,提取出抽象基类。

2、如果基类和派生类的同名方法实现完全不同,则将此同名方法作为抽象类的抽象方法;如果上述同名方法实现部分相似,则运用模板方法模式设计。

最终得到的类继承树中,所有的叶子节点都是且仅是具体类,根节点和所有中间节点都是且仅是抽象类。如下图:


不是抽象类的基类不是好基类!**

转载于:https://blog.51cto.com/14058389/2313662

不是抽象类的基类不是好基类相关推荐

  1. java 虚基类_C++中虚基类与抽象类的简单理解。

    虚基类   是相对于它的派生类而言的,它本身可以是一个普通的类. 只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类.比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有 ...

  2. C++编程练习:多态实验——设计一个基类Shapes,Shapes类公有派生产生矩形类Rectangle和圆类Circle

    例.设计一个基类Shapes,包含成员函数display()并声明为纯虚函数.Shapes类公有派生产生矩形类Rectangle和圆类Circle,分别定义display()函数实现其主要几何元素的显 ...

  3. 模板类中使用友元函数的方式,派生类友元函数对基类的成员使用情况

    在一般友元函数的前面加上 template<typename T),注意在函数的声明和定义处都要加这个模板 例如: //模板类,长方体类 template <typename Elemen ...

  4. 基类成员的public访问权限在派生类中变为_C++ 派生类的构造函数(学习笔记:第7章 06)...

    派生类的构造函数[1] 默认情况 基类的构造函数不被继承; 派生类需要定义自己的构造函数. C++11规定 可用using语句继承基类构造函数. 但是只能初始化从基类继承的成员. 派生类新增成员可以通 ...

  5. 派生类到基类的转换 和基类到派生类的转换

    一. 基类与派生类的转换     3种继承方式(公用.保护.私有继承)中,公用派生类才是基类真正的子类型,它完整地继承了基类的功能.     不同类型数据之间在一定条件下可以进行类型的转换.基类与派生 ...

  6. 将派生类指针赋值给基类的指针

    除了可以将派生类对象赋值给基类对象(对象变量之间的赋值),还可以将派生类指针赋值给基类指针(对象指针之间的赋值).我们先来看一个多继承的例子,继承关系为: #include <iostream& ...

  7. 派生类类型可以转换为基类类型,反之则不行

    派生类的对象都含有基类对象作为其一部分,我们可以将指向派生类型的引用转换为指向它的基类型的引用,像转换指针一样,我们可以用派生类的对象初始化或赋值基类对象,反之却不行.class base{ publ ...

  8. 基类成员函数和派生类成员函数不构成重载

    基类成员和派生类成员的名字一样时会造成遮蔽,这句话对于成员变量很好理解,对于成员函数要引起注意,不管函数的参数如何,只要名字一样就会造成遮蔽.换句话说,基类成员函数和派生类成员函数不会构成重载,如果派 ...

  9. c++,派生类对象可以对基类赋值,基类对派生类不可以赋值

    派生类对象可以对基类对象赋值,赋值时属于派生类独有的部分就舍弃不用. #include <iostream> using namespace std;class DemoA { publi ...

最新文章

  1. 一般熟练盲打需要多久_进口攻略!一般货物进口清关需要多久?如何有效提高清关效率?...
  2. python数据文件读写
  3. 到底这个电路是如何振荡的?
  4. oracle网络公开课《存储技术》课件和视频共享下载
  5. Java正则表达式库基准测试– 2015年
  6. 详解3D点云分割网络 Cylindrical and Asymmetrical 3D Convolution Networksfor LiDAR Segmentation
  7. Pycharm导入anaconda环境
  8. Python 迭代器和 C++ 迭代器,最大的不同竟然是......
  9. vue的route和router的区别
  10. 如何实现不同vlan,不同网关的终端间的通信
  11. 从零开始封装windows10 1803 超详细图文分享 第三篇:程序的安装与优化
  12. 人大金仓数据库工程师培训实战教程(同步复制、读写分离、集群高可用)
  13. 视频压缩基本介绍与标准
  14. 最容易月薪过万城市排名出炉!
  15. uva 10827 - Maximum sum on a torus
  16. LaTeX插入视频示例
  17. 拼多多校招-----六一儿童节(python)
  18. 改html更改百度首页背景颜色,百度地图_更改标注颜色
  19. 全新 Google Pixel Watch 重磅上线 | 着手为 Wear OS 构建应用!
  20. Opencv Python图像处理进阶教程②

热门文章

  1. unity怎么导入系统的树_Unity5.0_树_软件教程_资源库
  2. 保留指定小数位数js函数封装
  3. kali linux 修改用户名密码
  4. 【转】浅谈程序猿的职业规划,看你如何决定自己的未来吧。
  5. 4412开发板UT-Exynos4412三星ARM四核旗舰开发平台android4.0GPS功能
  6. AI比赛-推荐系统(一)-新闻推荐03:多路召回【用不同策略分别召回部分候选集,然后把候选集混在一起供后续排序模型使用】【①、YoutubeDNN双塔召回;②、基于物品召回;③、基于用户召回】【天池】
  7. Mellanox网卡FW刷新方法
  8. 高速信号的完整性分析
  9. 计算机网络三级考的是什么,三级网络技术都考什么东西?
  10. Linux驱动开发(一)