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

里氏替换原则Liskov Substitution Principle,LSP

里氏替换原则 的核心思想是:
子类对象必须能够替换掉其父类对象,而程序的逻辑不变。

换句话说,程序中任何使用父类对象的地方,如果替换成其子类对象,程序不应该产生任何错误或异常,行为也应该与预期一致

很多人初学时会有一个误解:“里氏替换不就是说子类继承了父类,所以能用在父类出现的地方吗?” 这其实只对了一半。语法上的可替换(编译不报错)只是最低要求,里氏替换更强调的是行为上的可替换(运行时不出错、逻辑一致)

public class Liskov {public static void main(String[] args) {// TODO Auto-generated method stubA a=new A();System.out.println(a.fun1(2, 3));B b = new B();System.out.println(b.fun1(0, 3));System.out.println(b.fun2(2,2));}}
class A{public int fun1(int num1,int num2) {return num1-num2;	}
}
class B extends A{//无意识重写了fun1,导致修改了原逻辑,无法实现B的对象替换A的对象public int fun1(int num1,int num2) {return num1+num2;}public int fun2(int num1,int num2) {return fun1(num1,num2)+9;}	
}

这里B继承A,所以理论上B的fun1是可以替换A的fun1的,但是显然,直接替换会有逻辑错误,原本的减法变成了加法。显然违背了LSP。
违反LSP的根源往往是紧耦合的继承关系。一个更优的设计是使用组合或者更抽象的接口。

修改后:

public class ImporveLiskov {public static void main(String[] args) {// TODO Auto-generated method stubA a=new A();System.out.println(a.fun1(2, 3));//由于B不在是A的子类,所以调用的时候不会认为b的方法和a方法功能一样B b = new B();System.out.println(b.fun1(0, 3));System.out.println(b.fun3(2,2));}}
interface Base{public int fun1(int num1,int num2);}
class A implements Base{public int fun1(int num1,int num2) {// TODO Auto-generated method stubreturn num1-num2;}
}class B implements Base{//使用组合来实现代码复用private A a=new A();//无意识重写了fun1,导致修改了原逻辑,无法实现B的对象替换A的对象public int fun1(int num1,int num2) {return num1+num2;}public int fun2(int num1,int num2) {return fun1(num1,num2)+9;}public int fun3(int num1,int num2) {return this.a.fun1(num1, num2);}}

此时A和B之间就没有了强制的“is-a”关系,它们只是共享了同一个接口。任何使用 Base 的地方,两者都可以替换,并且行为是符合各自定义的,不会出现意料之外的副作用

一个问题

Q:由于子类往往会重写父类方法,所以违背里氏替换原则。要遵循里氏替换原则,则需要使用组合和更抽象的接口吗?那么这种修改是否违背了继承

要理解这个问题,必须要有如下的认识:

第一个关键点

:不是"重写"本身违背LSP,而是"不恰当的重写"违背LSP。

第二个:关于"修改是否违背继承"的分析

继承的两种用途
1.实现继承(is-a关系) - 子类确实是父类的一种特殊形式
2.代码复用继承 - 仅仅为了复用代码,没有真正的is-a关系

问题的根源

很多违背LSP的情况,根源在于我们错误地使用了"实现继承"来表达"代码复用"的需求。所以,当考虑"使用组合和接口"时,实际上是在说:
“重新审视你的设计意图:你到底是需要真正的is-a关系,还是仅仅需要代码复用?”
如果是真正的is-a关系 → 使用继承,但要确保LSP
如果是代码复用 → 使用组合

所以说说,LSP原则不仅不会违背了继承,这恰恰是更好地理解了继承的本质。
由于传统误解:“继承就是代码复用”,而LSP揭示真相:“继承是行为的契约,代码复用只是副产品”
所以即使是修改为使用组合或者更加抽象的接口,这也不是在违背继承,而是在纠正对继承的误用

例如:
适合使用继承的场景:显然这里使用继承关系描述是很切合的

// 真正的is-a关系,行为一致
class Animal {public void breathe() { ... } // 所有动物都会呼吸
}class Mammal extends Animal { ... } // 哺乳动物确实是动物
class Fish extends Animal { ... }   // 鱼确实是动物

适合组合的场景:这里的Car和Airplane需要使用Engine的功能,复用和重写其代码,但是显然,他们之间使用继承关系描述很不合适,这时候仅仅是为了复用代码,那么通过LSP判断,使用组合更加合适。

// 需要复用功能但行为不同
class Engine { ... }
class Car {private Engine engine; // 汽车有引擎,但汽车不是引擎
}class Airplane {private Engine engine; // 飞机有引擎,但飞机不是引擎
}

所以说:里氏替换存在的意义,就是提醒说,
如果是真正的is-a关系 → 使用继承,但要确保LSP
如果是代码复用 → 使用组合
而如果使用了继承,我们尽量不要在子类中重写父类方法,如果一定要重写,则考虑使用组合,聚合,依赖的方式来解决问题。即通用的做法是,让原本的父类和子类都继承一个更通俗的类,或者实现一个更抽象的接口,将原有的继承关系取消掉,采用使用组合,聚合,依赖等关系替代

http://www.dtcms.com/a/589274.html

相关文章:

  • 享元设计模式
  • VitaBench:智能体在真实交互任务中的挑战与前沿探索
  • 深度学习:python动物识别系统 YOLOv5 数据分析 可视化 Django框架 pytorch 深度学习 ✅
  • 【数据库 | 基础】DDL语句以及数据类型
  • 视觉元素网站浙江建设职业技术学院迎新网站
  • 正规网站建设费用做网站阳泉
  • Java I/O 流详解:字符流与字节流的完整指南
  • STM32外设学习-ADC模数转换器(代码部分)四个模块,光敏,热敏,电位,反射式红外。
  • 公众号开发网站购物网站开发介绍
  • 结构型设计模式2
  • 怎么做ppt教程网站灰色项目源码
  • 蛋白质内在无序区域(IDR)预测benchmark
  • 【TIDE DIARY 7】临床指南转公众版系统升级详解
  • STM32百问百答:从硬件到软件全面解析
  • MyBatis-Plus 框架设计模式全景解析
  • 创建型设计模式1
  • AI大数据在医疗健康中的应用与发展趋势
  • 网站规划与开发实训室建设方案个人如何做微商城网站
  • 标准 Python 项目结构
  • 【开发者导航】面向快速模型演示与轻量交互开发的可视化工具:Gradio
  • Vue 项目实战《尚医通》,首页常见科室静态搭建,笔记16
  • 东莞热的建设网站网页视频怎么下载到电脑上
  • 区块链论文速读 CCF A--USENIX Security 2025(4)
  • PHM数据集轴承寿命预测!Transformer-GRU轴承寿命预测MATLAB代码实现
  • 网站错误代码301建设网站如何给页面命名
  • FreeRTOS 学习:(十六)可调用 FromISR 相关 API 函数的中断优先级范围
  • 织梦网站修改使用教程郑州高端网站建设怎么样
  • GPU集群poc测试
  • 操作系统(12)进程描述与控制--进程概述(2)
  • 数据库库、表的创建及处理