下面以“几维鸟不是鸟”为例来说明里氏替换原则(程序源码)。
#include <QCoreApplication>
#include <iostream>
/*!
* \brief 鸟类
*/
class Bird{
public:
double _fly_speed;
void SetSpeed(double speed){
_fly_speed = speed;
}
double GetFlyTime(double distance){
return (distance/_fly_speed);
}
};
/*!
* \brief 燕子类
*/
class Swallow:public Bird{
};
/*!
* \brief 几维鸟类
*/
class BrownKiwi:public Bird{
public:
void SetSpeed(double speed){
_fly_speed = 0;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Swallow *bird1 = new Swallow;
bird1->SetSpeed(120);
std::cout<<bird1->_fly_speed<<std::endl;
std::cout<<bird1->GetFlyTime(300)<<std::endl;
BrownKiwi *bird2 = new BrownKiwi;
bird2->SetSpeed(120);
std::cout<<bird2->_fly_speed<<std::endl;
std::cout<<bird2->GetFlyTime(300)<<std::endl;
return a.exec();
}
运行结果:
120
2.5
0
inf
分析:鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期,其类图如图 1 所示。
图1 “几维鸟不是鸟”实例的类图
程序运行错误的原因是:几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。
解决方法:
取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。其类图如图 2 所示。
#include <QCoreApplication>
#include <iostream>
/*!
* \brief 动物类
*/
class Animal{
public:
double _run_speed;
void SetRunSpeed(double speed){
_run_speed = speed;
}
double GetRunTime(double distance){
return (distance/_run_speed);
}
};
/*!
* \brief 鸟类
*/
class Bird:public Animal{
public:
double _fly_speed;
void SetSpeed(double speed){
_fly_speed = speed;
}
double GetFlyTime(double distance){
return (distance/_fly_speed);
}
};
/*!
* \brief 燕子类
*/
class Swallow:public Bird{
};
/*!
* \brief 几维鸟类
*/
class BrownKiwi:public Animal{
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Swallow *bird1 = new Swallow;
bird1->SetSpeed(120);
std::cout<<bird1->_fly_speed<<std::endl;
std::cout<<bird1->GetFlyTime(300)<<std::endl;
BrownKiwi *bird2 = new BrownKiwi;
bird2->SetRunSpeed(120);
std::cout<<bird2->_run_speed<<std::endl;
std::cout<<bird2->GetRunTime(300)<<std::endl;
return a.exec();
}
运行结果:
120
2.5
120
2.5
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
根据上述理解,对里氏替换原则的定义可以总结如下:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
参考