设计模式-里氏替换原则(Liskov Substitution Principle, LSP)


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

核心思想 :子类必须能够替换其父类,且替换后程序的正确性不受影响。
核心目标:确保继承关系的合理性,避免子类破坏父类的行为契约。


原理详解

  1. 行为兼容性

    • 子类的方法输入参数应比父类更宽松(前置条件不能更强)。
    • 子类的方法返回值应比父类更严格(后置条件不能更弱)。
    • 子类不应修改父类方法的预期行为(如抛出父类未声明的异常)。
  2. 契约设计

    • 父类定义行为规范(如接口或抽象类),子类实现需遵守这些规范。
    • 客户端代码应仅依赖父类抽象,而非具体子类实现。

应用案例

案例1:几何图形计算(经典反例)
错误设计(违反LSP)
java 复制代码
class Rectangle {
    protected int width;
    protected int height;

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

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width); // 强制宽高相等,破坏父类行为
    }

    @Override
    public void setHeight(int height) {
        super.setHeight(height);
        super.setWidth(height); // 同上
    }
}

// 客户端代码
public void calculateArea(Rectangle rectangle) {
    rectangle.setWidth(5);
    rectangle.setHeight(4);
    System.out.println(rectangle.getArea()); // 预期 20,但传入 Square 时输出 16
}

问题:

  • Square 修改了 Rectangle 的 setWidth 和 setHeight 的预期行为。
  • 客户端依赖 Rectangle 的规范,但 Square 破坏了该规范。
正确设计(遵循LSP)
java 复制代码
interface Shape {
    int getArea();
}

class Rectangle implements Shape {
    private int width;
    private int height;

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

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

class Square implements Shape {
    private int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    public int getArea() { return side * side; }
}

// 客户端代码
public void calculateArea(Shape shape) {
    System.out.println(shape.getArea());
}

优势:

  • 通过接口 Shape 定义统一契约,子类独立实现。
  • 客户端无需关心具体类型,仅依赖抽象。

案例2:支付系统设计
错误设计(违反LSP)
java 复制代码
abstract class Payment {
    public abstract void pay(double amount);
}

class CreditCardPayment extends Payment {
    @Override
    public void pay(double amount) { /* 信用卡支付逻辑 */ }
}

class CouponPayment extends Payment {
    @Override
    public void pay(double amount) {
        throw new UnsupportedOperationException("优惠券支付不支持金额修改"); // 违反父类契约
    }
}

// 客户端代码
Payment payment = new CouponPayment();
payment.pay(100); // 抛出异常,破坏预期行为

问题:

  • CouponPayment 的 pay 方法抛出父类未声明的异常,客户端无法安全替换。
正确设计(遵循LSP)
java 复制代码
interface Payable {
    void pay(double amount) throws PaymentException; // 明确声明可能异常
}

class CreditCardPayment implements Payable {
    @Override
    public void pay(double amount) { /* 正常支付逻辑 */ }
}

class CouponPayment implements Payable {
    @Override
    public void pay(double amount) throws PaymentException {
        if (amount != 0) {
            throw new PaymentException("优惠券金额不可修改");
        }
        // 使用固定金额支付
    }
}

// 客户端代码
public void processPayment(Payable payment, double amount) {
    try {
        payment.pay(amount);
    } catch (PaymentException e) {
        // 统一处理异常
    }
}

优势:

  • 子类 CouponPayment 明确声明异常,客户端可预期并处理。
  • 所有实现类均遵守 Payable 接口的契约。

LSP 实践指南

  1. 优先组合而非继承 :
    通过组合和接口实现多态,避免继承带来的耦合风险。
  2. 单元测试验证 :
    对父类编写测试用例,确保子类通过所有父类测试。
  3. 避免重写非抽象方法 :
    子类不应修改父类已实现的方法逻辑。
  4. 使用设计模式 :
    通过 策略模式 、模板方法模式 等实现行为扩展。

违反 LSP 的典型场景

场景 后果 修复方案
子类抛出父类未声明的异常 客户端无法处理意外异常 子类捕获异常或父类声明通用异常
子类修改父类方法返回值 客户端逻辑错误(如空指针) 子类返回值兼容父类(更具体或相同类型)
子类强化前置条件 客户端需额外处理子类限制 父类定义更宽松的前置条件

总结

里氏替换原则是面向对象设计的基石之一,强调 子类必须无缝替换父类。通过接口定义行为契约、避免继承滥用、编写兼容性测试,可有效提升代码的健壮性和可维护性。

相关推荐
Zane199418 小时前
同一段单例代码,为什么在高并发下偶尔会创建出两个实例?聊聊哪种单例实现才是真的安全
设计模式
Carl_奕然18 小时前
【智能体】Loop 的四种设计模式之:Agent Loop(2026 最新版)
人工智能·设计模式·语言模型
小酒星小杜19 小时前
画 AI 漫画,别只会写“日漫风”:10 种画风、适用故事和可复制提示词
人工智能·设计模式·程序员
sarasuki1 天前
为什么 Agent 工具需要一个「协议」而不是「框架」:MCP 成为标准的底层逻辑
设计模式·agent·mcp
vivo互联网技术1 天前
知识不是文件,也不是向量 | KDC 系列 02
人工智能·设计模式·架构
Zane19947 天前
一个 new 就能创建对象,为什么还要拆出单例、工厂、建造者、原型四种模式?
设计模式
怕浪猫8 天前
一行命令复刻爆款视频,我把 Hypit 从安装跑到了出片
人工智能·设计模式·程序员
Zane19948 天前
函数式编程里的函数,其实不是你天天写的那个函数——三大编程范式的边界在哪
设计模式
Zane19949 天前
策略模式现在该不该上?一次讲清楚过度设计和设计不足怎么找平衡
设计模式
她说..9 天前
常见设计模式-模板方法模式
java·spring·设计模式·springboot