设计原则 - 里氏替换原则
概念
-
里氏替换原则(Liskov Substitution Principle, LSP):一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
-
里氏替换原则表眀,在软件中将一个基类对象替换成他的子类对象,程序将不会产生任何错误和异常,反之则不成立;如果一个软件实体使用的是一个子类对象,那么它不一定能够使用基类对象。例如,张三喜欢动物,那张三一定喜欢狗,因为狗是动物的子类;如果张三喜欢狗,不能据此断定张三喜欢动物。
-
优点1:约束继承泛滥,开闭原则的一种体现。
-
优点2:加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险。
编码
实例
- 验证正方形是不是特殊的长方形
Rectangle.java
/**
* @Description 长方形
* @date Dec 15, 2021
* @Version 1.0
*/
public class Rectangle {
private long length;
private long width;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
}
Square.java
/**
* @Description 正方形
* @date Dec 15, 2021
* @Version 1.0
*/
public class Square extends Rectangle{
/**
* 边长
*/
private long sideLength;
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
@Override
public long getLength() {
return getSideLength();
}
@Override
public void setLength(long length) {
setSideLength(length);
}
@Override
public long getWidth() {
return getSideLength();
}
@Override
public void setWidth(long width) {
setSideLength(width);
}
}
Test.java
/**
* @Description 测试类
* @date Dec 15, 2021
* @Version 1.0
*/
public class Test {
/**
* 调整大小
* 当宽小于等于长的时候给宽度+1,直到它们相等
* @param rectangle
*/
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() <= rectangle.getLength()) {
rectangle.setWidth(rectangle.getWidth() + 1);
System.out.println("width: " + rectangle.getWidth() + " length: " + rectangle.getLength());
}
System.out.println("resize end, width: " + rectangle.getWidth() + " length: " + rectangle.getLength());
}
}
- 长方形(基类)验证
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
resize(rectangle);
}
- 输出如下,程序正常执行:
width: 11 length: 20
width: 12 length: 20
width: 13 length: 20
width: 14 length: 20
width: 15 length: 20
width: 16 length: 20
width: 17 length: 20
width: 18 length: 20
width: 19 length: 20
width: 20 length: 20
width: 21 length: 20
resize end, width: 21 length: 20
- 正方形(子类)验证
/**
* 正方形(子类)验证
* @param args
*/
public static void main(String[] args) {
Square square = new Square();
square.setLength(10);
resize(square);
}
- 输出如下,方法会无穷无境的进行下去,直到溢出,当我们将父类替换成子类进行执行的时候,程序运行的期望和我们所期望的是不一样的,这里的程序设计违反了里氏替换原则
width: 11 length: 11
width: 12 length: 12
width: 13 length: 13
width: 14 length: 14
width: 15 length: 15
width: 16 length: 16
width: 17 length: 17
width: 18 length: 18
width: 19 length: 19
width: 20 length: 20
width: 21 length: 21
width: 22 length: 22
width: 23 length: 23
width: 24 length: 24
width: 25 length: 25
width: 26 length: 26
width: 27 length: 27
width: 28 length: 28
....................
里氏替换原则
- 创建新的四边形类,解除长方形和正方形的继承关系
Quadrangle.java
/**
* @Description 四边形
* @date Dec 19, 2021
* @version 1.0
*/
public interface Quadrangle {
long getWidth();
long getLength();
}
Rectangle.java
/**
* @Description 长方形
* @date Dec 15, 2021
* @Version 1.0
*/
public class Rectangle implements Quadrangle {
private long length;
private long width;
@Override
public long getWidth() {
return width;
}
@Override
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public void setWidth(long width) {
this.width = width;
}
}
Square.java
/**
* @Description 正方形
* @date Dec 15, 2021
* @Version 1.0
*/
public class Square implements Quadrangle {
/**
* 边长
*/
private long sideLength;
@Override
public long getWidth() {
return sideLength;
}
@Override
public long getLength() {
return sideLength;
}
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
}
Test.java
- 可以看到,通过四边形,get方法时ok的,set方法却是不行的,因为四边形接口中并没有申明set长和宽,秘密在于父类并没有赋值方法,整个resize方法是不适用于四边形类型的,该方法中提出了约束,禁止继承泛滥
总结
-
里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象
-
在运用里氏替换原则时,应该将父类设计为抽象类或接口,让子类继承父类或实现父接口并实现在父类中声明的方法,程序运行时,子类实例替换父类实例,可以很方便地扩展系统的功能,无需修改原有子类的代码,增加新的功能可以通过增加新的子类来实现。