【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"关系。(你就是一个它)
  • 遵循其他设计原则,如单一职责原则、开闭原则和依赖倒置原则。

文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~

希望能和大佬们一起努力,诸君顶峰相见

再次感谢各位小伙伴儿们的支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/122846.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#:枚举是命名的整形常量的集合

在枚举&#xff08;enum&#xff09;中&#xff0c;整形常量指的是一组预定义的整数值&#xff0c;它们代表了枚举类型中的不同取值。这些整数值通常用于表示特定的状态、选项或标识符。 枚举类型是一种自定义的数据类型&#xff0c;它可以用于定义一组相关的命名常量。每个枚…

服务器动态/静态/住宅/原生IP都是什么意思

​  在互联网的世界中&#xff0c;我们经常会听到关于IP地址的各种说法&#xff0c;比如服务器动态IP、静态IP、住宅IP和原生IP。那么这些术语究竟代表着什么意思呢?让我们一起来了解一下。 动态IP 动态IP(Dynamic IP)是指互联网服务提供商(ISP)在每次用户上网时&#xff0c…

Redis中的数据类型以及适用场景

1.Redis中的数据类型 Redis中的数据类型包括&#xff1a;String(字符串&#xff09;、Hash(字典)、List(列表)、Set(集合)、Sorted Set【Zset】(有序集合&#xff09;。 Redis 所有的数据结构都是一个key对应一个value&#xff0c;不同类型的数据结构之间的差异就在于value的…

【微信小程序开发】学习小程序的网络请求和数据处理

前言 网络请求是微信小程序中获取数据和与服务器交互的重要方式。微信小程序提供了自己的API来处理网络请求&#xff0c;使得开发者可以轻松地在微信小程序中实现数据的获取和提交。本文将介绍微信小程序中的网络请求&#xff0c;包括使用wx.request发起GET和POST请求&#xf…

jenkins实践篇(1)——基于分支的自动发布

问题背景 想起初来公司时&#xff0c;我们还是在发布机上直接执行发布脚本来运行和部署服务&#xff0c;并且正式环境和测试环境的脚本都在一起&#xff0c;直接手动操作脚本时存在比较大的风险就是将环境部署错误&#xff0c;并且当时脚本部署逻辑还没有检测机制&#xff0c;…

JVM面试知识点整理

文章目录 (一) JVM组成JVM组成部分和运行流程从图中可以看出 JVM 的主要组成部分运行流程&#xff1a;程序计数器Java堆虚拟机栈方法区堆栈的区别是什么&#xff1f; (二) 类加载器双亲委派模型类装载的执行过程 (三) 垃圾回收对象什么时候可以被垃圾回收哪些可以作为根对象 垃…

【QT】Qt控件不显示图标

问题描述 本人在跟着B站视频学习QT时&#xff0c;遇到了一件十分悲惨的事情&#xff0c;一模一样的步骤&#xff0c;我的图标却不能显示。 于是我上网查询一下解决方案&#xff0c;第一种&#xff0c;亲测没用&#xff1b;第二种亲测可以。 解决方法一 1、构建 -> 清理项目…

ArcGIS Maps SDK for JS:隐藏地图边框

文章目录 1 问题描述2 解决方案 1 问题描述 近期&#xff0c;将ArcGIS Api for JS v4.16更新到了ArcGIS Maps SDK for JS v4.27&#xff0c;原本去除地图的css代码失效了。 v4.26及以前版本 &#xff0c;需要用.esri-view-surface--inset-outline:focus::after 控制边框属性。…

基于docker的Mysql版本升级

引言 随着版本的迭代和漏洞的提出&#xff0c;Mysql作为一款软件避免不了打补丁的命运。 由于docker版本的升级教程较少&#xff0c;以下基于docker版本的MySQL升级说明。 以下操作均在本地虚拟机中操作验证。 使用runlike查看启动脚本 镜像与镜像的差距就在启动命令的区别…

假如我有一台服务器,我会让它提供三种服务

一、提供照片上传、存储和下载服务 随着移动互联网时代的持续快速发展&#xff0c;PC互联网日益势微&#xff0c;各大互联网门户网站的博客、空间也跟着凋零&#xff0c; 作为博客、空间的标配功能的相册也随之被关闭。 2019年3月6日网易相册发布停运公告并于当年5月8日正式停…

基于RK3568高性价比全国产EMS储能解决方案(二)设计方案

目录 版 本 修 订 记 录 1. 产品介绍 1.1. 什么是XM3568-EP 1.2. 产品特点 1.3. 外壳尺寸 1.4. 外壳外观 1.5. 规格参数 2. 设备使用介绍 2.1. 下载需要使用到的驱动和调试工具 2.2. 启动网关 2.3. DEBUG串口的使用方法 2.4. LED指示灯说明 3. Linux系…

0029Java程序设计-家政服务平台管理系统

文章目录 摘要目录系统设计开发环境 摘要 本文首先研究并介绍国内外目前的背景和现状&#xff0c;在此基础上给出论文的主要研究内容&#xff0c;其次&#xff0c;对家政服务平台管理系统的需求进行了分析。再次&#xff0c;对家政服务平台管理系统进行了总体设计&#xff0c;…

【java学习—九】模板方法(TemplateMethod)设计模式(4)

文章目录 1. 在java中什么是模板2. 模板方法设计解决了什么问题&#xff1f;3. 代码化理解 1. 在java中什么是模板 抽象类体现的就是一种模板模式的设计&#xff0c;抽象类作为多个子类的通用模板&#xff0c;子类在抽象类的基础上进行扩展、改造&#xff0c;但子类总体上会保留…

【自动驾驶】Free space与Ray casting

文章目录 1 Free space是什么2 Ray casting是什么3 它俩啥关系4 TODO 1 Free space是什么 在自动驾驶领域&#xff0c;free space即可行驶区域&#xff0c;在结构化道路的十字路口/非结构化道路都有很大作用。 2 Ray casting是什么 ray casting是计算机视觉领域&#xff0c;…

数据结构实验3

1、建立顺序循环队列&#xff0c;并在顺序循环队列上实现入队、出队基本操作。 &#xff08;1&#xff09;根据输入的队列长度n和各元素值建立一个循环顺序表表示的队列&#xff08;循环队列&#xff09;&#xff0c;并输出队列中各元素值。 &#xff08;2&#xff09;将数据…

Jest和Mocha对比:两者之间有哪些区别?

什么是单元测试&#xff1f; 所谓单元测试&#xff0c;是对软件中单个功能组件进行测试的一种软件测试方式&#xff0c;其目的是确保代码中的每一个基本单元都能正常运行。因此&#xff0c;开发人员在应用程序开发的整个过程&#xff08;即代码编写过程&#xff09;中都需要进行…

单例模式及其使用场景

单例模式&#xff08;Singleton&#xff09;&#xff1a;指在一个系统中某个类只存在一个实例&#xff0c;类中自行实例化&#xff0c;实例向该系统提供统一的访问接口。 单例模式有两种表现形式&#xff0c;饿汉式&#xff1a;类加载时&#xff0c;就进行实例化&#xff1b;懒…

【目标检测】Visdrone数据集和CARPK数据集预处理

之前的博文【目标检测】YOLOv5跑通VisDrone数据集对Visdrone数据集简介过&#xff0c;这里不作复述&#xff0c;本文主要对Visdrone数据集和CARPK数据集进行目标提取和过滤。 需求描述 本文需要将Visdrone数据集中有关车和人的数据集进行提取和合并&#xff0c;车标记为类别0&…

在 Windows 用 Chrome System Settings 设置代理

在 Windows 用 Chrome System Settings 设置代理 贴心提示&#xff1a;在设置代理之前&#xff0c;请确保您已经安装了 浏览器。 &#x1f527; 设置代理的详细步骤如下&#xff1a; 打开 浏览器&#xff0c;输入 //settings/system 并回车。 在「系统和网络设置」页面中&am…