04.里氏替换原则(Liskov Substitution Principle)

2023-12-01 07:12

本文主要是介绍04.里氏替换原则(Liskov Substitution Principle),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

暴论:一般的,如果一个富二代不想着证明自己,那么他一辈子都会衣食无忧。

一言

里氏替换原则想告诉我们在继承过程中会遇到什么问题,以及继承有哪些注意事项。


概述

这是流传较广的一个段子:

“一个坐拥万贯家财的富二代,他可以终日花天酒地,飞扬跋扈,跑车炸街,美女为伴,极尽荒唐之能事。只要他不想着证明自己比父亲强,让父辈的产业按既定的规则运转,那么他将一生衣食无忧。”

在这里插入图片描述
看似戏谑的言论实则透露出的是一种稳健的合理。在父辈足够优秀,后人的能力又并非出类拔萃的情况下,不打破既有的优秀机制无疑是最稳妥的选择。段子归段子,玩笑归玩笑。在软件设计中,我们会经常遇到父类子类的继承关系,这个看似荒唐又合理的原则实际上就是里氏替换原则的精髓

OO中的继承性

继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障。
于是里氏替换原则提供了针对这种问题的规范。

何为里氏替换原则

里氏替换原则在1988年由麻省理工学院的**芭芭拉·里斯克夫(Barbara Liskov)**女士提出。
如果对每个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都代换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
在使用继承时,子类尽量不要重写父类的方法。继承实际让两个类的耦合性增强了,在适当的情况下可以通过聚合、组合、依赖来解决问题。


三寸反骨

我们还是从一个反骨仔的故事开始,为什么里氏替换原则不让重写父类方法?我就要重写父类方法!
反例

public class Story {public static void main(String[] args) {A a = new A();System.out.println("100-50 = "+a.func1(100,50));System.out.println("100-200 = "+a.func1(100,200));System.out.println("-------------------------------");B b = new B();System.out.println("100-50 = "+b.func1(100,50));System.out.println("100-200 = "+b.func1(100,200));System.out.println("(100+200)*10 = "+b.func2(100,200));}
}class A{public int func1(int a, int b){return a-b;}
}class B extends A{public int func1(int a, int b){return a+b;}public int func2(int a, int b){return func1(a, b)*10;}
}

当反骨仔随心所欲的继承重写之后:
在这里插入图片描述
我们发现原本运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。
子类过分的逆反使得代码极难维护,甚至随着代码量的扩张,真的是牵一发而动全身。


克己正心

听人劝,吃饱饭。反骨仔经过实践之后觉得这个继承之后的重写确实要慎重。那么究竟要如何去优化呢?
通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉采用依赖,聚合,组合等关系代替。
在这里插入图片描述
于是经过深思熟虑的代码出现了:

public class Story {public static void main(String[] args) {A a = new A();System.out.println("100-50="+a.func1(100,50));System.out.println("100-200="+a.func1(100,200));System.out.println("---------------------------------");B b = new B();//因为B类不再继承A类,因此调用者,不会在func1求减法System.out.println("100+50="+b.func1(100,50));System.out.println("100+200="+b.func1(100,200));System.out.println("(100+200)*10="+b.func2(100,200));System.out.println("---------------------------------");System.out.println("使用组合依然可以使用到A的方法");System.out.println("100-50="+b.func3(100,50));}
}
class Base{
}
class A extends Base{public int func1(int num1,int num2){return num1-num2;}
}
class B extends Base {private A a = new A();public int func1(int a, int b) {return a + b;}public int func2(int a, int b) {return func1(a, b) * 10;}public int func3(int a, int b) {return this.a.func1(a, b);}
}

我们采用引入基类,子类组合的方式淡化反骨仔的继承关系,进而削弱A、B业务之间的冲突,在一定程度上解耦合。提高了灵活性的同时,也遵循了里氏替换原则。
在这里插入图片描述


里氏替换原则面向类与类之间的继承关系提出了设计规范,在一定程度上规避了业务设计上的杂糅,使得方法在继承关系中更纯粹,也使得设计在扩展方面具有更好的管理性。
事实上,与这个理论很相近的开闭原则才是所有设计的核心。关于开闭原则的拆解我们下次继续!


关注我,共同进步,每周至少一更!——Wayne

这篇关于04.里氏替换原则(Liskov Substitution Principle)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/440274

相关文章

取得 Git 仓库 —— Git 学习笔记 04

取得 Git 仓库 —— Git 学习笔记 04 我认为, Git 的学习分为两大块:一是工作区、索引、本地版本库之间的交互;二是本地版本库和远程版本库之间的交互。第一块是基础,第二块是难点。 下面,我们就围绕着第一部分内容来学习,先不考虑远程仓库,只考虑本地仓库。 怎样取得项目的 Git 仓库? 有两种取得 Git 项目仓库的方法。第一种是在本地创建一个新的仓库,第二种是把其他地方的某个

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

浙大数据结构:04-树7 二叉搜索树的操作集

这道题答案都在PPT上,所以先学会再写的话并不难。 1、BinTree Insert( BinTree BST, ElementType X ) 递归实现,小就进左子树,大就进右子树。 为空就新建结点插入。 BinTree Insert( BinTree BST, ElementType X ){if(!BST){BST=(BinTree)malloc(sizeof(struct TNo

读软件设计的要素04概念的关系

1. 概念的关系 1.1. 概念是独立的,彼此间无须相互依赖 1.1.1. 一个概念是应该独立地被理解、设计和实现的 1.1.2. 独立性是概念的简单性和可重用性的关键 1.2. 软件存在依赖性 1.2.1. 不是说一个概念需要依赖另一个概念才能正确运行 1.2.2. 只有当一个概念存在时,包含另一个概念才有意义 1.3. 概念依赖关系图简要概括了软件的概念和概念存在的理

notepad++ 正则表达式多条件查找替换

基础语法参考: https://www.cnblogs.com/winstonet/p/10635043.html https://www.linuxidc.com/Linux/2019-05/158701.htm   通常情况下我们查找的内容和要被替换掉的内容是一样的,我们只需要使用正则表达式精确框定查找内容,替换直接输入要替换的内容即可。 但有时会比较复杂,查找的内容,只需要替换其中

shell脚本中变量中字符串替换的测试 /和//的区别

test_char=abbbcbbbf echo "bf:test_char = " $test_char test_char=${test_char/bbb/ddd} echo "af:test_char = " $test_char 输出: bf:test_char =  abbbcbbbf af:test_char =  adddcbbbf 只匹配第一个

[苍穹外卖]-04菜品管理接口开发

效果预览 新增菜品 需求分析 查看产品原型分析需求, 包括用到哪些接口, 业务的限制规则 业务规则 菜品名称必须是唯一的菜品必须属于某个分类下, 不能单独存在新增菜品时可以根据情况选择菜品的口味每个菜品必须对应一张图片 接口设计 根据类型查询分类接口 文件上传接口 新增菜品接口 数据表设计 设计dish菜品表 和 dish_fl

【动手学深度学习】04 数据操作 + 数据预处理(个人向笔记)

数据操作 N维数组是机器学习和神经网络的主要数据结构其中 2-d 矩阵中每一行表示每一行表示一个样本 当维度来到三维的时候则可以表示成一张图片,再加一维就可以变成多张图片,再加一维则可以变成一个视频 访问元素 冒号表示从冒号左边的元素到冒号右边的前一个元素(开区间),其中如果左边为空,那么表示从第一个开始,如果右边为空,那么表示访问到最后一个,如果两边都为空,则表示全部访问其中一行中我们指

springboot启动时替换配置参数

SpringBoot启动时配置参数替换 一.背景 SpringBoot项目启动的时候,在不使用配置中心等的前提下或者有公司强制使用指定的“密码箱”情况下,需要远程获取关键配置信息,比如数据库密码,则需要在项目启动前获取配置并且进行本地配置替换。 二.Demo实现 1.maven依赖 <dependencies><dependency><groupId>org.springframewor

【SpringMVC学习04】SpringMVC中的参数绑定总结

众所周知,springmvc是用来处理页面的一些请求,然后将数据再通过视图返回给用户的,前面的几篇博文中使用的都是静态数据,为了能快速入门springmvc,在这一篇博文中,我将总结一下springmvc中如何接收前台页面的参数,即springmvc中的参数绑定问题。 1. 参数绑定的过程 我们可以回忆一下,在struts2中,是通过在Action中定义一个成员变量来接收前台传进来的参数,而在