整洁架构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可以且应该被应用于软件架构层面,因为一旦违背了可替换性,该系统架构就不得不为此增添大量复杂的应对机制。

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

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

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

相关文章

Meta MobileLLM

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

鸿蒙开发—基础组件

目录 安装介绍 1.Text 2.Image 3.Button 4.Slider 安装介绍 该文章介绍鸿蒙开发中的部分基础组件,适用于鸿蒙开发的初学者。 1.软件下载地址:DevEco Studio-HarmonyOS Next Beta版-华为开发者联盟 (huawei.com) 2.安装DevEco Studio:…

最新版智能修图-中文luminar ai 1.55(13797) 和 neo1.20,支持m芯片和intel芯片(绝对可用)

Luminar AI for macOS 完整版本 这个程序是第一个完全由人工智能驱动的图像编辑器。有了它,创建引人注目的照片是有趣的,令人惊讶的容易。它是一个独立的照片编辑器和macOS插件。 Luminar AI for macOS 轻轻地塑造和完善一个肖像打造富有表现力的眼睛…

增加内容曝光、获得更多粉丝 - 「评论发红包」功能

目录 博客发放以及领取红包规则 1. 发布博客评论社区红包规则: 2. 博客评论红包领取规则 如何发红包评论? 发布红包评论益处 不知道大家有没有注意到,我们的「评论发红包」功能已经上线啦~ 现在几乎所有的内容 -- 博客&…

TCP连接的三次握手和断开的四次挥手

TCP连接的建立过程通过三次握手完成,‌而连接的关闭过程则通过四次挥手完成。‌ 三次握手:‌这是TCP连接建立的过程,‌主要目的是确保双方都准备好进行数据传输。‌具体步骤如下:‌ 客户端向服务器发送一个SYN报文,‌请…

独立开发者系列(24)——使用redis

(一)REdis的使用原理 在早期的网站的时候,如果系统本身功能不是很复杂,比如就是内部的几个用户使用,而且基本就是汇报一点简单的设备维护信息,还有日常公告。完全可以不使用数据库,直接使用jso…

IoTDB 集群高效管理:一键启停功能介绍

如何快速启动、停止 IoTDB 集群节点的功能详解! 在部署 IoTDB 集群时,对于基础的单机模式,启动过程相对简单,仅需执行 start-standalone 脚本来启动 1 个 ConfigNode 节点和 1 个 DataNode 节点。然而,对于更高级的分布…

02:项目二:感应开关盖垃圾桶

感应开关盖垃圾桶 1、PWM开发SG901.1、怎样通过C51单片机输出PWM波?1.2、通过定时器输出PWM波来控制SG90 2、超声波测距模块的使用3、感应开关盖垃圾桶 需要材料: 1、SG90舵机模块 2、HC-SR04超声波模块 3、震动传感器 4、蜂鸣器 5、若干杜邦线 1、PWM开…

7、y0usef

难度-低 局域网靶机地址发现 端口服务扫描 通过目录扫描发现adminstration目录,但是访问发现提升没有权限 尝试通过添加请求头X-Forwarded-For: http://127.0.0.1 成功绕过 访问发现是一个登录框 尝试admin admin发现成功登录。。。 发现文件上传功能点 尝试进…

JavaWeb后端学习

Web:全球局域网,万维网,能通过浏览器访问的网站 Maven Apache旗下的一个开源项目,是一款用于管理和构建Java项目的工具 作用: 依赖管理:方便快捷的管理项目以来的资源(jar包)&am…

鸿蒙系统在服装RFID管理中的应用:打造智能零售新时代

​随着物联网技术的迅速发展,服装零售行业正面临着新的变革与挑战。鸿蒙系统作为新一代智能操作系统,结合RFID技术,为服装行业提供了高效、智能的管理解决方案。常达智能物联,作为RFID技术的领先企业,致力于将鸿蒙系统…

Linux的世界 -- 初次接触和一些常见的基本指令

一、Linux的介绍和准备 1、简单介绍下Linux的发展史 1991年10月5日,赫尔辛基大学的一名研究生Linus Benedict Torvalds在一个Usenet新闻组(comp.os.minix)中宣布他编制出了一种类似UNIX的小操作系统,叫Linux。新的操作系统是受到另一个UNIX的…

jenkins系列-04-jenkins参数化构建

使用maven build之前,先checkout 指定分支或标签: 拖拽调整顺序:shell执行在前,构建在后: gitee新建标签tag:

代理模式(大话设计模式)C/C++版本

代理模式 C #include <iostream> using namespace std;class Subject // Subject 定义了RealSubject和Proxy的共用接口..这样就在任何使用RealSubject的地方都可以使用Proxy { public:virtual void func(){cout << "Subject" << endl;} };class R…

头歌资源库(29)流水线最优调度

一、 问题描述 二、算法思想 这是一个经典的作业调度问题&#xff0c;可以使用动态规划来解决。 首先&#xff0c;我们可以将每个任务定义为一个节点&#xff0c;图中的边表示任务的先后顺序。根据题目的要求&#xff0c;每个任务必须先在印刷车间进行印刷&#xff0c;然后…

prometheus+grafana应用监控配置

配置Prometheus 官方地址&#xff1a;Download | Prometheus &#xff08;wegt下载压缩包&#xff0c;解压并重命名prometheus&#xff0c;文件放于/data/prometheus即可&#xff09; 配置 service方法(文件放于 /etc/systemd/system/prometheus.service)&#xff1a; [Unit…

k8s(四)---node

四、node node就是节点 1.查看node&#xff08;查询集群状态&#xff09; kubectl get no状态为kubec Ready 可以查看更多信息&#xff1a;-owide kubectl node -owide node没有命名空间隔离&#xff0c;所以node不需要指定命名空间 此处是一个master节点、两个worker节点、状态…

Neo4j:图数据库的革命性力量

Neo4j 首席技术官 prathle 撰写了一篇出色的博文&#xff0c;总结最近围绕 GraphRAG 的热议、我们从一年来帮助用户使用知识图谱 LLM 构建系统中学到的东西&#xff0c;以及我们认为该领域的发展方向。Neo4j一时间又大火起来&#xff0c;本文将带你快速入门这神奇的数据库。 前…

NLP之词的重要性

文章目录 何为重要词TF*IDFTF*IDF其他版本TFIDF 算法特点TF*IDF的优势TF*IDF劣势 TF*IDF的应用搜索引擎文本摘要文本相似度计算 上一篇文章介绍了新词的发现&#xff0c;用内部凝固度和左右熵来发现新词。这时候机器对一篇文章有了对词的一定理解&#xff0c;这时我们让机器上升…

Prometheus 云原生 - 微服务监控报警系统 (Promethus、Grafana、Node_Exporter)部署、简单使用

目录 开始 Prometheus 介绍 基本原理 组件介绍 下文部署组件的工作方式 Prometheus 生态安装&#xff08;Mac&#xff09; 安装 prometheus 安装 grafana 安装 node_exporter Prometheus 生态安装&#xff08;Docker&#xff09; 安装 prometheus 安装 Grafana 安装…