派生类的对象能够替换其基类的对象被使用
定义
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范
举个例子,比如有一个基类Animal和一个派生类Cat,程序中任何使用Animal对象的地方,我们都可以放Cat对象上去(派生类的对象能够替换子类的对象),而完全不扰乱程序的逻辑(程序里关于Animal的假设,Cat都不能打破)。
更通俗一点描述就是,假如我们给Animal设定方法,可以设定一个吃(), 这个没问题,凡是动物都会吃东西,猫也会吃东西。但是如果给动物设定方法:走路(),就不太好了,因为不是所有的动物都会走路,比如鱼。假如Animal类存在走路这个方法,且Animal对象调用了该方法,此时再用Cat对象替换Animal对象就不符合常识了,此时违背了里氏替换原则。
实现方法
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法(除非基类中的纯虚函数必须要重写,虚函数可以重写,普通函数不能或者尽量不要重写)
-
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
-
子类中可以增加自己特有的方法
-
当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
-
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
案例分析
[示例1] 几维鸟不是鸟–破坏原则案例
分析:鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如类的设计如下:基类为鸟(具有飞行速度这一属性且v大于0),燕子和几维鸟继承自鸟(实际上不是所有鸟的v都大于零,因此几维鸟对象替换基类对象时可能会出错)。
#include <iostream>
class Bird {
public:
float flySpeed; //不符合里氏替换原则
void setSpeed(float v) {
flySpeed = v;
}
float getFlyTime(float dis) {
return dis/flySpeed;
}
};
class Swallow : public Bird {
};
class BrownKiwi : public Bird {
};
int main(){
Bird* b1 = new Swallow; // &b1 是
Bird* b2 = new BrownKiwi;
b1->setSpeed(300);
b2->setSpeed(0);
std::cout<<"b1 fly time is: " << b1->getFlyTime(300)<<std::endl;
std::cout<<"b2 fly time is: " << b2->getFlyTime(300)<<std::endl;
return 1;
}
[示例2]
正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有呼吸的能力,鸟类继承自动物类,燕子继承自鸟类;几维鸟继承自动物类。
#include <iostream>
class Animal{
public:
float runSpeed;
virtual void setRunSpeed(float v) {
runSpeed = v;
}
virtual float getRunTime(float dis) {
return dis/runSpeed;
}
};
class Bird : public Animal{
public:
float flySpeed;
virtual void setSpeed(float v) {
flySpeed = v;
}
virtual float getFlyTime(float dis) {
return dis/flySpeed;
}
};
class Swallow : public Bird {
};
class BrownKiwi : public Animal {
};
int main(){
Bird* b1 = new Swallow; // &b1 是
Animal* b2 = new BrownKiwi;
b1->setSpeed(300);
b1->setRunSpeed(0.01);
b2->setRunSpeed(0.2);
std::cout<<"b1 fly time is: " << b1->getFlyTime(300)<<std::endl;
std::cout<<"b1 run time is: " << b1->getRunTime(300)<<std::endl;
std::cout<<"b2 fly time is: " << b2->getRunTime(300)<<std::endl;
return 1;
}