里氏替换原则(LSP)

里氏替换原则(LSP)

介绍

里氏替换原则(Liskov Substitution Principle,LSP)是面向对象编程中的一个重要原则,由Barbara Liskov提出,主要阐述了子类在继承父类时应该遵循的规则,即在任何父类出现的地方都可以替换为其子类,且不会导致原有程序的错误或异常行为。

原则

父类出现的地方一定可以用子类的对象替换,子类不可以改变父类的行为和状态
违反里氏替换原则的前提是错误的继承
不重写父类的默认方法,只重写父类的抽象方法。如果重写了父类的默认方法,那么就是修改了父类的行为

正反案例

反例

一个不符合LSP的例子是一个鸟类的继承结构。如果定义一个鸟类的基类,其中有一个fly()方法,那么这个方法可以被所有的鸟类继承并重写。但是,如果我们在这个基类中添加了一个walk()方法,因为有些鸟类不能够行走,所以它们必须空实现这个方法。这就违反了LSP,因为这个基类不能够被所有的子类无歧义地替换。

正例

一个符合LSP的例子是一个基类Shape,其中有一个getArea()方法,这个方法可以被所有的形状类继承并重写。如果我们在Shape基类中添加了一个getColor()方法,因为颜色对于形状而言是没有意义的,所以它们可以空实现这个方法,这样所有的子类都可以无歧义地替换基类。

简单来说就是:

如果我们需要在所有形状中添加颜色有关的方法,但是部分子类不需要实现这个方法。可以定义为有方法体的方法(在接口中叫默认方法),这样子类不要强制实现这个方法,保证所有的子类都可以继承父类。

//父类
class Shape {
    //父类的方法
    public double getArea() {
        return 0.0;
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}

这里,Shape类作为一个基类,定义了一个getArea()方法,CircleRectangle都继承了Shape类,并且重写了getArea()方法。这样,我们可以定义一个Shape类型的变量,并将它赋值为一个CircleRectangle对象,因为它们都是Shape类型的子类,并且可以无歧义地替换基类。

特点

  1. 里氏替换原则强调子类必须完全实现父类的(抽象)方法。如果子类不能完全实现基类的方法,那么它们应该抛出一个异常,或者定义一个默认的实现(比如接口中的默认方法)。
  2. LSP支持多态,可以在程序中使用父类对象代替子类对象,提高代码的可扩展性和可维护性。

注意事项(*重点)

  1. LSP 原则是维护面向对象系统的正确性和稳定性的重要手段,但是在实际开发过程中,需要权衡不同原则的利弊,合理选择实现方式。
  2. 在使用 LSP 原则时,需要对系统进行全面的分析和设计,充分考虑各种可能的情况,以确保系统的正确性和稳定性。