整洁架构SOLID-里氏替换原则(LSP)

文章目录

  • 定义
  • LSP继承实践
    • 正例
    • 反例
  • LSP软件架构实践
    • 反例
  • 小结

定义

1988年,Barbara Liskov在描述如何定义子类型时写下了这样一段话:

这里需要的是一种可替换性:如果对于每个类型是S的对象o1都存在一个类型为T的对象o2,能使操作T类型的程序P在用o2替换o1时行为保持不变,我们就可以将S称为T的子类型。

这段话中所体现的设计理念,就是里氏替换原则(LSP)。

上述定义可以总结为:

  1. 衍生类不要修改基类的行为,保证基类与衍生类的可替换性
  2. 基类型中应该只提供尽量少的必需的行为,而且不针对这些行为进行任何实现
  3. 只要有可能,不要从具体类继承,而应该由抽象类继承或由接口实现

LSP继承实践

正例

假设我们有一个License类,其结构如下图所示。该类中有一个名为calcFee()的方法,该方法将由Billing应用程序来调用。而License类有两个“子类型”:PersonalLicense与BusinessLicense,这两个类会用不同的算法来计算授权费用。

在这里插入图片描述

上述设计是符合LSP原则的,因为Billing应用程序的行为并不依赖于其使用的任何一个衍生类。也就是说,这两个衍生类的对象都是可以用来替换License类对象的。

反例

正方形/长方形问题是一个著名的违反LSP的设计案例,结构图如下所示:
在这里插入图片描述

在这个案例中,Square类并不是Rectangle类的子类型,因为Rectangle类的高和宽可以分别修改,而Square类的高和宽则必须一同修改。由于User类始终认为自己在操作Rectangle类,因此会带来一些混淆。例如在下面的代码中:

Rectangle r = …r.setW(5);
r.setW(5);

r.setH(2);
assert(r.area()== 10);

很显然,如果上述代码在…处返回的是Square类,则最后的这个assert是不会成立的。

如果想要防范这种违反LSP的行为,唯一的办法就是在User类中增加用于区分Rectangle和Square的检测逻辑(例如增加if语句)。但这样一来,User类的行为又将依赖于它所使用的类,这两个类就不能互相替换了。

LSP软件架构实践

我们的普遍认知正如上文所说,认为LSP只不过是指导如何使用继承关系的一种方法,然而随着时间的推移,LSP逐渐演变成了一种更广泛的、指导接口与其实现方式的设计原则。

这里提到的接口可以有多种形式:

  • 可以是Java风格的接口,具有多个实现类。
  • 可以像Ruby一样,几个类共用一样的方法签名。
  • 甚至可以是几个服务响应同一个REST接口。

LSP适用于上述所有的应用场景,因为这些场景中的用户都依赖于一种接口,并且都期待实现该接口的类之间能具有可替换性。

想要从软件架构的角度来理解LSP的意义,最好的办法还是来看一个反面案例。

反例

假设我们现在正在构建一个提供出租车调度服务的系统。在该系统中,用户可以通过访问我们的网站,从多个出租车公司内寻找最适合自己的出租车。当用户选定车子时,该系统会通过调用restful服务接口来调度这辆车。

接下来,我们再假设该restful调度服务接口的URI被存储在司机数据库中。一旦该系统选中了最合适的出租车司机,它就会从司机数据库的记录中读取相应的URI信息,并通过调用这个URI来调度汽车。

也就是说,如果司机Bob的记录中包含如下调度URI:

purplecab.com/driver/Bob

那么,我们的系统就会将调度信息附加在这个URI上,并发送这样一个PUT请求:

purplecab.com/driver/Bob
    /pickupAddress/24 Maple St.
    /pickupTime/153
    /destination/ORD

这意味着所有参与该调度服务的公司都必须遵守同样的REST接口,它们必须用同样的方式处理pickupAddress、pickupTime和destination字段

接下来,我们再假设Acme出租车公司现在招聘的程序员由于没有仔细阅读上述接口定义,结果将destination字段缩写成了dest。

这会对系统的架构造成什么影响呢?

显然,我们需要为系统增加一类特殊用例,以应对Acme司机的调度请求。而这必须要用另外一套规则来构建。

最简单的做法当然是增加一条if语句:

if(driver.getDispatchUri().startsWith("acme.com"))…

然而很明显,任何一个称职的软件架构师都不会允许这样一条语句出现在自己的系统中。因为直接将“acme”这样的字串写入代码会留下各种各样神奇又可怕的错误隐患,甚至会导致安全问题。

Acme也许会变得更加成功,最终收购了Purple出租车公司。然后,它们在保留了各自名字的同时却统一了彼此的计算机系统。在这种情况下,系统中难道还要再增加一条“purple”的特例吗?

软件架构师应该创建一个调度请求创建组件,并让该组件使用一个配置数据库来保存URI组装格式,这样的方式可以保护系统不受外界因素变化的影响。例如其配置信息可以如下:
在这里插入图片描述

但这样一来,软件架构师就需要通过增加一个复杂的组件来应对并不完全能实现互相替换的restful服务接口。

整体思想:根据调用方的需求抽象出一套getUrl的接口,对于不同的公司可以实现这个接口。将差异性(变化点)封装,加一个抽象层,磨平差异。

小结

LSP可以且应该被应用于软件架构层面,因为一旦违背了可替换性,该系统架构就不得不为此增添大量复杂的应对机制。

参考内容来源于:《架构整洁之道》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

牛客TOP101:合并k个已排序的链表

文章目录 1. 题目描述2. 解题思路3. 代码实现 1. 题目描述 2. 解题思路 多个链表的合并本质上可以看成两个链表的合并,只不过需要进行多次。最简单的方法就是一个一个链表,按照合并两个有序链表的思路,循环多次就可以了。   另外一个思路&a…

PySide(PyQt),csv文件的显示

1、正常显示csv文件 import sys import csv from PySide6.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QWidgetclass CSVTableWidgetDemo(QMainWindow):def __init__(self):super().__init__()# 创建显示控件self.widget QWidget(self)sel…

improve-前端运行项目内存溢出解决

1.场景 运行项目时,out of memory,内存溢出。导致前端运行需要重启项目。 2.解决 2.1删除缓存 删除依赖包中的cacle临时缓存 2.2 更改项目配置 "scripts": {"serve": "node --max_old_space_size5120 node_modules/vue/cli-s…

C++基础知识:C++内存分区模型,全局变量和静态变量以及常量,常量区,字符串常量和其他常量,栈区,堆区,代码区和全局区

1.C内存分区模型 C程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统进行管理的(在编译器中所书写的代码都会存放在这个空间。) 全局区:存放全局变量和静态变量以及常量 栈区:由编译器自动分…

最优控制公式推导(代数里卡提方程,李雅普诺夫方程,HJB方程)

本文探讨了线性时不变系统(LTI系统)的最优控制问题,特别是线性二次调节器(LQR)问题。通过Hamilton-Jacobi-Bellman (HJB) 方程的推导,求得了系统的最优控制律,并进一步推导了代数里卡提方程&…

书生浦语-大模型平台学习-环境搭建01

任务:完成SSH连接与端口映射并运行hello_world.py 详细步骤详见:https://github.com/InternLM/Tutorial/blob/camp3/docs/L0/Linux/readme.md 1、InternStudio介绍 InternStudio 是大模型时代下的云端算力平台。基于 InternLM 组织下的诸多算法库支持…

Win10+Docker环境使用YOLOv8 TensorRT推理加速

这一部分内容和WSL-Ubuntu20.04环境使用YOLOv8 TensorRT推理加速-CSDN博客 是基本相同的,有细微差别我也会在文中指出来。 1.TensorRTX下载 这里使用Wang-xinyu大佬维护的TensorRTX库来对YOLOv8进行推理加速的演示,顺便也验证一下前面环境配置的成果。 github地址:GitHub -…

推荐系统之MIND用户多兴趣网络

目录 引言MIND算法原理1. 算法概述2. 模型结构3. 多兴趣提取层4. 标签感知注意力层 实践应用应用场景1. 电商平台2. 社交媒体3. 视频流媒体4. 内容分发平台 结论 引言 随着大数据和人工智能技术的快速发展,推荐系统已成为电商平台、社交媒体和内容分发平台的重要组成…

写给大数据开发:为什么我们容易不信任数据

目录 1. 产品经理视角:数据优先级低故事与示例伪代码示例 2. 开发者视角:数据任务缺乏技术挑战故事与示例伪代码示例 3. 测试人员视角:数据的不可见性和逻辑复杂性故事与示例伪代码示例 4. 组织文化视角:缺乏数据意识故事与示例伪…

国外UI设计赏析—汽车行业

国外汽车网页设计界面往往展现出几个显著的优点,这些优点不仅提升了用户体验,还增强了品牌形象与产品吸引力。首先,它们注重界面设计的直观性与互动性,通过高清大图、动态效果以及简洁明了的布局,让用户能够一目了然地…

etime:拓展time

拓展C库的time模块,时间格式转换、代码块计时器。

35+打工人:岁月不是枷锁,是经验的徽章

即将8月份上映的电影《逆行人生》以其独特的视角,深刻揭示了一位45岁程序员面对职场年龄歧视的心酸历程,最终选择投身外卖行业的生存抉择。影片不仅触动了观众的心弦,更映射出当下社会就业市场的一隅现实,尤其是在今年应届毕业生人…

[Spring] Spring Web MVC案例实战

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

Qt会议室项目

在Qt中编写会议室应用程序通常涉及到用户界面设计、网络通信、音频/视频处理等方面。以下是创建一个基本会议室应用程序的步骤概述: 项目设置: 使用Qt Creator创建一个新的Qt Widgets Application或Qt Quick Application项目。 用户界面设计&#xff1…

明日周刊-第16期

最近很想去看一场蔡健雅的演唱会,以前从来没去过演唱会。原先是想把第一次机会留给周杰伦的演唱会,但是周董的票太难抢了。 文章目录 一周热点资源分享言论歌曲推荐 一周热点 一、经济与市场 北京二手房价环比上涨: 6月份,北京二…

【Diffusion学习】【生成式AI】Diffusion Model 原理剖析 (2/4) (optional)【公式推导】

文章目录 影像生成模型本质上的共同目标【拟合分布】Maximum Likelihood Estimation VAE 影像生成模型本质上的共同目标【拟合分布】 Maximum Likelihood Estimation VAE

19.x86游戏实战-创建MFC动态链接库

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

【智能算法改进】改进的麻雀搜索算法及其求解旅行商问题

目录 1.算法原理2.改进点3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】麻雀搜索算法(SSA)原理及实现 2.改进点 改进发现者更新位置 为了使 SSA 算法能够避开向原点收敛的弊端, 将算法向最优位置跳跃的操作转换为向最优位置的移动: X i ,…

[Java IO] 流原理及流的分类

Java IO 流概念 Java IO(输入/输出)流是Java用于处理输入和输出操作的一种方式。 Java IO 系统主要基于流(Stream)的概念,流是一组有序的数据序列,可以是输入流(从数据源读取数据)或…

DP(4) | 0-1背包 | Java | LeetCode 1049, 494, 474 做题总结

1049. 最后一块石头的重量 II 和 LC 416.分割等和子集 类似 思路(我没有思路): 两块石头相撞,这里没有想到的一个点是,相撞的两个石头要几乎相似 以示例1为例,stones [2,7,4,1,8,1],如果从左到…