当前位置: 首页 > news >正文

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

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • MySQL表的内连和外连(重点)
  • Java 如何不用再每次新建线程,直接使用公共线程池
  • IoTDB 集群高效管理:一键启停功能介绍
  • 数据结构——排序算法(冒泡、快速、选择、插入)
  • Qt中使用RapidJSON
  • Gitea 仓库事件触发Jenkins远程构建
  • 从零编写一个神经网络完成手写数字的识别分类(pytorch实现)
  • 通过Bugly上报的日志查找崩溃闪退原因
  • Rust: 关于Pin以及move前后分析
  • Python基础-循环语句
  • 安全防御,防火墙配置NAT转换智能选举综合实验
  • 在家上网IP地址是固定的吗?
  • Is Temperature the Creativity Parameter of Large Language Models?阅读笔记
  • GitHub+Picgo图片上传
  • Web学习day04
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • GraphQL学习过程应该是这样的
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • Linux gpio口使用方法
  • vue的全局变量和全局拦截请求器
  • 阿里云Kubernetes容器服务上体验Knative
  • 代理模式
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 面试遇到的一些题
  • 那些年我们用过的显示性能指标
  • 如何优雅地使用 Sublime Text
  • 手写双向链表LinkedList的几个常用功能
  • Prometheus VS InfluxDB
  • ​【已解决】npm install​卡主不动的情况
  • # 透过事物看本质的能力怎么培养?
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (12)Linux 常见的三种进程状态
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (AngularJS)Angular 控制器之间通信初探
  • (day18) leetcode 204.计数质数
  • (备忘)Java Map 遍历
  • (二)测试工具
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (附源码)ssm码农论坛 毕业设计 231126
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (转)【Hibernate总结系列】使用举例
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • .net core 依赖注入的基本用发
  • .NET 常见的偏门问题
  • .NET 某和OA办公系统全局绕过漏洞分析
  • @PreAuthorize注解
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • [2019红帽杯]Snake
  • [Android]Android开发入门之HelloWorld
  • [Android]通过PhoneLookup读取所有电话号码
  • [BZOJ 4129]Haruna’s Breakfast(树上带修改莫队)
  • [C++][基础]1_变量、常量和基本类型
  • [C++]入门基础(1)
  • [CentOs7]搭建ftp服务器(2)——添加用户