【23种设计模式】里氏替换原则
个人主页:金鳞踏雨
个人简介:大家好,我是金鳞,一个初出茅庐的Java小白
目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作
我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~
本文来自抖音《IT楠老师》设计模式课程,下面是本人结合原课件的一些学习心得。
一、原理概述
子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。
举一个最简单的例子,就是我们定义了一个函数,它的参数是 XXX(List list),当我们传入ArrayList的对象(它的子类),不会有任何问题!
案例分析
案例一
// 基类:鸟类
public class Bird {public void fly() {System.out.println("I can fly");}
}// 子类:企鹅类
public class Penguin extends Bird {// 企鹅不能飞,所以覆盖了基类的fly方法,但这违反了里氏替换原则public void fly() {throw new UnsupportedOperationException("Penguins can't fly");}
}
企鹅不会飞,所以在企鹅类中重写的fly(),会抛出异常。那么这个子类并不能很好的替代父类,因为它执行fly() 方法会报错!!!
为了遵循LSP,我们可以重新设计类结构,将能飞的行为抽象到一个接口中,让需要飞行能力的鸟类实现这个接口,对于企鹅,就不实现这个方法!
// 飞行行为接口
public interface Flyable {void fly();
}// 基类:鸟类
public class Bird {
}// 子类:能飞的鸟类
public class FlyingBird extends Bird implements Flyable {@Overridepublic void fly() {System.out.println("I can fly");}
}// 子类:企鹅类,不实现Flyable接口!!!
public class Penguin extends Bird {
}
通过这样的设计,我们遵循了里氏替换原则,同时也保证了代码的可维护性和复用性。
案例二
假设我们正在开发一个支持多种数据库的程序,包括MySQL、PostgreSQL和SQLite。我们可以使用里氏替换原则来设计合适的类结构,确保代码的可维护性和扩展性。
首先,我们定义一个抽象的Database基类,它包含一些通用的数据库操作方法,如connect()、disconnect()和executeQuery()。这些方法的具体实现将在子类中完成。
public abstract class Database {public abstract void connect();public abstract void disconnect();public abstract void executeQuery(String query);
}
然后,为每种数据库类型创建一个子类,继承自Database基类。这些子类需要实现基类中定义的抽象方法,并可以添加特定于各自数据库的方法。
public class MySQLDatabase extends Database {@Overridepublic void connect() {// 实现MySQL的连接逻辑}@Overridepublic void disconnect() {// 实现MySQL的断开连接逻辑}@Overridepublic void executeQuery(String query) {// 实现MySQL的查询逻辑}// 其他针对MySQL的特定方法
}public class PostgreSQLDatabase extends Database {// 类似地,为PostgreSQL实现相应的方法
}public class SQLiteDatabase extends Database {// 类似地,为SQLite实现相应的方法
}
这样设计的好处是,我们可以在不同的数据库类型之间灵活切换,而不需要修改大量代码。只要这些子类遵循里氏替换原则,我们就可以放心地使用基类的引用来操作不同类型的数据库。例如:
public class DatabaseClient {private Database database;public DatabaseClient(Database database) {this.database = database;}public void performDatabaseOperations() {database.connect();database.executeQuery("SELECT * FROM users");database.disconnect();}
}public class Main {public static void main(String[] args) {// 使用MySQL数据库DatabaseClient client1 = new DatabaseClient(new MySQLDatabase());client1.performDatabaseOperations();// 切换到PostgreSQL数据库DatabaseClient client2 = new DatabaseClient(new PostgreSQLDatabase());client2.performDatabaseOperations();// 切换到SQLite数据库DatabaseClient client3 = new DatabaseClient(new SQLiteDatabase());client3.performDatabaseOperations();}
}
通过遵循里氏替换原则,我们确保了代码的可维护性和扩展性。如果需要支持新的数据库类型,只需创建一个新的子类,实现Database基类中定义的抽象方法即可。
好了,我们稍微总结一下。虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。
- 多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。
- 里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。
二、哪些代码明显违背了 LSP?
1. 子类覆盖或修改了基类的方法
当子类覆盖或修改基类的方法时,可能导致子类无法替换基类的实例而不引起问题。这违反了LSP,会导致代码变得脆弱和不易维护。
public class Bird {public void fly() {System.out.println("I can fly");}
}public class Penguin extends Bird {@Overridepublic void fly() {throw new UnsupportedOperationException("Penguins can't fly");}
}
在这个例子中,Penguin类覆盖了Bird类的fly()方法,抛出了一个异常。这违反了LSP,因为现在Penguin实例无法替换Bird实例。
2. 子类违反了基类的约束条件
当子类违反了基类中定义的约束条件(如输入、输出或异常等),也会违反LSP。
// 栈
public class Stack {private int top;private int[] elements;public Stack(int size) {elements = new int[size];top = -1;}public void push(int value) {if (top >= elements.length - 1) {throw new IllegalStateException("Stack is full");}elements[++top] = value;}public int pop() {if (top < 0) {throw new IllegalStateException("Stack is empty");}return elements[top--];}
}// 正数的栈
public class NonNegativeStack extends Stack {public NonNegativeStack(int size) {super(size);}@Overridepublic void push(int value) {if (value < 0) {throw new IllegalArgumentException("Only non-negative values are allowed");}super.push(value);}
}
在这个例子中,NonNegativeStack子类违反了Stack基类的约束条件,因为它在push()方法中添加了一个新的约束,即只允许非负数入栈。这使得NonNegativeStack实例无法替换Stack实例而不引发问题,违反了LSP。
正确的写法应该是:
public class NonNegativeStack extends Stack {public NonNegativeStack(int size) {super(size);}@Overridepublic void push(int value) {super.push(value);}// 定义新的约束条件public void pushNonNegative(int value) {if (value < 0) {throw new IllegalArgumentException("Only non-negative values are allowed");}super.push(value);}
}
3. 子类与基类之间缺乏"is-a"关系
当子类与基类之间缺乏真正的"is-a"关系时,也可能导致违反LSP。例如,如果一个类继承自另一个类,仅仅因为它们具有部分相似性,而不是完全的"is-a"关系,那么这种继承关系可能不满足LSP。
为了避免违反LSP,我们需要在设计和实现过程中注意以下几点:
- 确保子类和基类之间存在真正的"is-a"关系。(你就是一个它)
- 遵循其他设计原则,如单一职责原则、开闭原则和依赖倒置原则。
文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~
希望能和大佬们一起努力,诸君顶峰相见
再次感谢各位小伙伴儿们的支持!!!
相关文章:

【23种设计模式】里氏替换原则
个人主页:金鳞踏雨 个人简介:大家好,我是金鳞,一个初出茅庐的Java小白 目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作 我的博客&am…...

嵌入式系统设计师考试笔记之操作系统基础复习笔记一
目录 1、嵌入式软件基础 (1)嵌入式软件的特点: (2)嵌入式软件分类: (3)无操作系统的嵌入式软件的两种实现方式: (4)有操作系统的三大优点&am…...

Unity开发之观察者模式(事件中心)
观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者&#…...

16、window11+visual studio 2022+cuda+ffmpeg进行拉流和解码(RTX3050)
基本思想:需要一个window11 下的gpu的编码和解码代码,逐开发使用,先上个图 几乎0延迟的,使用笔记本的显卡 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.0\extras\demo_suite>deviceQuery.exe deviceQuery.exe Starting...CUDA Device Query (Runtime API…...

【C++笔记】如何用检查TCP或UDP端口是否被占用
一、检查步骤 使用socket函数创建socket_fd套接字。使用sockaddr_in结构体配置协议和端口号。使用bind函数尝试与端口进行绑定,成功返回0表示未被占用,失败返回-1表示已被占用。 二、步骤详解 2.1 socket函数 socket 函数是用于创建套接字的函数&…...

“华为杯”研究生数学建模竞赛2015年-【华为杯】D题:面向节能的单/多列车优化决策问题
目录 摘 要: 一、问题重述 二、模型假设 三、符号说明 四、问题一求解...

『第三章』雨燕栖息地:Swift 开发环境
在本篇博文中,您将学到如下内容: 1. Swift 开发平台2. Swift 集成开发环境 Xcode?3. 原型试验场:Playground4. 另一种尝试:iPad 上的 Swift Playgrounds5. Swift 交互实验室:Swift REPL总结 咫尺春三月,寻常百姓家。为…...

elasticsearch-5.6.15集群部署,如何部署x-pack并添加安全认证
目录 一、环境 1、JDK、映射、域名、三墙 2、三台服务器创建用户、并为用户授权 二、配置elasticsearch-5.6.15实例 1、官网获取elasticsearch-5.6.15.tar.gz,拉取到三台服务器 2、elas环境准备 3、修改elasticsearch.yml配置 4、修改软、硬件线程数 5、修改…...

C++ list 模拟实现
目录 1. 基本结构的实现 2. list() 3. void push_back(const T& val) 4. 非 const 迭代器 4.1 基本结构 4.2 构造函数 4.3 T& operator*() 4.4 __list_iterator& operator() 4.5 bool operator!(const __list_iterator& it) 4.6 T* operator->…...

Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (三)
这是继之前文章: Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (一) Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (二&…...

主流电商平台价格如何高频监测
双十一来临在即,除了商家很兴奋,品牌和消费者同样持续关注,除了关注不同平台的产品上架情况,价格也是这些渠道参与者最为关注的,品牌需要通过掌握各店铺的价格情况,了解市场情况以及各经销商的渠道治理现状…...

Spring关于注解的使用
目录 一、使用注解开发的前提 1.1 配置注解扫描路径 二、使用注解创建对象 2.1 Controller(控制器储存) 2.2 Service(服务储存) 2.3 Repository(仓库储存) 2.4 Component(组件储存) …...

图像处理入门 1(Introduction to image processing)
如何获得一张照片 (How to obtain a photo)? 每次看到一些光学设备的规格介绍的时候,一些专用名词,例如:等效焦距,曝光模式 等 让你一头雾水。爱学习的你一定十分好奇他们是什么意思。每次看到…...

中国大模型开源创新与合作的新篇章 | 2023 CCF中国开源大会
2023年10月21日至22日,由中国计算机学会(CCF)和开放原子开源基金会联合主办的CCF中国开源大会(CCF ChinaOSC)在湖南省长沙市北辰国际会议中心成功召开。此次大会以“开源联合,聚力共赢”为主题,…...

vue项目切换页面白屏的解决方案
问题描述 1、页面切换后白屏,同时切换回上一个页面同样白屏 2、刷新后正常显示 3、有警告:Component inside <Transition> renders non-element root node that cannot be animated 解决方法 <Transition>中的组件呈现不能动画化的非元素根…...

5G vs 4G
5G与4G的关键性能指标对比 指标名称流量密度连接密度空口时延移动性能效指标用户体验速率频谱效率峰值速率4G 参考值0.1 M b i t / s / m 2 Mbit/s/m^2 Mbit/s/m2 1 ∗ 1 0 5 / k m 2 1*10^5/km^2 1∗105/km210ms350km/h1倍10Mbit/s1倍1Gbit/s5G 参考值10 M b i t / s / m 2 M…...

【JavaEE重点知识归纳】第11节:认识异常
目录 一:异常的概念和体系结构 1.概念 2.体系结构 3.异常分类 二:异常的处理 1.防御式编程 2.异常的抛出 3.异常的捕获 4.异常的处理流程 三:自定义异常 一:异常的概念和体系结构 1.概念 (1)在…...

一键自助建站系统api版系统源码
自助建站系统,一建建站系统api版,自动建站 安装推荐php7.2或7.2以下都行 可使用虚拟主机或者服务器进行搭建。 分站进入网站后台 域名/admin 初始账号123456qq.com密码123456 找到后台的网站设置 将主站域名及你在主站的通信secretId和通信secretKey填进去。 即可正常使用 通信…...

全国三维数字化创新设计大赛湖北赛区省赛成功举办
须弥芥子,数字如海。10月14日—15日,2023 年数字科技文化节——第16届全国三维数字化创新设计大赛湖北赛区省赛暨产教联合体大会在武汉软件工程职业学院成功举行。 (大赛全体专家领导合影) 全国三维数字化创新设计大赛组委会副秘…...

OpenCV #以图搜图:均值哈希算法(Average Hash Algorithm)原理与实验
1. 介绍 均值哈希算法(Average Hash Algorithm) 是哈希算法的一种,主要用来做相似图片的搜索工作。 2. 原理 均值哈希算法(aHash)首先将原图像缩小成一个固定大小的像素图像,然后将图像转换为灰度图像&am…...

博通BCM575系列RDMA网卡驱动bnxt_re分析(一)
简介 整个BCM系列驱动分成以太网部分(bnxt_en.ko)和RDMA部分(bnxt_re.ko), 两个模块之间通过内核的auxiliary_bus进行管理.我们主要分析下bnxt_re驱动. 代码结构 这个驱动的核心是 qplib_fp.c, 这个文件主要包含了驱动的数据路径, 包括Post Send, Post Recv, Poll CQ流程的实…...

集合总结-
Collection 常用方法 package com.test01;import java.util.ArrayList; import java.util.Collection; /*添加元素---boolean add(E e);移除元素---boolean remove(Object c);判断元素是否存在---boolean contains(Object c);*/ public class S {public static void main(Str…...

【知识串联】概率论中的值和量(随机变量/数字特征/参数估计)【考研向】【按概率论学习章节总结】
就我的概率论学习经验来看,这两个概念极易混淆,并且极为重点,然而,在概率论的前几章学习中,如果只是计算,对这方面的辨析不清并没有问题。然而,到了后面的参数估计部分,却可能出现问…...

上游服务不可用了,下游服务如何应对?
上游服务不可用了,下游服务如何应对? 引言 在系统中,上游服务和下游服务是两个关键概念。上游服务通常指的是提供某种功能或数据的服务端,它接收来自下游服务的请求,并根据请求进行处理和响应。下游服务通常指的是发…...

WebGL笔记:矩阵的变换之平移的实现
矩阵的变换 变换 变换有三种状态:平移、旋转、缩放。当我们变换一个图形时,实际上就是在移动这个图形的所有顶点。解释 webgl 要绘图的话,它是先定顶点的,就比如说我要画个三角形,那它会先把这三角形的三个顶点定出来…...

XTU-OJ 1187-Candy
WCB某天买了非常多的糖果并把它们分成N份,依次分别有1,2,3…,N个糖果。他想拿出其中的3份分给他的室友, 为了不让室友们闹意见,必须让这三份的糖果总数恰好能被三人均分。请问他一共有多少种不同的组合方案数ÿ…...

基于 nodejs+vue城市轨道交通线路查询系统mysql
目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…...

电商时代,VR全景如何解决实体店难做没流量?
近日,电商和实体经济的对立成为了热门话题,尽管电商的兴起确实对线下实体店造成了一定的冲击,但实体店也不是没有办法挽救。VR全景助力线下实体店打造线上店铺,打通流量全域布局,还能实现打开产品、查看产品内部细节等…...

操作系统-浅谈CPU与内存
目录 计算机的基本组成CPU内存虚拟内存内存分段内存分页 CPU与内存的交互过程高速缓存cache 所有图片均来自:小林coding 计算机的基本组成 计算机由软件和硬件组成 硬件由CPU(中央处理器)存储器(内存外存)外部设备组成。 软件由应用软件和系…...

K8s 部署 CNI 网络组件+k8s 多master集群部署+负载均衡
------------------------------ 部署 CNI 网络组件 ------------------------------ ---------- 部署 flannel ---------- K8S 中 Pod 网络通信: ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器(Pod 内的容器是不会跨宿主机的)共享同一…...