里氏替换原则 (Liskov Substitution Principle, LSP)

LSP 是五大 SOLID 原则中的重要组成部分。它们的目标是通过合理的设计和架构,提升系统的可维护性、扩展性和灵活性。


定义

里氏替换原则的核心思想是:子类对象必须能够替换其父类对象,并且不会影响程序的正确性

这意味着子类在扩展父类的功能时,不能改变父类原有的行为。

简单来说,程序中的子类对象应该能在任何父类对象可以出现的地方正常工作。

主要内容

  • 子类应该完全兼容父类,任何使用父类的地方都可以安全地使用子类。
  • 子类在重写父类方法时,不能违反父类的方法行为规范。
  • 子类在继承父类的同时,不应该对父类的功能做破坏性的修改。

好处

  • 增强系统的灵活性:符合 LSP 的子类能够无缝替换父类,使得代码更易于扩展和维护。
  • 提高代码的健壮性:确保子类在继承父类时,不会破坏程序的行为,使得程序更加稳定和可靠。
  • 减少继承带来的副作用:如果子类修改了父类的核心逻辑,可能会带来意料之外的问题。LSP 的遵守可以减少这种副作用。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Bird {
public void fly() {
System.out.println("Bird is flying");
}
}

class Sparrow extends Bird {
@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}

// 违反 LSP 的子类
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Ostrich cannot fly");
}
}

public class Main {
public static void main(String[] args) {
Bird bird1 = new Sparrow();
bird1.fly(); // 输出: Sparrow is flying

Bird bird2 = new Ostrich();
bird2.fly(); // 运行时抛出异常:UnsupportedOperationException
}
}

在上面的代码中,Sparrow 类完全遵循了 LSP,可以无缝替换 Bird 类。

Ostrich 类却破坏了 Bird 的行为,因为鸵鸟不能飞。

这违反了 LSP,因为 Ostrich 类无法在所有 Bird 出现的地方安全地使用,导致程序的运行出现错误。

如何避免 LSP 违反

  • 子类应该尽量保持父类的行为一致,尤其是那些对外暴露的方法。
  • 如果子类需要改变父类行为,应该考虑使用组合而非继承,将不兼容的行为抽象为独立的模块。

总结

  • LSP 强调子类必须可以无缝替换父类,而不改变系统的功能或引发错误。

  • 遵守 LSP 有助于维护代码的正确性和稳定性,确保继承不会带来不必要的风险。