贯穿设计模式第四话--里氏替换原则

🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳

从今天开始,将开启一个专栏,【贯穿设计模式】,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。为了能更好的设计出优雅的代码,为了能更好的提升自己的编程水准,为了能够更好的理解诸多技术的底层源码, 设计模式就是基石,万丈高楼平地起,一砖一瓦皆根基。 ✨✨欢迎订阅本专栏✨✨

🥺 本人不才,如果文章知识点有缺漏、错误的地方 🧐,也欢迎各位人才们评论批评指正!和大家一起学习,一起进步! 👀

❤️ 愿自己还有你在未来的日子,保持学习,保持进步,保持热爱,奔赴山海! ❤️

💬 最后,希望我的这篇文章能对你的有所帮助! 🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!

📃 前言回顾


​ 🔥【贯穿设计模式】第一话·设计模式初介绍和单一职责原则🔥

​ 🔥【贯穿设计模式】第二话·设计模式的七大原则之开闭原则🔥

​ 🔥【贯穿设计模式】第三话·设计模式的七大原则之依赖倒转🔥

在第三篇文章中,我们了解设计模式的七大原则中第三个原则:依赖倒转原则;

我们来回顾下,它的定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类。

并且我们通过上学时每个学期可能上课的不同导致如果需要再学习一门新课程的需求导致代码的修改等问题,值得注意的是:在实现依赖倒转原则时,需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入其他对象中。依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。而常用的注入方式有3种:接口注入、构造注入和Setter注入。

⭐ 前提需要

在面向对象的语言中,继承是必不可少的,它主要有以下几个优点:

  • 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
  • 提高代码的可重用性;
  • 提高代码的可扩展性;
  • 提高产品或项目的开放性。

相应的,继承也存在缺点,主要体现在以下几个方面:

  • 继承是入侵式的。只要继承,就必须拥有父类的所有属性和方法;
  • 降低代码的灵活性。子类必须拥有父类的属性和方法,使子类受到限制;
  • 增强了耦合性。当父类的常量、变量和方法修改时,必须考虑子类的修改,这种修改可能造成大片的代码需要重构。
  • 从整体上看,继承的“利”大于“弊”,然而如何让继承中“利”的因素发挥最大作用,同时减少“弊”所带来的麻烦,这就需要引入“里氏替换原则”。

🍊 里氏替换原则

今天我们学习的是里氏替换原则,任何基类可以出现的地方,子类一定可以出现。

🫓 概述

  • 该原则是指任何基类可以出现的地方,子类一定可以出现,即所有引用基类的地方都必须能够透明的使用其子类;里氏替换原则是继承与复用的基石,只有当子类可以替换掉基类,且系统的功能不受影响时,基类才能被复用,而子类也能够在基础类上增加新的行为;所以里氏替换原则指的是任何基类可以出现的地方,子类一定可以出现;

  • 里氏替换原则是对开闭原则的补充,实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范;

  • 简单理解就是子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,如果子类强制要重写父类的方法,那么可以再抽象一个基类,为他们的公共父类,或采用依赖、组合、聚合的方式来实现;

  • 比如有一个基类A有个实现了的方法,其子类B、C等都可以完全替换A类来实现,但不能影响原有的代码功能,如果子类B、C需要重写父类的方法的话,就会导致子类B、C不能完全替换基类A来使用了,此时应该可以再抽象一个基类,为他们的公共父类,或采用依赖、组合、聚合的方式来实现。

🧇 特点

里氏替换原则是实现开闭原则的重要方式之一,通过里氏替换可以使系统有以下优点

  • 解决了继承中重写父类造成的可复用性变差的问题;
  • 是动作正确性的保证,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性;
  • 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。

🧀 问题引出

在上Java相关课程时,学习面向对象的时候,老师都会提动物、猫、狗之间的关系,都会说他们之间的抽象关系或者继承关系,那接下来就以动物的例子来形象的讲解里氏替换原则吧。

1. 定义一个鸟类Bird:

对于鸟类来说,我们对此的第一印象就是鸟类都是能飞的。

package com.ygt.principle.lsp;

/**
 * 新建一个鸟类
 *  鸟类有个方法是可以非的方法
 */
public class Bird {

    // 鸟的名字
    private String name;

    // 构造方法
    public Bird(String name) {
        this.name = name;
    }

    public void fly(){
        System.out.println(this.name + "开始飞啦~~~");
    }
}

2. 定义一个百灵鸟类Lark并继承鸟类的飞行的方法:

package com.ygt.principle.lsp;

/**
 * 创建一个百灵鸟类,继承鸟类
 */
public class Lark extends Bird{

    public Lark(String name) {
        super(name);
    }
}

3. 建立一个测试类LiskovSubstitutionTest测试一下百灵鸟的飞行:

package com.ygt.principle.lsp;

/**
 * 测试里氏替换原则
 */
public class LiskovSubstitutionTest {

    public static void main(String[] args) {
        // 创建百灵鸟,并让其有飞的动作
        Lark lark = new Lark("百灵鸟");
        lark.fly();
    }
}

4. 得到的结果:

百灵鸟开始飞啦~~~

5. 现在新建一个鸵鸟类Ostrich并继承鸟类:

package com.ygt.principle.lsp;

/**
 * 建立一个鸵鸟类,并继承鸟类
 */
public class Ostrich extends Bird{
    public Ostrich(String name) {
        super(name);
    }
}

6. 测试鸵鸟的飞行功能:

package com.ygt.principle.lsp;

/**
 * 测试里氏替换原则
 */
public class LiskovSubstitutionTest {

    public static void main(String[] args) {
        // 创建百灵鸟,并让其有飞的动作
        Lark lark = new Lark("百灵鸟");
        lark.fly();

        // 创建鸵鸟,测试其飞行功能
        Ostrich ostrich = new Ostrich("鸵鸟");
        ostrich.fly();
    }
}

7. 得到的结果:

百灵鸟开始飞啦~~~
鸵鸟开始飞啦~~~

可是现在有个问题,我们知道普遍的鸟类动物都是善于飞翔的,但是也有些鸟类是不会飞行的,就如鸵鸟、企鹅一般的鸟类是不会飞行的,那此时我们在代码中将其继承鸟类,是不合理的,因为如果要在鸵鸟类中重写修改飞行方法的话,这就务必导致违反了里氏替换原则了,我们将不能使用鸵鸟类来替换成鸟类来使用了。下面我们就一起来看看解决方案吧。

🍕 解决方案

在上面的时候说过,当然我们也可以重写飞行的方法,使鸵鸟的飞行功能是无的,但是这就破坏了里氏替换原则,也会导致整个系统的可复用性变差;这时常用的解决方案就是取消原来的继承关系,重新设计他们之间的关系,即使原来的父类(鸟类)和子类(鸵鸟类)都继承一个更通俗的基类(动物类),这样原来的继承关系去掉,最后采用依赖,聚合,组合等关系代替。

1. 定义一个动物类Animal:

package com.ygt.principle.lsp;

/**
 * 定义一个动物类
 */
public class Animal {

    // 动物的名称
    public String name;

    public Animal(String name) {
        this.name = name;
    }
}

2. 修改鸟类、鸵鸟类和测试类:

package com.ygt.principle.lsp;

/**
 * 新建一个鸟类
 *  鸟类有个方法是可以非的方法
 *  修改如下,继承自动物类
 */
public class Bird extends Animal {

    public Bird(String name) {
        super(name);
    }

    public void fly(){
        System.out.println(super.name + "开始飞啦~~~");
    }
}
package com.ygt.principle.lsp;

/**
 * 建立一个鸵鸟类,并继承鸟类
 *  修改如下,不继承鸟类,改继承动物类
 */
public class Ostrich extends Animal{

    // 如果还想使用鸟类的属性以及方法,可以采用依赖方式
    private Bird bird;

    public Ostrich(String name) {
        super(name);
    }

    public void run(){
        System.out.println(name + "开始奔跑****");
    }
}
package com.ygt.principle.lsp;

/**
 * 测试里氏替换原则
 */
public class LiskovSubstitutionTest {

    public static void main(String[] args) {
        // 创建百灵鸟,并让其有飞的动作
        Lark lark = new Lark("百灵鸟");
        lark.fly();

        // 创建鸵鸟,测试其飞行功能
        Ostrich ostrich = new Ostrich("鸵鸟");
        ostrich.run();
    }
}

得到的结果:

百灵鸟开始飞啦~~~
鸵鸟开始奔跑****

这样我们通过使原来的鸟类和鸵鸟都继承一个新的基类Animal类后,这就排除了修改鸟类中的飞行方法了,如果还需要使用鸟类中的功能,可以通过依赖、聚合,组合等关系代替。

而我们在实际编程中,常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。

🌸 完结

相信各位看官看到这里大致都对设计模式中的其中一个原则有了了解吧,里氏替换原则指任何基类可以出现的地方,子类一定可以出现,即所有引用基类的地方都必须能够透明的使用其子类,里氏替换原则是继承与复用的基石,里氏替换原则是对实现抽象化的具体步骤的规范。

学好设计模式,让你感受一些机械化代码之外的程序设计魅力,也可以让你理解各个框架底层的实现原理。最后,祝大家跟自己能在程序员这条越走越远呀,祝大家人均架构师,我也在努力。 接下来期待第五话:接口隔离原则。 💪💪💪

文章的最后来个小小的思维导图:

🧐 本人不才,如有什么缺漏、错误的地方,也欢迎各位人才们评论批评指正!🤞🤞🤞

🤩 当然如果这篇文章确定对你有点小小帮助的话,也请亲切可爱的人才们给个点赞、收藏下吧,非常感谢!🤗🤗🤗

🥂 虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!✨✨✨

💟 感谢各位看到这里!愿你韶华不负,青春无悔!让我们一起加油吧! 🌼🌼🌼

💖 学到这里,今天的世界打烊了,晚安!🌙🌙🌙

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

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

相关文章

关于位运算的巧妙性:小乖,你真的明白吗?

一.位运算的概念什么是位运算?程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。位运算就是直接操作二进制数,那么有哪些种类的位运算呢?常见的运算符有与(&)、或(|)、异或(^)、…

软硬结合板设计,过孔到软板区域的间距设计多少合适

一博高速先生成员:王辉东 十里樱花香无边, 满枝芳华尽娇艳。 春风不知少年心, 红粉树下看如烟。 周六的下午,赵理工推开窗,一阵香风袭来,空气中氤氲着樱花的气息。樱花开得浪漫,恰似少年的…

[致敬未来的攻城狮计划 1] 使用 “FSP Configuration”(FSP 配置)透视配置器设置运行环境

开启攻城狮的成长之旅!这是我参与的由 CSDN博客专家 架构师李肯(http://yyds.recan-li.cn)和 瑞萨MCU (瑞萨电子 (Renesas Electronics Corporation) ) 联合发起的「 致敬未来的攻城狮计划 」的第 4 天,点击…

动态规划-不相交的线

动态规划-不相交的线 前言 动态规划中存在一类问题,它涉及到两个数组或链表,需要求解出两个数组中的最长公共子序列,如果要求解两个数组的最长公共子序列。如果采取最原始的方式,选择对第一个数组中的元素的不同排列进行有序组合…

Excel:vlookup函数

Excel:VlookUp函数VlookUp函数VlookUp函数 首先还是先放官方文档的参考:VLOOKUP 函数 Vlookup函数参数: VLOOKUP(lookup_ value, table_ array, col index_ num, [range_ lookup]) lookup_ value:要查找的内容; table_ array&a…

CloudCompare 二次开发(6)——插件中拖拽添加Qt窗口(区域生长算法为例)

目录 一、概述二、插件制作三、Cmake编译四、插件代码五、结果展示一、概述 手动拖拽的方式搭建Qt对话框界面的制作流程,以PCL中的点云区域生长算法为例进行制作。 二、插件制作 1、将....\plugins\example路径下的ExamplePlugin复制一份并修改名字为CCPointCloudProcess。 …

大数据之Spark基础环境

文章目录前言一、Spark概述(一)Spark是什么(二)Spark的四大特点(三)Spark的风雨十年(四)Spark框架模块(五)Spark通信框架总结前言 #博学谷IT学习技术支持# 本…

【lwIP(第四章)】网络接口

目录一、lwIP网络接口简介二、lwIP的netif结构三、lwIP的netif相关函数1. lwIP网络接口的全局变量2. netif_add()函数3. netif_remove()函数4. netif_set_default()函数一、lwIP网络接口简介 lwIP协议栈支持多种不同的网络接口(网卡),由于网卡…

OSPF----优化

优化主要目的---减少LSA的更新量以及数量 路由汇总(减少骨干区域的LSA更新量)OSPF特殊秋雨(减少非骨干区域的LSA更新量)OSPF路由汇总(路由聚合) OSPF路由汇总是由手工部署的OSPF的汇总称为---区域汇总&…

Swagger快速入门【基础总结】

Swagger 背景信息 什么是前后端分离: 即: Vue Springboot 开发模式 以前是后端时代(后端是主力):前端只用管理静态页面;html—>后端。 前后端分离时代: 前端 :前端控制层、视图层【前端团队】后端:后…

客户端安装SSH工具Xshell图解

一、客户端安装SSH工具 windows客户端:安装Putty、XShell 或者 SecureCRT Linux客户端:yum install openssh-clients macOS客户端:默认已经安装了SSH客户端 我们这里安装windows客户端,选择XShell 工具。 Xshell5、Xftp5下载&am…

Linux系统之安装PostgreSQL数据库

Linux系统之安装PostgreSQL数据库一、PostgreSQL介绍1.PostgreSQL简介2.PostgreSQL特点二、本次实践介绍1.本次实践介绍2.实践环境介绍三、配置PostgreSQL的yum仓库源1.检查本地是否部署PostgreSQL2.配置镜像源3.检查yum仓库镜像源状态四、安装PostgreSQL1.安装PostgreSQL2.初始…

GPIO的八种模式分析

GPIO是general purpose input output,即通用输入输出端口,作用是负责外部器件的信息和控制外部器件工作。 GPIO有如下几个特点:1.不同型号的IO口数量不同;2,反转快速,每次翻转最快只需要两个时钟周期,以ST…

dubbo的SPI机制和服务暴露,引用原理

一、SPI引入:spi标准:1、需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service2、在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :2.1 文件名必须是扩展的接口的全路径名…

量子运算-比算子描述更广泛的一类刻画量子态在客观世界演化的数学工具

参考链接:1.1 量子运算 - 知乎 (zhihu.com)一个量子操作(包括量子测量和量子信道)指的是把一个密度矩阵变成另一个密度矩阵的变换,一般记为 背景演化算符是酉的。这里考虑考虑特殊的演化-测量。测量对应的算子是投影算子&#xff…

刘禹锡最经典诗文10首,每一首都是千古名作,读懂受益一生

他是唐代最乐观的诗人,是比他的好友乐天更乐天的人!他与柳宗元并称“刘柳”,与韦应物、白居易合称“三杰”,并与白居易合称“刘白”。他是在唐代诗人中,出了名的豪放豁达的刘禹锡。白居易称他为“诗豪”。自“永贞革新…

Elasticsearch:理解 Master,Elections,Quorum 及 脑裂

集群中的每个节点都可以分配多个角色:master、data、ingest、ml(机器学习)等。 我们在当前讨论中感兴趣的角色之一是 master 角色。 在 Elasticsearch 的配置中,我们可以配置一个节点为 master 节点。master 角色的分配表明该节点…

【javaEE】阻塞队列、定时器、线程池

目录 🌴一、阻塞队列 1.概念 2.生产者消费者模型 3.阻塞队列的实现 🏹二、定时器 1.引出定时器 2.定时器的实现 🔥三、线程池 1.引出线程池 2.ThreadPoolExecutor 构造方法 3.标准数据库的4种拒绝策略【经典面试题】【重点掌握】 …

2020年第十一届C/C++ B组第一场蓝桥杯省赛真题

准备参加第十四届蓝桥杯,今天开始刷题目的第一天,下面是2020年第十一届C/C B组第一场蓝桥杯省赛真题,以下是我的做题目心得。跑步训练第一次写的代码失误点如下:第一个错误点是因为好久没有写代码,忘记判断对才能循环&…

【SCL】博图——先入先出排序法

使用博图SCL语言来实现先入先出排序 前言 使用SCL完成一个先入先出排序 具体要求:最先输入的一个数值,最先输出出来,下面的数自动向前填充; 注:这里可能有两种理解:一是第一个输入的第一个出来&#xff…
最新文章