设计模式 - 浅谈里氏替换原则

里氏替换原则

原则说明: 里氏替换原则(Liskov Substitution principle)是对子类型的特别定义,派生类(子类)对象可以在程序中代替其基类(超类)对象。

为什么要遵守该原则

违反里氏替换原则会导致代码的复杂性增加,可读性降低,同时也会增加代码修改的难度。当一个子类不能完全替代其父类时,这就增加了出错的可能性。
按我自己遇到的场景去理解,我调用了一个计算面积的方法,而子类无法完全替代超类,导致一个Bug的产生。下面就是一个简单的案例。

用一个简单的例子说明

假定我们有两个类,一个是矩形 Rectangle,一个是正方形 Square继承矩形 Rectangle

public class Rectangle {
    private int width;
    private int height;
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getArea() {
        return this.width * this.height;
    }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }
    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

有个计算面积的方法。如果你传入一个矩形对象,这个函数会打印20,这是正确的。但是如果你传入一个正方形对象,由于正方形的宽度和高度总是相等的,所以无论你怎么设置,它们总是会被设置为相同的值,这个函数会打印16或25,这就是错误的。

public void calculateArea(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    System.out.println(r.getArea());
}

遵守里氏替换原则,因为正方形类不能完全替代矩形类。我们应该将正方形类从矩形类中分离出来,使它们成为两个独立的类:

public class Rectangle {
    private int width;
    private int height;
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getArea() {
        return this.width * this.height;
    }
}

public class Square {
    private int side;
    public void setSide(int side) {
        this.side = side;
    }
    public int getArea() {
        return this.side * this.side;
    }
}

符合里氏替换原则的继承

如果子类可以完全替代父类的行为,那么这个继承就是符合里氏替换原则的。
例如,有一个动物类和一个狗类,动物类的职责是吃,狗类的职责也是吃,那么狗类就可以完全替代动物类的职责,这个继承就是符合里氏替换原则的。

public class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}

如何避免违反里氏替换原则

  1. 从行为出发来设计。在做抽象或设计时,不只是要从模型概念出发,还要从行为出发。
  2. 明确职责,确保子类可以完全替代父类的职责
  3. 契约设计,时刻保持派生类与基类的契约不被破坏。

本文链接:

https://pugqq.com/archives/richter-substitution.html
设计模式 - 浅谈里氏替换原则 - I/O
快来做第一个评论的人吧~

# 最近更新

Nginx的proxy_pass指令完全拆解2021-03-21

Nginx配置Jenkins域名访问2021-01-03

设计模式 - 浅谈备忘录模式2020-12-02

设计模式 - 浅谈中介者模式2020-11-23

设计模式 - 浅谈迭代器模式2020-11-02

MySQL5.7 字符集设置2020-10-26

设计模式 - 浅谈状态模式2020-10-23

设计模式 - 浅谈访问者模式2020-10-13

设计模式 - 浅谈观察者模式2020-10-12

设计模式 - 浅谈命令模式2020-09-21