设计原则 - 里氏替换原则

概念

  • 里氏替换原则(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方法是不适用于四边形类型的,该方法中提出了约束,禁止继承泛滥

总结

  • 里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象

  • 在运用里氏替换原则时,应该将父类设计为抽象类或接口,让子类继承父类或实现父接口并实现在父类中声明的方法,程序运行时,子类实例替换父类实例,可以很方便地扩展系统的功能,无需修改原有子类的代码,增加新的功能可以通过增加新的子类来实现。


源码


- End -
- 个人学习笔记 -
- 仅供参考 -

posted @ 2022-01-10 07:00  Maggieq8324  阅读(43)  评论(0编辑  收藏  举报