里氏替换原则 Java代码示例
里氏替换原则的本质就是为了多态
里氏替换原则(LSP)的核心确实是为了实现“安全的多态”。
详细解释
1. 多态的前提
- 多态(Polymorphism)是面向对象编程的三大特性之一。
- 多态的实现前提是:父类引用可以指向子类对象,并且调用方法时,实际执行的是子类的实现。
2. 里氏替换原则的作用
- 里氏替换原则要求:子类对象必须能够替换父类对象,且程序逻辑不出错。
- 这样,无论父类引用指向哪个子类对象,程序都能正常运行,这就是多态的安全基础。
3. 如果不遵守LSP
- 如果子类不能完全替代父类(比如重写方法时行为不一致、抛异常等),
- 那么多态就会失效,程序会出错,面向对象的灵活性和扩展性也会大打折扣。
总结
- 里氏替换原则的本质,就是保证多态的正确性和安全性。
- 只有遵守LSP,才能让“父类引用指向子类对象”时,程序行为始终正确,真正发挥多态的威力。
## 一、什么是里氏替换原则?
**定义**:
子类对象必须能够替换父类对象,且程序逻辑不出错。
也就是说,**父类能用的地方,子类也能用,且行为一致**。
---
## 二、不遵守LSP的坏处(反例)
### 1. 代码示例
```java
// 父类:鸟
class Bird {
public void fly() {
System.out.println("鸟在飞");
}
}
// 子类:鸵鸟
class Ostrich extends Bird {
@Override
public void fly() {
// 鸵鸟不会飞
throw new UnsupportedOperationException("鸵鸟不会飞!");
}
}
public class LSPBadDemo {
public static void letBirdFly(Bird bird) {
bird.fly();
}
public static void main(String[] args) {
Bird sparrow = new Bird();
letBirdFly(sparrow); // 正常输出
Bird ostrich = new Ostrich();
letBirdFly(ostrich); // 运行时抛异常
}
}
```
### 2. 坏处分析
- **父类引用指向子类对象时,行为不一致**,导致程序出错。
- 破坏了多态的初衷,**调用者无法安全地使用父类引用**。
- 代码可维护性差,后续扩展容易出bug。
- 违背了“对扩展开放,对修改关闭”的设计原则。
---
## 三、遵守LSP的好处(正例)
### 1. 代码示例
#### 方法一:用接口区分能力
```java
// 飞行接口
interface Flyable {
void fly();
}
// 鸟类
class Bird {}
// 麻雀会飞
class Sparrow extends Bird implements Flyable {
@Override
public void fly() {
System.out.println("麻雀在飞");
}
}
// 鸵鸟不会飞
class Ostrich extends Bird {
// 没有fly方法
}
public class LSPGoodDemo {
public static void letItFly(Flyable f) {
f.fly();
}
public static void main(String[] args) {
Flyable sparrow = new Sparrow();
letItFly(sparrow); // 正常输出
// Ostrich 不能传给 letItFly,编译期就能发现问题
// letItFly(new Ostrich()); // 编译报错
}
}
```
#### 方法二:用组合而非继承
```java
class Bird {
// 公共属性
}
class FlyAbility {
public void fly() {
System.out.println("会飞");
}
}
class Sparrow extends Bird {
private FlyAbility flyAbility = new FlyAbility();
public void fly() {
flyAbility.fly();
}
}
class Ostrich extends Bird {
// 没有fly方法
}
```
### 2. 好处分析
- **父类能用的地方,子类都能用,且行为一致**,多态安全。
- **调用者不用担心子类行为异常**,代码健壮性高。
- **扩展新类型时,不会破坏原有代码**,易于维护和扩展。
- **编译期就能发现问题**,而不是运行时才出错。
---
## 四、总结
| 不遵守LSP(坏处) | 遵守LSP(好处) |
|--------------------------|--------------------------|
| 运行时容易出错 | 多态安全,行为一致 |
| 维护困难,扩展易出bug | 易于维护和扩展 |
| 调用者无法信任父类引用 | 调用者可以放心用父类引用 |
| 破坏面向对象设计原则 | 遵循设计原则,代码健壮 |
---