当前位置: 首页 > news >正文

里氏替换原则(LSP)理解

核心记住一句话:基类能够针对所有的子类进行替换

换到软件系统的实现里面就是:在软件系统中,子类应该可以替换任何基类能够出现的位置,并且经过替换以后,代码还能正常工作。

里氏替换原则(LSP)——让代码“讲武德”

生动理解

  1. 别搞“特权”
    父类能做的,子类必须全盘接手,比如:

    • ✅ 父类「鸟」会飞,子类「麻雀」也能飞

    • ❌ 子类「企鹅」继承「鸟」却不会飞(坑爹设计!)

  2. 禁止“魔改”
    子类方法参数要比父类更宽松,返回值要比父类更严格。即是不修改方法的核心功能、不加强输入约束、不削弱输出保证。

  3. 异常不乱抛
    子类不新增父类未声明的异常类型。

1、3两点好理解,重点是第二点提及的不加强输入约束,不削弱输出保证是什么意思。这里举一个不加强输入约束的例子。

// 父类:动物可以吃任何食物
class Animal {
    void eat(Food food) { // 接受广义的Food类型
        System.out.println("动物吃食物");
    }
}

// 子类:狗只能吃狗粮(违反LSP)
class Dog extends Animal {
    @Override
    void eat(DogFood food) { //看似很优雅的设计实现,整体体现多态的特性 但是违反LSP 输入约束更严格(参数类型特化)
        System.out.println("狗吃狗粮");
    }
}

为什么违反LSP?

  1. 前置条件被加强

    • 父类契约eat()方法接受任何Food类型(如Food可以是肉类、蔬菜等)。

    • 子类修改Dog要求参数必须是DogFood(如狗粮是Food的子类)。

    • 结果:原本能处理Food的调用逻辑,在替换为Dog后,如果传入非DogFoodFood类型(如Meat),程序将无法正常工作。

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 多态引用(父类指向子类对象)
        Food meat = new Meat();    // 创建一个肉类食物
        
        animal.eat(meat); // 编译通过,但运行时可能崩溃!
                         // 因为Dog的eat()实际需要DogFood参数
    }
}
  • 预期行为:父类Animaleat()本应处理所有Food类型。

  • 实际行为:子类Dogeat()无法处理Meat,导致运行时错误(如ClassCastException)。

提出一种符合LSP规范的设计方案:使用泛型

// 定义泛型父类,约束食物类型
abstract class Animal<T extends Food> {
    abstract void eat(T food); // 子类可指定具体食物类型
}

// 狗只能吃狗粮(明确声明泛型类型)
class Dog extends Animal<DogFood> {
    @Override
    void eat(DogFood food) { // ✅ 输入类型与父类泛型一致
        System.out.println("狗吃狗粮");
    }
}

优点

  • 调用方在编译时即可明确Dog需要DogFood,避免了运行时错误。

  • 通过泛型参数传递类型信息,符合LSP的隐式契约。

那么不削弱输出保证也好理解了,可以理解为子类方法的返回值或状态变更不能比父类更宽松。

我们考虑状态变更的例子。比如,父类的方法保证在调用后某个字段不为null,而子类的方法可能在调用后该字段仍为null,这违反了后置条件。针对返回值,比如,父类的方法可能返回一个非空集合,而子类的方法返回了可能为空的集合,这也是后置条件更宽松的情况。举个实际场景的例子——使用银行账户,父类的取款方法保证余额不会透支,而子类的方法允许透支,导致状态变更更宽松,这违反了后置条件。

LSP的核心教训

  1. 子类不能“挑食”:若父类方法接受Food,子类必须同样接受所有Food类型。

  2. 泛型是你的朋友:当需要类型特化时,用泛型在编译期约束类型,而非运行时强制检查。

  3. 契约不可违背:子类必须遵守父类方法的“承诺”(参数范围、返回值、异常等),否则继承关系应被质疑。

相关文章:

  • 996引擎-M2设置笔记
  • 一键生成PPT,AI让工作变得轻松高效
  • Mysql存储引擎
  • DeepSeek写弹球打砖块手机小游戏
  • MySQL 存储引擎详解:InnoDB、MyISAM 与 Memory 对比
  • MySQL零基础教程16—表连接进阶
  • 遗传算法基础讲解
  • 软件测试基础:功能测试知识总结
  • 【消息队列】数据库的数据管理
  • 鸿蒙开发 - 键盘避让
  • HRNet的pt模型转rknn并实现前向推理
  • Javase学习复习D11[接口,多态]
  • 基于流量域的数据全链路治理方案:从原理到实践
  • c++面试常考问题之引用与指针的关系,ADD如何用宏写
  • 内核编程八:基于printk宏的pr_* 宏
  • 网络安全中蓝牙攻击有哪些?
  • EasyDSS视频推拉流系统:清理缓存文件时如何确保缓存读写不受影响?
  • 【无标题】FrmImport
  • 最短路算法 dijkstra 从认识到熟练掌握
  • 爬虫:从Chrome浏览器进行抓包详解
  • 岳阳网站优化/移动建站优化
  • 自己做网站兼职/足球积分排行榜最新
  • 菏泽网站建设电话咨询/免费b站推广网站2022
  • 青岛黄岛区做网站设计的/软文营销范文100字
  • 中央人民政府门户网站建设理念/内容营销的4个主要方式
  • 用博客网站做淘宝客/北京seo顾问