干货!终于有人把设计模式的 “里式替换原则”讲清楚了

jboss-remoting服务 依赖的jar: org.jboss.remoting jboss-remoting 2.5.3 concurrent concurrent 1.3.4 jboss jboss-serialization 4.2.2.GA 服务端代码: import java.net.MalformedURLExcept 阅读详情

前言

​我们学习了 SOLID 原则中的单一职责原则和开闭原则。今天,我们再来学习 SOLID 中的 “L” 对应的原则:里式替换原则。

整体上来讲,这个设计原则是比较简单、容易理解和掌握的。今天我主要通过几个反例,带你看看,哪些代码是违反里式替换原则的?我们该如何将它们改造成满足里式替换原则?除此之外,这条原则从定义上看起来,跟我们之前讲过的 “多态” 有点类似。

如何理解 “里式替换原则”?

里式替换原则的英文翻译是:Liskov Substitution Principle,缩写为 LSP。这个原则最早是在 1986 年由 Barbara Liskov 提出,他是这么描述这条原则的:

If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。

在 1996 年,Robert Martin 在他的 SOLID 原则中,重新描述了这个原则,英文原话是这样的:

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。

我们综合两者的描述,将这条原则用中文描述出来,是这样的:子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

这么说还是比较抽象,我们通过一个例子来解释一下。如下代码中,父类 Transporter 使用 org.apache.http 库中的 HttpClient 类来传输网络数据。子类 SecurityTransporter 继承父类 Transporter,增加了额外的功能,支持传输 appId 和 appToken 安全认证信息。


public class Transporter {

  private HttpClient httpClient;



  public Transporter(HttpClient httpClient) {

    this.httpClient = httpClient;

  }

  public Response sendRequest(Request request) {

    // ...use httpClient to send request

  }

}

public class SecurityTransporter extends Transporter {

  private String appId;

  private String appToken;

  public SecurityTransporter(HttpClient httpClient, String appId, String appToken) {

    super(httpClient);

    this.appId = appId;

    this.appToken = appToken;

  }

  @Override

  public Response sendRequest(Request request) {

    if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) {

      request.addPayload("app-id", appId);

      request.addPayload("app-token", appToken);

    }

    return super.sendRequest(request);

  }

}

public class Demo {

  public void demoFunction(Transporter transporter) {

    Reuqest request = new Request();

    //... 省略设置 request 中数据值的代码...

    Response response = transporter.sendRequest(request);

    //... 省略其他逻辑...

  }

}

// 里式替换原则

Demo demo = new Demo();

demo.demofunction(new SecurityTransporter(/* 省略参数 */););

在上面的代码中,子类 SecurityTransporter 的设计完全符合里式替换原则,可以替换父类出现的任何位置,并且原来代码的逻辑行为不变且正确性也没有被破坏。

不过,你可能会有这样的疑问,刚刚的代码设计不就是简单利用了面向对象的多态特性吗?多态和里式替换原则说的是不是一回事呢?从刚刚的例子和定义描述来看,里式替换原则跟多态看起来确实有点类似,但实际上它们完全是两回事。为什么这么说呢?

我们还是通过刚才这个例子来解释一下。不过,我们需要对 SecurityTransporter 类中 sendRequest () 函数稍加改造一下。改造前,如果 appId 或者 appToken 没有设置,我们就不做校验;改造后,如果 appId 或者 appToken 没有设置,则直接抛出 NoAuthorizationRuntimeException 未授权异常。改造前后的代码对比如下所示:

// 改造前:

public class SecurityTransporter extends Transporter {

  //... 省略其他代码..

  @Override

  public Response sendRequest(Request request) {

    if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) {

      request.addPayload("app-id", appId);

      request.addPayload("app-token", appToken);

    }

    return super.sendRequest(request);

  }

}

// 改造后:

public class SecurityTransporter extends Transporter {

  //... 省略其他代码..

  @Override

  public Response sendRequest(Request request) {

    if (StringUtils.isBlank(appId) || StringUtils.isBlank(appToken)) {

      throw new NoAuthorizationRuntimeException(...);

    }

    request.addPayload("app-id", appId);

    request.addPayload("app-token", appToken);

    return super.sendRequest(request);

  }

}

在改造之后的代码中,如果传递进 demoFunction () 函数的是父类 Transporter 对象,那 demoFunction () 函数并不会有异常抛出,但如果传递给 demoFunction ()函数的是子类 SecurityTransporter 对象,那 demoFunction () 有可能会有异常抛出。尽管代码中抛出的是运行时异常(Runtime Exception),我们可以不在代码中显式地捕获处理,但子类替换父类传递进 demoFunction 函数之后,整个程序的逻辑行为有了改变。

虽然改造之后的代码仍然可以通过 Java 的多态语法,动态地用子类 SecurityTransporter 来替换父类 Transporter,也并不会导致程序编译或者运行报错。但是,从设计思路上来讲,SecurityTransporter 的设计是不符合里式替换原则的。

回顾

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解 design by contract,按照协议来设计” 这几个字。父类定义了函数的 “约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的 “约定”。

这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

更多java原创阅读:https://javawu.com

[ESP32]:基于esp-modbus实现serial主机 阅读详情

相关推荐

NBU计算机大二下期末

计算机网络 任课老师:JG (放了一学期的他自己的视频和上了一学期网课没什么区别,甚至不如上网课。。。) 考试题型 填空题10*1 判断题10*1 选择题30*1 简答和计算题30(4题) 讨论题20 强烈建议不要再这门课上花太多时间复习,不然有复习了个寂寞的感觉。。。计网是一页开卷的,一页开卷纸不用写太多,第一他不会考太详细;第二反正最后都要被收走。 填空题、判断题基本看懂了英文就能做的差不多;选择题去网上找点英文题多刷一下问题不大;简答题把书上每章根据他上课的内容在开卷纸上抄下就好,今年是解释check

weixin_45816954的博客 710

使用多态替代条件判断----示例

当存在这样一类条件表达式,它根据对象类型的不同选择不同的行为。可以将这种表达式的每个分支放进一个子类内的复写函数中,然后将原始函数声明为抽象函数。 public int calculate(int a, int b, String operator) { int result = Integer.MIN_VALUE; if ("add".equals(operator)) { result = a + b; } else if ("multiply".equal

yqh的博客 839

IO多路转接 ——— select、poll、epoll

select是系统提供的一个多路转接接口。select函数的函数原型如下: 参数说明:参数timeout的取值:返回值说明:select调用失败时,错误码可能被设置为:fd_set结构与sigset_t结构类似,fd_set本质也是一个位图,用位图中对应的位来表示要监视的文件描述符。 调用select函数之前就需要用fd_set结构定义出对应的文件描述符集,然后将需要监视的文件描述符添加到文件描述符集当中,这个添加的过程本质就是在进行位操作,但是这个位操作不需要用户自己进行,系统提供了一组专门的接口,用

2021dragon的博客 7979

设计模式:策略模式和状态模式

策略模式和状态模式策略模式状态模式 目前看了一些设计模式,但是还有的记不住,现在感觉策略模式与状态模式非常像,于是想写点东西. 首先看了一下菜鸟上的说明策略模式和状态模式因此想做一下分析,深入理解一下两个模式的区别. 策略模式 首先我们可以看一下什么是策略模式,我总结了以下几点: 1)需要针对不同的环境,使用不同的策略 2)不同的策略中实现了不同的方法 3)将策略传入环境中,策略中的方法去解决环境中的问题 基于上面的几个点,我们来写一下实现方法 /* 策略基类,里面有一个策略方法 */ public cl

zhuang12346413的博客 411

如何构建高质量的 QA 问答知识库

NLP 技术落地最普遍的场景就是智能客服,如阿里的”小蜜“,京东的“JIMI”等等。NLP 技术也广泛应用于搜索系统。无论是“智能客服”,还是“搜索系统”,构建的前提是拥有“高质量的QA知识库”。今天聊聊“如何构建高质量QA知识库。”。 历史对话数据分布分析 基于公司现有系统积累的大规模对话文本数据,需要对文本数据进行归类处理,分析每个部分数据的占比,挖掘出有价值的文本通过 NLP 相关技术进行商业价值转化。下文以医疗领域来举例说明,通过输入对象,可以将文本分为客服输入和用户输入两部分。挖掘相关问题,主要从

Jiassheng的博客 2153

Buck-Boost电路

前面我们学习了降压的Buck电路 (左图) 和升压的Boost电路 (右图),那么如果我们把Buck电路和Boost电路结合一下会发生什么情况? 我们尝试着画一下: 我们用Buck电路作为输入,然后用Boost电路进行调整,最后输出。因此Buck电路中的负载、Boost电路的电源都可以删掉。得到下图: 由于Buck电路中的C2的作用是在输出的时候滤波,这个功能已经由Boost电路中的C1实现了,因此可以删除Buck电路中的C2电容。得到下图: 这里两个电感处于串联状态,可以合二为一,得到下面的电路:

wangeil007的博客 4万+

用多态和组合替换多个条件

这是一种众所周知的重构模式,可以将条件条件替换为多态性。 如果您不熟悉该模式,可以在此处查看 。 但是,一旦该类中有多个条件检查所基于的字段,该基本解决方案便会开始崩溃。 我们将研究一些有关如何使用这些可能性的想法。 有很多方法可以解决,因此我们将从最简单到最困难的工作,始终使用简单的示例来尽可能减少混乱。 那么,最简​​单的情况是什么? 看一看: public class Clas...

最佳 Java 编程 712

ANSYS 2026 R1 Windows系统安装避坑指南:环境配置、模块部署与许可证实战

ANSYS作为主流CAE仿真平台,其安装部署已远超传统软件范畴,演变为涉及操作系统底层、GPU驱动兼容性、许可证微服务架构和统一缓存机制的系统工程。理解ANSYS安装原理,需从Windows 11 UEFI安全启动要求、DirectX 12 Ultimate图形栈依赖、gRPC架构的License Manager端口动态分配等核心机制切入;技术价值在于保障HFSS GPU加速、Fluent MPI并行稳定性及Workbench多模块协同可靠性;典型应用场景涵盖高校仿真实验室升级、工业设计院所平台重建及高性能

weixin_29251337的博客 1431

封装继承多态_用多态和组成替换多个条件

封装继承多态 用多态替换条件语句是一种众所周知的重构模式。 如果您不熟悉该模式,可以在此处查看。 但是,一旦在类中存在多个条件检查所基于的字段,该基本解决方案便会开始崩溃。 我们将研究一些有关如何使用这些可能性的想法。 有很多方法可以解决,因此我们将从最简单到最困难的工作,始终使用简单的示例,以尽可能减少混乱。 那么,最简​​单的情况是什么? 看一看: public class ClassW...

cunfen8879的博客 205

【STM32 CubeMX】I2C中断方式与DMA方式

在STM32 CubeMX环境中,I2C(Inter-Integrated Circuit)通信协议的实现可以采用中断方式或DMA(Direct Memory Access)方式。中断方式通过触发中断来处理数据传输,而DMA方式则利用直接内存访问技术来实现数据传输,减轻了CPU的负担。在选择I2C通信方式时,开发者需要根据具体的应用需求和性能要求来选择合适的方式。STM32 CubeMX提供了图形化配置工具,使得配置I2C通信方式变得更加便捷。

m0_62599305的博客 7749

高通GAIA V3命令参考手册的研读学习(15):自定制命令的详细工作描述

里的示例一样,手机APP发出这个GetManuFacturer的命令发0x0000,那么耳机回复:0x8000, 同时在回复的playload部分,用最多15个字节的ACSII码来表达制造商信息(因此最多15个字母)。那么这个APP在通过GAIA协议控制这个耳机时,不仅可以通过前面讲过的vendor ID来识别,也可以更进一步通过这个命令来识别这个耳机,到底是不是本公司的。通过这个可变长度的playload,真的是可以传达极多的事先约定的信息,相当的灵活,真是不错!这个命令用来得到设备的制造商信息。

qq_37191547的博客 399

必会重构技巧:使用多态替换条件

使用多态替换条件:指在进行类型检查和执行某些类型操作时,最好将算法封装在类中,并且使用多态来对代码中的调用进行抽象   举例理解:看定义可能比较迷糊,其实说的简单一点,对于使用分支语句并且分支条件是和类型检查相关的程序段,如 if(type == typeof(TypeA)){...}else if(type == typeof(TypeB)){...},可以把{...}中的Cod...

weixin_30652491的博客 353

基于CANopen协议,实现机器人500-1000Hz高频控制(附实操实例) (1)

基于CANopen协议实现机器人500-1000Hz高频控制,核心不是“协议本身的性能极限”,而是“通过优化配置、硬件适配、软件调优,最大化发挥协议的实时性优势”。关键在于:以PDO为核心实现实时数据传输,以SYNC帧锁定高频同步周期,以精简冗余、优化硬件降低总线负载,同时通过实操调试解决周期漂移、延迟、干扰等问题。

start_up_go的博客 93

小酌重构系列[14]——使用多态代替条件判断

概述 有时候你可能会在条件判断中,根据不同的对象类型(通常是基类的一系列子类,或接口的一系列实现),提供相应的逻辑和算法。当出现大量类型检查和判断时,if else(或switch)语句的体积会比较臃肿,这无疑降低了代码的可读性。另外,if else(或switch)本身就是一个“变化点”,当需要扩展新的对象类型时,我们不得不追加if else(或switch)语句块,以及相应的逻辑,...

dengjie0448的博客 311

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

从java多态到策略模式_设计模式中的多态——策略模式详解

2. 策略模式详解2.1 策略模式定义策略模式定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户端而独立的变化。可以使用多态进行类比来理解策略模式的定义。一系列算法可以理解成接口的不同实现类,因为不同实现类都实现了相同的接口,因而它们也可以相互替换。策略模式让算法独立于客户端而变化与接口的实现类可以独立于使用接口的客户端变化类似。2.2 策略模式的...

weixin_34229076的博客 361

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

ElasticSearch学习 - (六)TransportClient的使用

前提 /** * ElasticSearch客户端 */ @Autowired private TransportClient client; 索引 判断索引是否存在 public boolean isIndexExist(String index) { return client.admin().indices().prepareE...

GoodIdea 4728

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

建议104:用多态代替条件语句

假设开发自动驾驶系统。在设计之初,自动驾驶系统拥有一个驾驶系统命令的枚举类型: enum DriveCommand { start, stop } 又假设有一个驾驶的方法处理车辆接收到的指令。一开始我们这样编码: static void Main(string[] args) { DriveCommand comand=DriveCommand.Start; Drive(comand); comand=DriveCommand.Stop; ...

AI浩 3828

多态练习

描述交通工具轮船、巴士、飞机运输客人的方式,数量                        (Java文件截图)首先是写一个运输的父类Transport.java,源码为package com.imooc;public class Transport { public int nums=0; public String name=""; public String method=""; pub...

Owen的博客 432

js 使用多态替换条件语句_用多态和组成替换多个条件

js 使用多态替换条件语句 用多态替换条件语句是一种众所周知的重构模式。 如果您不熟悉该模式,可以在此处查看 。 但是,一旦类中有多个条件检查所基于的字段,该基本解决方案便会开始崩溃。 我们将研究一些有关如何使用这些可能性的想法。 有很多方法可以解决,因此我们将从最简单到最困难的工作,始终使用简单的示例来尽可能减少混乱。 那么,最简​​单的情况是什么? 看一看: public clas...

最佳 Java 编程 605

JAVA使用Transport类发送邮件

        使用Authenticator类的情况:当直接使用Transport的无参方法connect或者是Transport的静态方法直接发送邮件时,若服务器需要认证信息者则这两个方法都不能提供,因此要在创建Session对象时提供了Authenticator的认证信息,则就可以直接使用了。 通过使用Authenticator类的方式向邮件服务器提供认证信息可以按照以下步骤和思路进行编写...

江南雨 1万+

【设计模式】面向对象 - 多态 ( 面向对象 3 要素 | 多态 提升 程序扩展性 | 多态示例 )

一、面向对象 3 要素、 二、多态 ( 提升程序扩展性 )、 三、不使用多态示例、 四、使用多态示例、 总结、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1426

一起写RPC框架(十)RPC服务提供端二--服务的编织和注册

首先先进行服务的编织,将一个服务的一些基本信息编织成一个类,发送给注册中心,订阅者在注册中心取到的编织信息就可以调用该方法,这是整体的思路,我们在网络篇说过,所有的数据传输走RemotingTransporter,核心的传输主体要实现CommonCustomBody接口,接下来,我们就定义Provider发送给注册的类: 编织的服务信息基本信息应该有 1)服务的IP地址 2)端口号

BazingaLyncc 3406

如何修改代码又不违背开闭原则呢?看这里

前言 看了前一篇重构之后的代码,你可能还会有疑问: 在添加新的告警逻辑时,改动二(添加新的 handler 类)是基于扩展而非修改的方式来完成的,但改动一、三、四貌似不是基于扩展而是基于修改的方式来完成的, 那改动一、三、四不就违背了开闭原则吗? public class Alert { // 代码未改动... } public class ApiStatInfo {// 省略 constructor/getter/setter 方法 private String api; priva

dashengjava的博客 824

Java程序员必备奇淫巧技篇(上)

前言 前面我们讲了Java编程过程中的命名和注释、代码风格,今天我们来讲一些比较实用的Java编程技巧,帮你切实地提高代码可读性。这部分技巧比较琐碎,也很难罗列全面,我仅仅总结了一些我认为比较关键的,更多的技巧需要你在实践中自己慢慢总结、积累。 1. 把代码分割成更小的单元块 大部分人阅读代码的习惯都是,先看整体再看细节。所以,我们要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节,让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性。不过,只有代码逻辑比较复杂的时候,我们其

dashengjava的博客 575

一篇讲透Java重构之道

前言 “重构” 这个词对于大部分工程师来说都不陌生。实际上大部分人都只是 “听得多做得少”,真正进行过代码重构的人不多,而把持续重构作为开发的一部分的人,就更是少之又少了。 一方面,重构代码对一个工程师能力的要求,要比单纯写代码高得多。重构需要你能洞察出代码存在的坏味道或者设计上的不足,并且能合理、熟练地利用设计思想、原则、模式、编程规范等理论知识解决这些问题。 另一方面,很多工程师对为什么要重构、到底重构什么、什么时候重构、又该如何重构等相关问题理解不深,对重构没有系统性、全局性的认识,面对一堆烂代码,没

dashengjava的博客 418

异常这样处理,对用户更友好

在项目中,经常有一些业务需要抛出异常,但是如果后台直接抛出throw new Exception的话,前端就很难看,对用户提示也不够友好,今天我们就来解决这个问题。 先建立一个工程,模拟将异常抛出。如下: @RestController public class DemoController { @GetMapping("test") public String test() throws Exception{ if(true){ throw n

dashengjava的博客 396
上一篇: 过滤器、拦截器入门,看这一篇就够了
下一篇: 如何修改代码又不违背开闭原则呢?看这里
大盛玩java
博客等级 码龄5年 0粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值