贯穿设计模式第四话--里氏替换原则
🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳
从今天开始,将开启一个专栏,
【贯穿设计模式】,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。为了能更好的设计出优雅的代码,为了能更好的提升自己的编程水准,为了能够更好的理解诸多技术的底层源码, 设计模式就是基石,万丈高楼平地起,一砖一瓦皆根基。 ✨✨欢迎订阅本专栏✨✨
🥺 本人不才,如果文章知识点有缺漏、错误的地方 🧐,也欢迎各位人才们评论批评指正!和大家一起学习,一起进步! 👀
❤️ 愿自己还有你在未来的日子,保持学习,保持进步,保持热爱,奔赴山海! ❤️
💬 最后,希望我的这篇文章能对你的有所帮助! 🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!
📃 前言回顾
🔥【贯穿设计模式】第一话·设计模式初介绍和单一职责原则🔥
🔥【贯穿设计模式】第二话·设计模式的七大原则之开闭原则🔥
🔥【贯穿设计模式】第三话·设计模式的七大原则之依赖倒转🔥
在第三篇文章中,我们了解设计模式的七大原则中第三个原则:依赖倒转原则;
我们来回顾下,它的定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类。
并且我们通过上学时每个学期可能上课的不同导致如果需要再学习一门新课程的需求导致代码的修改等问题,值得注意的是:在实现依赖倒转原则时,需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入其他对象中。依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。而常用的注入方式有3种:接口注入、构造注入和Setter注入。
⭐ 前提需要
在面向对象的语言中,继承是必不可少的,它主要有以下几个优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的可重用性;
- 提高代码的可扩展性;
- 提高产品或项目的开放性。
相应的,继承也存在缺点,主要体现在以下几个方面:
- 继承是入侵式的。只要继承,就必须拥有父类的所有属性和方法;
- 降低代码的灵活性。子类必须拥有父类的属性和方法,使子类受到限制;
- 增强了耦合性。当父类的常量、变量和方法修改时,必须考虑子类的修改,这种修改可能造成大片的代码需要重构。
- 从整体上看,继承的“利”大于“弊”,然而如何让继承中“利”的因素发挥最大作用,同时减少“弊”所带来的麻烦,这就需要引入“里氏替换原则”。
🍊 里氏替换原则
今天我们学习的是里氏替换原则,任何基类可以出现的地方,子类一定可以出现。
🫓 概述
-
该原则是指任何基类可以出现的地方,子类一定可以出现,即所有引用基类的地方都必须能够透明的使用其子类;里氏替换原则是继承与复用的基石,只有当子类可以替换掉基类,且系统的功能不受影响时,基类才能被复用,而子类也能够在基础类上增加新的行为;所以里氏替换原则指的是任何基类可以出现的地方,子类一定可以出现;
-
里氏替换原则是对开闭原则的补充,实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范;
-
简单理解就是子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,如果子类强制要重写父类的方法,那么可以再抽象一个基类,为他们的公共父类,或采用依赖、组合、聚合的方式来实现;
-
比如有一个基类A有个实现了的方法,其子类B、C等都可以完全替换A类来实现,但不能影响原有的代码功能,如果子类B、C需要重写父类的方法的话,就会导致子类B、C不能完全替换基类A来使用了,此时应该可以再抽象一个基类,为他们的公共父类,或采用依赖、组合、聚合的方式来实现。
🧇 特点
里氏替换原则是实现开闭原则的重要方式之一,通过里氏替换可以使系统有以下优点 :
- 解决了继承中重写父类造成的可复用性变差的问题;
- 是动作正确性的保证,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性;
- 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
🧀 问题引出

在上Java相关课程时,学习面向对象的时候,老师都会提动物、猫、狗之间的关系,都会说他们之间的抽象关系或者继承关系,那接下来就以动物的例子来形象的讲解里氏替换原则吧。
1. 定义一个鸟类Bird:
对于鸟类来说,我们对此的第一印象就是鸟类都是能飞的。
package com.ygt.principle.lsp;/*** 新建一个鸟类* 鸟类有个方法是可以非的方法*/
public class Bird {// 鸟的名字private String name;// 构造方法public Bird(String name) {this.name = name;}public void fly(){System.out.println(this.name + "开始飞啦~~~");}
}
2. 定义一个百灵鸟类Lark并继承鸟类的飞行的方法:
package com.ygt.principle.lsp;/*** 创建一个百灵鸟类,继承鸟类*/
public class Lark extends Bird{public Lark(String name) {super(name);}
}
3. 建立一个测试类LiskovSubstitutionTest测试一下百灵鸟的飞行:
package com.ygt.principle.lsp;/*** 测试里氏替换原则*/
public class LiskovSubstitutionTest {public static void main(String[] args) {// 创建百灵鸟,并让其有飞的动作Lark lark = new Lark("百灵鸟");lark.fly();}
}
4. 得到的结果:
百灵鸟开始飞啦~~~

5. 现在新建一个鸵鸟类Ostrich并继承鸟类:
package com.ygt.principle.lsp;/*** 建立一个鸵鸟类,并继承鸟类*/
public class Ostrich extends Bird{public Ostrich(String name) {super(name);}
}
6. 测试鸵鸟的飞行功能:
package com.ygt.principle.lsp;/*** 测试里氏替换原则*/
public class LiskovSubstitutionTest {public static void main(String[] args) {// 创建百灵鸟,并让其有飞的动作Lark lark = new Lark("百灵鸟");lark.fly();// 创建鸵鸟,测试其飞行功能Ostrich ostrich = new Ostrich("鸵鸟");ostrich.fly();}
}
7. 得到的结果:
百灵鸟开始飞啦~~~
鸵鸟开始飞啦~~~

可是现在有个问题,我们知道普遍的鸟类动物都是善于飞翔的,但是也有些鸟类是不会飞行的,就如鸵鸟、企鹅一般的鸟类是不会飞行的,那此时我们在代码中将其继承鸟类,是不合理的,因为如果要在鸵鸟类中重写修改飞行方法的话,这就务必导致违反了里氏替换原则了,我们将不能使用鸵鸟类来替换成鸟类来使用了。下面我们就一起来看看解决方案吧。
🍕 解决方案
在上面的时候说过,当然我们也可以重写飞行的方法,使鸵鸟的飞行功能是无的,但是这就破坏了里氏替换原则,也会导致整个系统的可复用性变差;这时常用的解决方案就是取消原来的继承关系,重新设计他们之间的关系,即使原来的父类(鸟类)和子类(鸵鸟类)都继承一个更通俗的基类(动物类),这样原来的继承关系去掉,最后采用依赖,聚合,组合等关系代替。
1. 定义一个动物类Animal:
package com.ygt.principle.lsp;/*** 定义一个动物类*/
public class Animal {// 动物的名称public String name;public Animal(String name) {this.name = name;}
}
2. 修改鸟类、鸵鸟类和测试类:
package com.ygt.principle.lsp;/*** 新建一个鸟类* 鸟类有个方法是可以非的方法* 修改如下,继承自动物类*/
public class Bird extends Animal {public Bird(String name) {super(name);}public void fly(){System.out.println(super.name + "开始飞啦~~~");}
}
package com.ygt.principle.lsp;/*** 建立一个鸵鸟类,并继承鸟类* 修改如下,不继承鸟类,改继承动物类*/
public class Ostrich extends Animal{// 如果还想使用鸟类的属性以及方法,可以采用依赖方式private Bird bird;public Ostrich(String name) {super(name);}public void run(){System.out.println(name + "开始奔跑****");}
}
package com.ygt.principle.lsp;/*** 测试里氏替换原则*/
public class LiskovSubstitutionTest {public static void main(String[] args) {// 创建百灵鸟,并让其有飞的动作Lark lark = new Lark("百灵鸟");lark.fly();// 创建鸵鸟,测试其飞行功能Ostrich ostrich = new Ostrich("鸵鸟");ostrich.run();}
}
得到的结果:
百灵鸟开始飞啦~~~
鸵鸟开始奔跑****

这样我们通过使原来的鸟类和鸵鸟都继承一个新的基类Animal类后,这就排除了修改鸟类中的飞行方法了,如果还需要使用鸟类中的功能,可以通过依赖、聚合,组合等关系代替。
而我们在实际编程中,常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。
🌸 完结
相信各位看官看到这里大致都对设计模式中的其中一个原则有了了解吧,里氏替换原则指任何基类可以出现的地方,子类一定可以出现,即所有引用基类的地方都必须能够透明的使用其子类,里氏替换原则是继承与复用的基石,里氏替换原则是对实现抽象化的具体步骤的规范。
学好设计模式,让你感受一些机械化代码之外的程序设计魅力,也可以让你理解各个框架底层的实现原理。最后,祝大家跟自己能在程序员这条越走越远呀,祝大家人均架构师,我也在努力。 接下来期待第五话:接口隔离原则。 💪💪💪
文章的最后来个小小的思维导图:

🧐 本人不才,如有什么缺漏、错误的地方,也欢迎各位人才们评论批评指正!🤞🤞🤞
🤩 当然如果这篇文章确定对你有点小小帮助的话,也请亲切可爱的人才们给个点赞、收藏下吧,非常感谢!🤗🤗🤗
🥂 虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!✨✨✨
💟 感谢各位看到这里!愿你韶华不负,青春无悔!让我们一起加油吧! 🌼🌼🌼
💖 学到这里,今天的世界打烊了,晚安!🌙🌙🌙
相关文章:
贯穿设计模式第四话--里氏替换原则
🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳 从今天开始,将…...
6501: 鸡兔同笼
描述 一个笼子里面关了鸡和免子(鸡有两只脚,兔子有4只脚,没有例外)。已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至多有多少只动物。 输入 一个正整数a(a<32768)。 输出 包含两个正整数,第一个是最少的动物数,第二个是最多的…...
Linux项目自动化构建工具-make/makefile 介绍及使用
使用背景 在工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义一系列 规则来指定什么文件需要先编译,什么文件需要后编译,哪些文件需要重新编译,或者更复杂 的功能操作 makefile带来的好处…...
【云原生|Docker】06-dokcerfile详解
目录 前言 Dockerfile基础示例 Dockerfile简介 1. Dockerfile概念 2. Dokcer镜像分层理解 3. Doker build构建原理 Dockerfile参数解析 1. Dokcerfile组成 2. 指令说明 2.1 FROM引入基础镜像 2.2 LABEL 2.3 ENV 2.4 RUN 2.5 COPY 2.6 ADD 2…...
【SCL】博图——先入先出排序法
使用博图SCL语言来实现先入先出排序 前言 使用SCL完成一个先入先出排序 具体要求:最先输入的一个数值,最先输出出来,下面的数自动向前填充; 注:这里可能有两种理解:一是第一个输入的第一个出来ÿ…...
OSPF----特殊区域
目录 OSPF----特殊区域 第一大类----末梢区域(Stub Area) 完全末梢区域((Totally Stub Area) 第二大类特殊区域----非完全末梢区域(NSSA) OSPF----特殊区域 第一大类----末梢区域(Stub Area)…...
JVM-类加载
1:类加载机制: 加、验、准、解、初、使、卸 加、烟、准、姐、初、湿、鞋 加载、将class 文件转化为二进制流加载 JVM 内存中并生成一个该类的Class对象验证、Class 文件的字节流中包含的信息是否符合当前虚拟机的要求准备、在方法区中分配这些变量所…...
超详细讲解C语言文件操作!!
超详细讲解C语言文件操作!!什么是文件文件名文件的打开和关闭文件指针文件的打开和关闭文件的顺序读写文件的随机读写fseekftellrewind文本文件和二进制文件文件读取结束的判定文件缓冲区什么是文件 磁盘上的文件是文件。但是在程序设计中,我…...
linxu学习之进程
文章目录进程程序和进程产生进程销毁进程多进程高并发设计孤儿僵尸守护进程孤儿进程:守护进程(重点)僵尸进程:进程 程序和进程 操作系统可以运行多个程序,那他是如何运行的?实际上,CPU的执行是很快的,而待…...
蓝桥杯真题2
[蓝桥杯 2013 省 B] 连号区间数 题目描述 小明这些天一直在思考这样一个奇怪而有趣的问题: 在 111 ~ NNN 的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是: 如果区间 [L,R][L, R][L,R] 里的所有元素(即此排列的…...
PWM互补输出,以及死区时间计算
本文基于野火例程进行解说 实验内容 本次实验输出一对互补的pwm波,且进行死区时间的计算说明。 代码 互补输出对应的定时器初始化代码: bsp_advance_tim.c /********************************************************************************* fi…...
基于深度学习的海洋动物检测系统(Python+YOLOv5+清新界面)
摘要:基于深度学习的海洋动物检测系统使用深度学习技术检测常见海洋动物,识别图片、视频和实时视频中的海洋动物,方便记录、展示和保存结果。本文详细介绍海洋动物检测系统,在介绍算法原理的同时,给出Python的实现代码…...
C# 计算方差
50,100,100,60,50 计算他们的方差 为了计算这些数的方差,需要进行以下步骤: 1. 计算平均值,即将这些数相加,然后除以它们的数量。 平均值 (50 100 100 60 50) / 5 72 2. 计…...
HJZS电源监视继电器HJZS-E202 AC220V
系列型号: HJZS-E202断电延时继电器 HJZS-E002断电延时继电器 一 应用 HJZS-E202电源监视继电器用于直流或交流操作的各种保护和自动控制的装置中,用以增加触点数量。 二 安装结构 导轨安装9壳体结构,具体尺寸参阅外型尺寸图。 三 产品型号…...
dolphinscheduler 2.0.6 资源中心改造方案二:通过NFS挂载共享目录
目录调度资源中心存储概要安装NFS服务器客户端调度验证关闭SFTP开关(可忽略)重新上传资源文件worker执行任务验证服务器woker客户端worker其它nfs共享目录的配置文件/etc/exports说明调度资源中心存储概要 针对现有的单机存储可以做哪些扩展?…...
基于集成学习的用户流失预测并利用shap进行特征解释
基于集成学习的用户流失预测并利用shap进行特征解释 小P:小H,如果我只想尽可能的提高准确率,有什么好的办法吗? 小H:优化数据、调参侠、集成学习都可以啊 小P:什么是集成学习啊,听起来就很厉害的…...
【Java版oj 】 day17杨辉三角形的变形、计算某字符出现次数
目录 一、杨辉三角形的变形 (1)原题再现 (2)问题分析 (3)完整代码 二、计算某字符出现次数 (1)原题再现 (2)问题分析 (3)完整代…...
智能驾驶芯片赛道混战:如何看待5类玩家的竞争格局?
智能驾驶芯片赛道,一直是业内关注的焦点。 高工智能汽车注意到,针对L0-L2,业内基本采用智能前视一体机(IFC)方案;要实现高速NOA、城市NOA等更为高阶的智驾功能等,则基本采用域控制器方案。从IF…...
vue antd table表格的增删改查(三)input输入框根据关键字模糊查询【后台管理系统 使用filter与indexOf嵌套】
vue antd table表格的增删改查(三)input输入框根据关键字查询【后台管理系统filter与indexOf嵌套】知识回调场景复现利用filter和indexOf方法实现模糊查询1.查询对象为单层的数组元素2.查询对象为多层的数组元素(两层为例)3.查询对…...
【计组】性能指标——速度
衡量计算机性能的指标之一——速度,是指计算机执行完所有指令所耗费时间的长短。 一、概念: 引出了如下概念:机器字长:指计算机一次能处理的二进制位数,也就是我们通常说的32位64位计算机中的位。 机器字长决定了计算…...
拆解Meta Ray-Ban同款主控:高通AR1芯片如何让AI眼镜‘听懂’你的手势和眼神?
高通AR1芯片如何赋能Meta Ray-Ban:从异构计算到交互革命 当你的眼镜能读懂眼神、响应手势,甚至预判你的需求时,科技与日常的边界便被重新定义。Meta Ray-Ban智能眼镜之所以成为现象级产品,核心秘密藏在仅指甲盖大小的高通AR1芯片中…...
嵌入式系统代码执行时间测量方法与优化
1. 嵌入式程序运行时间测量的必要性在嵌入式系统开发中,精确测量代码执行时间是每个工程师必备的技能。无论是优化算法效率、调试实时系统,还是验证硬件性能,时间测量都扮演着关键角色。以STM32为例,当我们需要确认一个延时函数是…...
基于Python的多媒体信息共享平台毕业设计源码
博主介绍:✌ 专注于Java,python,✌关注✌私信我✌具体的问题,我会尽力帮助你。一、研究目的本研究旨在设计并实现一个基于Python的多媒体信息共享平台,以满足现代网络环境下多媒体信息传播的需求。具体研究目的如下:构建一个高效、…...
SaaS的末日重构:AI Agent浪潮下的危机与新生
目录 前言 一、 市场恐慌的源头:“软件-PE”的死亡循环 二、 核心重构:AI 将如何改造企业级 SaaS? 2.1 交互层的降维打击:从“点界面”到“说意图” 2.2 流程层的动态重组:从“应用中心”到“工作流中心” 2.3 定…...
EmbeddingGemma-300m部署指南:Ollama镜像+Prometheus监控+日志追踪一体化
EmbeddingGemma-300m部署指南:Ollama镜像Prometheus监控日志追踪一体化 想快速搭建一个功能强大、易于管理的文本向量化服务吗?EmbeddingGemma-300m作为谷歌推出的轻量级嵌入模型,凭借其3亿参数和出色的性能,是构建本地语义搜索、…...
FPGA实战:手把手教你用Vivado的MMCM IP核动态调整ADC采样时钟相位(附仿真避坑指南)
FPGA实战:Vivado MMCM动态相位调整的工程化实现与深度避坑指南 在高速数据采集系统中,ADC采样时钟相位的精确控制往往是决定信号完整性的关键因素。当FPGA工程师发现采样数据存在周期性抖动或眼图闭合时,动态调整时钟相位便成为优化系统性能的…...
WPF进阶:Canvas动态图形绘制与交互实现
1. Canvas动态图形绘制基础 WPF中的Canvas就像一块无限延伸的画布,我们可以在这块画布上自由地绘制各种图形元素。与静态绘制不同,动态绘制的魅力在于图形能够根据用户操作实时变化。我刚开始接触Canvas时,最让我兴奋的就是看到鼠标移动时能实…...
突破试用限制:开源脚本实现IDM无限使用的完整解决方案
突破试用限制:开源脚本实现IDM无限使用的完整解决方案 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script 一、问题引入:IDM用户的痛点与解决…...
VSCode配置PyTorch开发环境:从CUDA版本检查到镜像源加速(附常见报错解决方案)
VSCode配置PyTorch开发环境:从CUDA版本检查到镜像源加速(附常见报错解决方案) 在深度学习领域,PyTorch凭借其动态计算图和易用性已成为研究者和开发者的首选框架。然而,配置PyTorch开发环境时,CUDA版本匹配…...
90% 的开发者都在错误理解 async/await:协程本质与高并发实战指南
90% 的开发者都在错误理解 async/await:协程本质与高并发实战指南 很多人在第一次写 async def await 的时候,心里都暗暗期待:这下代码应该变快了吧? 结果写完一测,单个接口的响应时间和以前同步写法几乎一模一样&…...



