前言
接触Java这么久,我们一定都知道,作为面向对象阵营的典型代表,封装、继承、多态 (其实还应该包含抽象)是其三大基本特征
但是到底什么是多态,它的底层又是如何实现的,又有什么优缺点,可能就没那么容易说清楚
今天我们从它的近亲 C++
语言来重新审视下,到底什么是多态
下面,正文开始!
什么是多态
多态(Polymorphism)是面向对象的三大基本特征之一,多态是在 面向对象 的语境下,将数据模型封装为类对象,而对象之间又含有继承关系,一个基类(base class)可以有不同的派生类(derived class),不同的派生类有各自不同的行为,这种用法就叫做多态
多态是怎么实现的
从语言层面讲,多态依赖于面向对象的继承(implement)和函数的重写
来实现,一个基类可以派生出不同的子类对象,不同的子类对象可以有各自不同的行为,正所谓,龙生九子,凤育九雏
从编译器角度讲,多态依赖于不同的派生类有相同的 指针类型 ,所以创建一个类的时候,可以指向他的基类
进一步讲,派生类和基类的函数会有一个虚函数表(vtable)来存储他们的指针,以达到子类能继承和调用基类成员的功能,如下图所示
图片引自 JerryFu 的博客 >>> https://www.cnblogs.com/jerry-fuyi/p/value_polymorphism.html
多态有什么好处
我们经常强调要 面向接口编程/面向父类编程
,一个定义良好的基类,基本上是不需要修改的,需要修改的只有不同派生类的不同行为而已
因此,当我们在调用层面需要改变具体的行为时,可以修改派生类或者重新创建一个派生类,来满足业务需求
而调用层只需要将原来的派生类改为我们重新创建的派生类即可,达到以最少的代码改动实现对应需求的效果
多态有什么缺点
多态的缺点在于,其针对真实世界 对象
的模拟,在基类定义不准确时,会产生行为不一致的后果
比如我们定义一个基类 Bird
,根据常识,飞行是鸟的基本能力,所以我们定义了一个函数 fly()
不同的鸟类可能又有各自的不同的行为,我们再定义一个派生类 Ostrich
(鸵鸟), 但是鸵鸟是不会飞的,这就导致了派生类与基类行为不一致的后果
这个问题是多态没有办法解决的,所以必须通过其他形式给多态打个 补丁
,即通过函数重写,来改变基类方法,实现派生类自己的行为;但函数重写没有办法解决的是在调用层进行调用时,行为不一致导致的业务逻辑混乱,这时则需要更多的代码去修改调用层
所以当我们定义基类的时候,尽量要想清楚,哪些函数是公共的,需要放在基类中的,以减少这种情况的发生
多态的写法
上面讲了很多理论,下面我们通过代码来看一看,C++中多态的写法
源码请查看 >>> https://github.com/liyilin-jack/cpp_samples/blob/main/main.cpp
话说上帝创造人类的时候,也是使用了面向对象的思想
上帝先定义了一个基类Human ,Human中有定义了虚函数 pee()
和 walk()
,交给派生类去实现 , 由于所有人类都会爬行,所以这里直接实现了爬行的函数
#include <string>
#include <vector>
using namespace std;
class Human{
public:
string name = "";
int age = 1;
Human(){
};
virtual void pee(){
};
virtual void walk(){
};
void crawl(){
printf("%s crawing...\n",name.c_str());
};
};
上帝又定义一个派生类Baby, 由于baby太小,还没学会走路,所以baby的 walk()
函数中进行了判断,大于等于两岁才可 walk()
class Baby:public Human{
public:
Baby(){};
Baby(string name){
this->name = name;
};
~Baby(){
delete &name;
delete &age;
};
void pee() override{
if ((age)<3) {
printf("%s pee with it's parents help...\n",name.c_str());
}else{
printf("%s pee by itself...\n",name.c_str());
}
}
void walk() override{
if ((age)<2) {
printf("%s can't walk yet...\n",name.c_str());
}else{
printf("%s walking...\n",name.c_str());
}
}
};
上帝又创造了一个派生类Man ,该类中也根据实际场景实现了 pee()
和 walk
函数
class Man:public Human{
public:
Man();
Man(string name,int age){
this->name = name;
this->age = age;
}
void walk() override{
printf("%s walking...\n",name.c_str());
}
void pee() override{
printf("%s pee by standing...\n",name.c_str());
}
};
有了Man,再创造一个Woman吧!这样人类就可以自己繁衍后代了
class Woman:public Human{
public:
vector<Baby*> *babys = new vector<Baby*>(10);
Woman();
Woman(string name,int age){
this->name = name;
this->age = age;
}
Baby* haveABaby(Man* man,string name);
void walk() override{
printf("%s walking...\n",name.c_str());
}
void pee() override{
printf("%s pee by sitting...\n",name.c_str());
}
};
Baby* Woman::haveABaby(Man* man,string name){
Baby *baby = NULL;
if (man) {
baby = new Baby(name);
babys->push_back(baby);
}
return baby;
}
人类创造完了,该让他们干活了,现在开启人类世界(C++的main函数)
void createWorld();
int main(int argc, const char * argv[]) {
createWorld();
return 0;
}
void createWorld(){
//create Adam
Human* Adam = new Man("Adam",18);
Adam->walk();
Adam->pee();
//create Eve
Human* Eve = new Woman("Eve",16);
Eve->walk();
Eve->pee();
//Adam meet Eve
//Eve have a baby Gain
string name_gain = "Gain";
Human *Gain = ((Woman*)Eve)->haveABaby((Man*)Adam,name_gain);
Gain->walk();
Gain->pee();
Gain->crawl();
//Eve have a baby Abel
string name_abel = "Abel";
Human *Abel = ((Woman*)Eve)->haveABaby((Man*)Adam,name_abel);
Abel->age = 4;
Abel->walk();
Abel->pee();
Abel->crawl();
}
亚当和夏娃偷食禁果,生下了 Gain
和 Abel
两个孩子
自此,人类生生不息,繁衍至今
后记
以上是笔者最近学习C++的一点心得,如果你有不同的看法,或者心得体会,欢迎在评论中继续探讨
我是释然,我们下篇文章再见!