一、里氏替换原则定义
定义: 如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得所有以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生任何变化,那么类型T2是类型T1的子类型。
通俗理解就是:子类可以扩展父类的功能,但不能改变父类原有的功能。有以下几个引申含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(方法的输入,入参)要比父类的入参更宽松。
- 当子类的方法实现父类的方法时(重写,重载,实现抽象方法),方法的后置条件(输出、返回值)要比父类更严格或相等。
概念扩展:一个软件实体如果适用一个父类的话,那么一定适用其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换其父类对象,而程序的逻辑不变。
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,它反映了基类与子类之间的关系,是对开闭原则的补充。
二、里氏替换原则优点
- 约束继承泛滥,它也是开闭原则的一种很好的体现。
- 提高了代码的重用性。
- 降低了系统的出错率。类的扩展不会给原类造成影响,降低了代码出错的范围和系统出错的概率。
- 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
三、下面我们举符合和不符合的里氏替换原则的例子说明
例子描述: 先定义一个长方形类,正方形是特殊的长方形,再定义一个正方形类继承长方形类并重写长方形类中的方法,再编写一个测试类,测试类中定义一个方法,当长方形的长度大于宽度时候,高度每次加一,输出长和宽的数值。在main方法中给长方形长和宽赋初始值。发现长方形打印没问题。但如果给main方法中的正方形边长赋值,则while条件会变成永真条件,无限输出,这就违背了里氏替换原则,因为用父类执行可以,用子类执行逻辑就被改变了。
@Getter
@Setter
public class Rectangle {//长方形
private long height;
private long width;
}
@Setter
@Getter
public class Square extends Rectangle{//正方形
private long length;
@Override
public long getHeight() {
return getLength();
}
@Override
public long getWidth() {
return getLength();
}
@Override
public void setHeight(long height) {
setLength(height);
}
@Override
public void setWidth(long width) {
setLength(width);
}
}
public class SimpleTest {
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() >= rectangle.getHeight()){
rectangle.setHeight(rectangle.getHeight() +1);
System.out.println("width:" + rectangle.getWidth()+",height:"+rectangle.getHeight());
}
}
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setHeight(5);
resize(rectangle);
Square square = new Square();
square.setLength(10);
resize(square);
}
}
我们怎么修改代码使它遵循里氏替换原则呢,我们可以抽象出来一个四边形接口,长方形和正方形分别实现接口。
@Getter
@Setter
public class Rectangle implements QuadRangle{//长方形
private long height;
private long width;
}
public interface QuadRangle {//四边形接口
long getWidth();
long getHeight();
}
@Setter
@Getter
public class Square implements QuadRangle{//正方形
private long length;
@Override
public long getWidth() {
return length;
}
@Override
public long getHeight() {
return length;
}
}
public class SimpleTest {
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() >= rectangle.getHeight()){
rectangle.setHeight(rectangle.getHeight() +1);
System.out.println("width:" + rectangle.getWidth()+",height:"+rectangle.getHeight());
}
System.out.println("Resize End,width:" + rectangle.getWidth()+",height:"+rectangle.getHeight());
}
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setHeight(5);
resize(rectangle);
Square square = new Square();
square.setLength(10);
resize(square);
}
}
然后这里的 Rectangle参数就不能被改变了,如果传入QuadRangle则会报错,避免了继承泛滥问题
public static void resize(Rectangle rectangle) {
正方形也无法调用resize方法,不满足条件,这就通过修改符合了里氏替换原则。
resize(square);
这也只是类的继承关系结构之间的里氏替换原则的体现。方法上的里氏替换原则的具体体现就不再演示了。日常中大家也一直在使用。