浅谈学习C++时用到的【封装继承多态】三个概念
封装继承多态这三个概念不是C++特有的,而是所有OOP具有的特性。
由于C++语言支持这三个特性,所以学习C++时不可避免的要理解这些概念。
而在大部分C++教材中这些概念是作为铺垫,接下来就花大部分篇幅讲语言机制如如何实现这些特性的。
其实以上三个概念是不是OOP编程语言具有的特性,而是现实世界本身所具有的规律,只是OOP提炼了这些特性而已。
而且技术面试时最喜欢问的就是虚函数,而要理解虚函数这三个概念一个都少不了。
下面来分别描述这三个概念
【封装】
以下偷懒引自百度百科的描述。
隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
我的描述:封装在这里的解释我意为特化到编程语言上的解释,还原来理解,世间万物都是封装呈现给我们的。
比方说有一家银行,银行对我们而言就是一个封装好的概念,我们不需要知道资金是如何流转的,不需要知道钱是怎么取出来的,但银行对外开放提供贷款,存款等公用服务,我们又能实实在在的取到钱,通过银行我们能获得想要的服务。
提炼的结果就是:我们而不需要知道银行(封装模型)是如何运作的,只需要知道提供哪些银行卡/账单(封装模型的外部输入)等,就能获得哪些服务(封装对外开放的接口)。
再扩大一点,任何一个事物,电脑、手机、小猫小狗、公车、国家、首脑、地球、车展、晚会、酒吧等等,我们都可以从封装的角度去理解。
以上事物都可以提炼出提供哪些服务(需求决定的)的概念,并且我们不知道电脑/手机/小猫小狗这些事物的具体构造。
于是封装的概念应该是这样子的:封装是一个抽象的模型,该模型对外提供服务,而任何使用该模型的用户不需要知道模型是如何运作的。
我们还原了封装的本来的面貌:世间万物本身具有的特性。
那么来思考这样的一个问题,如果让你来设计一门编程语言,你要用编程语言来描述这一特性,你要怎么设计。
你会得到以下三个结论:
1.封装模型要包含固有属性(数据)---银行有多少员工,金库有多少钱等等
2.封装模型要提供服务(操作)---存款,贷款等
3.要使用访问权限来控制对外开放和隐藏---柜台窗口,金库保险箱密码等
这样甚至能得到面向过程编程语言到面向对象语言的发展是必然的这一结论。
映射到C++中就成了类,于是C++的类的描述可以写成这样:类是一个抽象的模型,该模型对外提供接口,而使用该类的用户不需要知道类是如何实现的。
上面的三个结论在C++中依次为:
1.类的数据成员-----封装模型的固有属性(数据)
2.类的成员函数-----封装模型提供的服务(操作)
3.访问权限关键字---使用访问权限来控制对外开放和隐藏
C++语言只是将封装这一概念从世间万物的本质特性提炼了出来,而C++语言设计者必然要设计出支持这一特性的语言机制,因为只有这样才能把现实生活中的事物映射到编程语言给实现出来。
至于各种教材上写道的封装的目的是是增强安全性和简化编程,我觉得就是扯淡,就是语言机制必须这样设计,不这么设计你几乎不可能建软件模型(架构)。
【继承】
以下同样引自百度百科的描述。
继承是面向对象语言的重要机制。借助继承,可以扩展原有的代码,应用到其他程序中,而不必重新编写这些代码。在java语言中,继承是通过扩展原有的类,声明新类来实现的。扩展声明的新类称为子类,原有的类称为超类(父类)。继承机制规定,子类可以拥有超类的所有属性和方法,也可以扩展定义自己特有的属性,增加新方法和重新定义超类的方法。
沿用解释【封装】的思路,我们依然可以还原继承的本质----世间万物的本质特性(更抽象一点的概念)。
为什么说是更抽象一点的概念?
如果封装的概念只去关注个体,那么继承就是去关注个体与个体之间的关系。封装我们只关注了一个模型,现在多个模型要产生关系,对这种关系要提炼一下。
相比封装而言,个体与个体间的关系,当然是关系更抽象一点了,那么把此关系还原到世间万物的本质特性就需要甄别一番。
最显而易见的是生物学对物种的分类,是非常好的关系模型。
先分一个物种大类,然后细分,再细分。生物学分七次:种、属、科、目、纲、门、界。
生物学分好了,物理学是不是也分好了:物理学-力学-流体力学。
再来看看文学:小说-科幻小说-硬科幻小说。
我的描述:所谓继承就是层次分类,并且世间万物绝大部分都是可以层次分类的。
那么分类的依据是什么?也就是根据层次分类要设计哪些语言机制。
为了描述方便我们引入 上某层和下某层 的概念
1.提取个体模型之间的共性-第一次分类得到结果N1---相对N2是下一层(个体之间的比较关系用相对这个词)
2.在N1这一层继续提取共性-第二次分类得到结果N2---相对N1是上一层
...
3.在Nn这一层提取共性-得到分类结果Nn---相对N1是上n层
用一张图来表示:
用Nn加入一些特有属性可能会变成Nn-1,N5甚至N1。
那么语言机制的特性必然是:
1.上某层必然包含下某层的属性和操作----子类包含父类的数据成员和成员函数
2.上某层加入某些某些属性和操作会变成下某层----使用继承加入数据成员和成员函数
这个加入的过程就是继承。
因为每次提炼都会舍弃很多个体的特有属性,所以分的层次越多,到最后的分类结果Nn可能是非常抽象的----我们又可以得到一些边边角角的结论,抽象基类的概念。
我们还可以解释为什么抽象基类实例化会很奇怪,因为没有人会这么说:我有生物学。而你却可以说我有小狗。
继承只是分类过程中发现的一种关系,当然还有组合关系,其他关系。
设计有一条原则是:优先使用组合,而不是继承,这也证明了世间万物的组合关系的数目是远大于继承关系的数目的。
【多态】
老习惯:
在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
我们依然使用还原的办法,多态---世间万物的本质特性。
继承体现了不同封装模型(类)之间的一种关系,它源自层次分类,那么不同层次之间显然是有联系的,多态就是其中一个联系,这个联系就是本质特性。
我的描述:使用上某层来执行下某层的具体操作。
依然举例子:所有的动物都有吃这个动作,小猫属于动物(层次分类--继承关系),那么小猫必然具有吃这个动作,小狗亦然。
现在喊小猫吃,猫就吃鱼了,喊小狗吃,小狗就吃骨头了。
同是吃这个操作,却体现了不同的行为。这就是多态。
我们使用了上层的操作来执行了下层的具体操作。
上图:
我们用继承的结论来推导:
上层能识别下层对象,下层包含上层的操作,而下层可能有多个封装模型(类),怎么实现用上层来描述下层的操作呢?
1.首先是继承关系
2.用上某层指向下某层(喊小猫,喊小狗)---基类指针或引用指向派生类对象
3.使用上某层来准确执行下某层不同封装模型的操作(吃这个动作)---虚函数
C++使用了很多语言机制来支持虚函数---每个类维护一个虚函数表、类型兼容规则等等,因为虚函数代表了运行时多态,对这个软件的设计有着至关重要的作用。
最后我们来总结一下:
封装:一种 对外提供服务的模型,封装模型是对世间万物的个体抽象。
继承:一种 封装模型之间关系的抽象,是不同封装模型的层次分类。
多态:一种 不同层次分类下的重要联系,是一种跨层操作。