定义
里氏代换原则,任何基类可以使用的地方,子类一定可以使用。但反过来不一定成立,子类出现的地方,基类不一定能使用。即子类可以拓展父类,但不能更改父类原有的功能。
概述
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样
后两句可以概括为:接受的参数(类型)范围比父类大,返回的参数(类型)范围比父类小
作用
- 里氏替换原则是实现开闭原则的重要方式之一;
- 解决了继承中重写父类造成的可复用性变差的问题;
- 是动作正确性的保证,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性;
- 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
代码实现
假设有一个燕子和一个企鹅,它们都是鸟,但是企鹅不会飞。
现在要计算它们移动一定距离所需的时间。
不使用里氏替换原则
定义Bird类
/**
* @author John117
* @date 2022/05/22 12:09
*/
public class Bird {
double flySpeed;
public void setFlySpeed(double speed) {
this.flySpeed = speed;
}
public double getFlyTime(double distance){`在这里插入代码片`
return distance/flySpeed;
}
}
定义Swallow类
/**
* @author John117
* @date 2022/05/22 12:11
*/
public class Swallow extends Bird{
}
定义Penguin类
/**
* @author John117
* @date 2022/05/22 12:12
*/
public class Penguin extends Bird {
@Override
public void setFlySpeed(double speed) {
this.flySpeed = 0;
}
}
测试类
/**
* @author John117
* @date 2022/05/22 12:13
*/
public class Test {
public static void main(String[] args) {
Bird swallow = new Swallow();
Bird penguin = new Penguin();
swallow.setFlySpeed(180);
penguin.setFlySpeed(180);
System.out.println("飞行距离为300公里");
System.out.println("swallow将飞行 "+swallow.getFlyTime(300)+" 小时");
System.out.println("penguin将飞行 "+penguin.getFlyTime(300)+" 小时");
}
}
结果为
很明显,这不是想要的结果,因为企鹅不会飞,它的飞行速度为0,所以结果才会显示 Infinity。
再来看看使用里氏替换原则是怎样的
使用里氏替换原则
这里把Bird类和Penguin类进一步抽象,定义一个Animal类。
Animal类有一个 runSpeed 属性,这很合理,毕竟鸟也能 run 嘛。
/**
* @author John117
* @date 2022/05/22 12:17
*/
public class Animal {
double runSpeed;
public void setRunSpeed(double runSpeed) {
this.runSpeed = runSpeed;
}
public double getRunTime(double distance) {
return distance/runSpeed;
}
}
再定义Brid类,Bird类继承Animal类,Bird类有自己的 flySpeed 属性
/**
* @author John117
* @date 2022/05/22 12:18
*/
public class Bird extends Animal{
double flySpeed;
public void setFlySpeed(double flySpeed) {
this.flySpeed = flySpeed;
}
public double getFlyTime(double distance) {
return distance/flySpeed;
}
}
再定义Swallow类与Penguin类,分别继承Bird类和Animal类。
这里Swallow就有 runSpeed 属性和 flySpeed 属性了,而因为Penguin不能飞,所以它只有 runSpeed 属性
/**
* @author John117
* @date 2022/05/22 12:20
*/
public class Swallow extends Bird{
}
/**
* @author John117
* @date 2022/05/22 12:20
*/
public class Penguin extends Animal{
}
测试
/**
* @author John117
* @date 2022/05/22 12:21
*/
public class Test {
public static void main(String[] args) {
Bird swallow = new Swallow();
Animal penguin = new Penguin();
swallow.setFlySpeed(120);
penguin.setRunSpeed(20);
System.out.println("如果移动300公里:");
System.out.println("swallow将移动:" + swallow.getFlyTime(300) + "小时");
System.out.println("penguin将移动:" + penguin.getRunTime(300) + "小时");
}
}
输出结果
这里才是符合预期的地方。
这个例子展示了里氏替换原则作用之一——保证动作正确性,其实里氏替换原则还能做更多事情。