1. 引言

编写高质量的面向对象软件并不是一件容易的事情。幸运的是,我们并不是第一批做这件事的人,很多前辈已经总结出了一些原则和技巧,帮助我们写出结构清晰、易于维护的代码。

其中最著名的一组原则就是 SOLID 原则

在本篇简短教程中,我们将重点讲解 SOLID 原则中的 L(Liskov Substitution Principle)里氏替换原则

2. 里氏替换原则的定义

2.1. SOLID 原则概述

SOLID 原则是由著名软件工程师 Robert C. Martin(人称 Uncle Bob)提出并推广的一组面向对象设计原则,用于指导我们写出更健壮、可扩展性强的代码。它包括以下五个原则:

  • Single Responsibility Principle (SRP):单一职责原则
  • Open-Closed Principle (OCP):开闭原则
  • Liskov Substitution Principle (LSP):里氏替换原则
  • Interface Segregation Principle (ISP):接口隔离原则
  • Dependency Inversion Principle (DIP):依赖倒置原则

遵循这些原则可以让我们写出更易维护、更清晰、更具扩展性的代码。

2.2. 里氏替换原则(LSP)

里氏替换原则的核心思想是:

子类应该能够替换其父类而不破坏程序的正确性。

换句话说,如果一个类继承自另一个类,那么在使用父类的地方,用子类来替换应该不会导致行为异常。

举个例子,假设我们有一个 Vehicle 类,然后有两个子类 CarTruck

The Liskov Substitution Principle Base Model

接着,我们有一个 Garage 类,用来修理车辆:

public class Garage {
    public void repair(Vehicle vehicle) {
        // 修理逻辑
    }
}

根据 LSP 的要求,无论我们传入的是 Car 还是 Truck,这个 repair() 方法都应该能正常工作。

但如果某个类的设计违反了 LSP,比如下面这个 CarDriver 类:

public class CarDriver {
    public void drive(Vehicle vehicle) {
        if (!(vehicle instanceof Car)) {
            throw new IllegalArgumentException("只能驾驶汽车");
        }
        // 驾驶逻辑
    }
}

这时候,如果我们传入一个 Truck 实例,就会抛出异常。这就违反了 LSP,因为 CarDriver 无法接受所有 Vehicle 的子类。

这种设计会带来一系列问题,我们将在下一节详细说明。

3. 违反 LSP 带来的后果

3.1. 误导性代码

违反 LSP 最直接的问题就是代码行为与预期不符。

✅ 你可能期望某个方法能处理所有 Vehicle 类型,但实际运行时却抛出异常或行为异常。

⚠️ 更糟的是,这种情况如果没有文档说明,只有在运行时才会暴露,甚至可能在生产环境中才被发现。

3.2. 可读性差的代码

假设我们发现某个方法不能接受所有子类,但又无法修改源码,那就只能在调用前加条件判断:

if (vehicle instanceof Car) {
    carDriver.drive(vehicle);
} else {
    // 处理异常情况
}

这样做的结果是代码中充满了类型判断逻辑,破坏了原本清晰的调用结构。

❌ 这也违背了面向对象设计的一个初衷:通过继承和多态隐藏子类差异。

3.3. 容易出错的代码

更严重的问题是:我们可能根本不知道某个方法不支持某些子类,直到程序运行时报错。

✅ 单元测试可能无法覆盖所有子类组合
❌ 线上环境突然出错,后果可能非常严重

4. 如何正确应用 LSP?

回到之前的例子,问题出在 CarDriver 接受的是 Vehicle,但其实只支持 Car

✅ 正确做法是:直接将 CarDriverCar 关联,而不是与 Vehicle 关联:

The Liskov Substitution Principle CarDriver Good

这样设计后,调用者就无法传入 Truck,从而避免了运行时错误。

5. 总结

里氏替换原则是面向对象设计中的核心原则之一,它的核心思想是:

子类应能替换父类而不破坏程序行为。

违反该原则可能导致:

  • 代码行为与预期不符
  • 强制引入类型判断逻辑,破坏代码结构
  • 潜在运行时错误,影响系统稳定性

✅ 在设计类继承结构时,要时刻考虑子类是否真的能替代父类,否则就应该重新设计继承关系。


原始标题:The Liskov Substitution Principle