设计模式-里氏替换原则(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 修改了 RectanglesetWidthsetHeight 的预期行为。
  • 客户端依赖 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); // 抛出异常,破坏预期行为

问题

  • CouponPaymentpay 方法抛出父类未声明的异常,客户端无法安全替换。
正确设计(遵循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 的典型场景

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

总结

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

相关推荐
灰子学技术1 小时前
Envoy 使用的设计模式技术文档
设计模式
Carl_奕然10 小时前
【智能体】Agent的四种设计模式之:ReAct
人工智能·设计模式·语言模型
二哈赛车手12 小时前
新人笔记---多策略搭建策略执行链实现RAG检索后过滤
java·笔记·spring·设计模式·ai·策略模式
楼田莉子12 小时前
仿Muduo的高并发服务器:Channel模块与Poller模块
linux·服务器·c++·学习·设计模式
geovindu1 天前
go: Strategy Pattern
开发语言·设计模式·golang·策略模式
嵌入式学习_force1 天前
02_state
设计模式·蓝牙
qcx232 天前
Warp源码深度解析(七):Token预算策略——双轨计费、上下文溢出与摘要压缩
人工智能·设计模式·rust·wrap
Cosolar2 天前
提示词工程面试题系列 - Zero-Shot Prompting 和 Few-Shot Prompting 的核心区别是什么?
人工智能·设计模式·架构
geovindu2 天前
go:Template Method Pattern
开发语言·后端·设计模式·golang·模板方法模式
钝挫力PROGRAMER2 天前
贫血模型的改进
java·开发语言·设计模式·架构