很多同学无法理解多态是怎么回事。什么叫:同样的消息作用在不同的对象上给出不同的反应?
其实现实中,多态的例子是有很多的,比如:挪动的指令,如果传递给一辆车的话,那就是挪车,实际的动作就是把车开走。如果传递给一个凳子的话,那就是挪凳子,实际的动作应该是把凳子拎走。这就是多态。
那么,在面向对象程序设计里,多态又有什么用呢?
总结一句话就是:
多态,为了增加程序的可扩展性。
接下来,我们用一个小例子给大家解释一下多态的真实原理和应用领域。
程序员小瑞刚结婚,正在筹备马尔代夫蜜月计划,酒店刚订完,Leader来电话,儿童益智游戏《小马良》项目批下来了。收益丰厚。项目完成需两个月。
主要功能:教孩子画画。可显示多种图形,甚至可以求图形面积和周长。
技术路线:面向对象技术。已有基类Point,后续各种形状子类有待进一步设计。形状数量未知。
小瑞的任务:设计开发一个函数,用来显示图形。
眼看马代计划泡汤。小瑞怎么办?
三套方案:
1.世界那么大,我出去走走。自己是爽了,工作丢啦。
2. 开启敬业模式,持续更新代码,等待后续的图形子类一一设计完成,针对性的订制显示图形的函数。 倒不是不可以,只是蜜月肯定是泡汤了,因为不知道到底有多少图形子类被设计出来,周期未知。
3.开发一个通用函数,无论后续有多少图形子类设计出来,都能够兼容的完成显示图形的功能。 小瑞开心:可以提前做好放在那,提着行李箱带着老婆去蜜。 老板高兴:等他蜜月回来,可以给安排别的任务,能者多劳。
问题就变成了,如何开发一个通用函数,能适应所有类别的图形的绘制?
要解决这个问题,需要一个面向对象语言支持以下几个机制:
1.类型适应
类型适应是在继承关系中,子类对象和基类对象的微妙关系。简单来说,有下面三种形式(主要关注形式1和2):
形式1:子类对象可以作为基类对象来使用。
一个研究生可以作为一个学生来使用。
一只猫可以作为一个动物来使用。
Point p; Circle c;其中Circle是Point的子类。
p = c; 合法。
语义上:c是一种Point,所以,c可以当做Point来使用。
语法上:c继承了Point类的属性,它有满足向p赋值的所有数据。
形式2:基类指针可以指向子类对象。
Point *p; Circle c;其中Circle是Point的子类。
p = &c; 合法。
语义上:c是一种Point,所以,p可以指向c。
语法上:c继承了Point类的属性,通过Point类指针p可以指向c中所有基类成员。
形式3:基类的引用可以引用子类对象。(C++)
Circle c;其中Circle是Point的子类。
Point &p = c; 合法。
语义上:c是一种Point,所以,c可以当做Point来使用
语法上:c继承了Point类的属性,通过Point类引用p可以引用到c中所有基类成员。
2.函数覆盖(override)
基类与子类中存在具有完全相同的函数原型的函数,称为子类对基类的函数覆盖。
考虑一下,《小马良》项目中Point类和Circle类设计。Point是基类,Circle是子类。
display就是一个典型的函数覆盖,也叫重写函数。
小试牛刀:
结合类型适应机制和函数覆盖机制,以上图设计的Point类和Circle类为基础,尝试设计一个通用的函数Disp,用来显示图形。
注意:Disp的参数为一个基类Point的引用。根据类型适应原则第三条,main函数中的子类Circle的对象c可以被基类引用p来引用。而根据函数覆盖原则,Circle类和Point类都定义了一个一模一样函数原型的函数display,当然函数定义的内容肯定不同,Circle类的display肯定是显示圆信息用的,Point的display则是显示点信息用的。在本例中,Disp函数的形式参数是Point引用p,实际参数是Circle对象c,p.display()这条语句则应该调用Circle类的display函数,显示一个圆。
如果是Python或者Java这种纯面向对象语言。多态的实现,就可以到此为止了。通用函数Disp也就开发完毕了。以后再出现任何新的基于Point的图形子类,如rectangle,triangle,square等,只要这些子类实现对display的函数覆盖,都可以调用Disp函数,实现相应图形对象的显示。
也就是说:上图中的Disp不需要任何改动,真正实现了零维护。
马尔代夫不是梦。
但是C++作为过渡时期的产物,支持多态的能力较弱,还需要手动的进行一个设置。我们叫他,虚函数。
只需要在函数定义前加上关键字virtual就可以啦。至于背后的机理,就不在这里阐述了。
学会了就点个赞吧。