设计模式中的黄金原则:引领你的代码风格,提升可维护性与扩展性

中国的先贤说过: 有道无术,术可求.有术无道,止于术. 术指的是技能、技术或方法,而道指的是原则、道德、智慧和理念。

西方古代的哲人也说过同样的话: 智慧之路从感性开始,却终极于理性.为什么要说设计原则呢, 因为设计模式通常需要遵循一些设计原则,在设计原则的基础之上衍生出了各种各样的设计模式。设计原则是设计要求,设计模式是设计方案,使用设计模式的代码则是具体的实现。

d3de79f38a67a59b18e524bad01d3bf2.jpeg

设计模式中主要有六大设计原则,简称为SOLID ,是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的),六大设计原则分别如下:

1、单一职责原则(Single Responsibitity Principle)

2、开放封闭原则(Open Close Principle)

3、里氏替换原则(Liskov Substitution Principle)

4、接口分离原则(Interface Segregation Principle)

5、依赖倒置原则(Dependence Inversion Principle)

6、迪米特法则(Law Of Demter)

1) 单一职责原则

一个类应该只有一个引起它变化的原因。换言之,一个类只负责一项职责。这样可以使得类更加可维护、可扩展、可重用。

在类的设计中 我们不要设计大而全的类,而是要设计粒度小、功能单一的类

生活中的例子

首先是单一职责原则: 一个类只负责一项职责, 比如说一辆汽车的刹车踏板,它的作用就是让行进间的汽车停止的, 如果现在这个刹车踏板的功能改成了,踩一半是油门, 踩到底是刹车,大家想一下 这会出现一种什么情况呢 ? 那现在世界上会开车的人 可能就非常少了, 估计女司机应该一个都没有. 这就是单一职责原则.

2d3b1b5bf8c6a42e275997ccb7fc07ad.jpeg

代码举例: 产品提出需, 要我们设计一个计算图形面积的类,如下:

f7696279b27bdf329082ec566014942f.jpeg

接着又提出,要能够将计算结果以JSON格式打印出来,然后我们就添加了这样一个打印JSON格式的方法

4ce4729ee0e28814b15cc7f7a9aa06ce.jpeg

请问在该类中添加打印方法是否合理 ?

答: 显然是不合理. 如果后面产品有提出了 打印XML格式...导出到Excel... 那

这个类就会变得十分臃肿,增加了很多本不属于他的责任

那该如何设计? 答: 将打印功能单独设计一个类出来

93a03d835fd357f86bbb60b29c635933.jpeg

注意: 面试官问的是一个思路,看你是不是有这种设计思维 不会关心具体代码

单一职责原则的优点包括:

1. 提高代码的可读性和可维护性:一个类只负责一个职责,代码更加清晰,更容易理解和维护。

2. 降低类的复杂度:一个类只需要负责一个职责,类的复杂度更低,更容易进行测试和调试。

3. 降低代码的耦合度:当一个类只负责一个职责时,不同的职责可以分配到不同的类中,不同的类之间相互独立,从而降低了代码的耦合度。

4. 提高代码的可复用性:一个类只负责一个职责,可以更容易地被其他模块复用。

5. 便于扩展和维护:当需求变化时,如果每个类只负责一个职责,我们只需要修改相关的类即可,而不需要修改其他的类,从而更容易进行扩展和维护。

延伸面试题: 如何判断一个类的职责是否单一?

类中的代码行数、函数、或者属性过多;

函数或者属性过多,说明该类设计的不够单一,很可能包含其他职责内容

类依赖的其他类过多

说明该类有一些本该属于自己的功能,被抽取到其他类中了

私有方法过多

过多的私有方法,说明该类很可能包含其他类的职责内容

类中大量的方法都是集中操作类中的几个属性

说明属性设计不合理,本应该设计在其他类的属性

总结: 不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的,最好的方式就是:

我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

2) 开放封闭原则

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,我们应该通过添加新的代码来扩展软件的功能,而不是修改已有的代码。

生活中的例子

开放封闭原则: 对扩展开发,对修改关闭. 我还是用汽车举例, 我是一个广东人, 今年冬天我要开车去东北 去东北吃雪, 因为广东人好吃嘛,没吃过雪 想去长白山吃点新鲜的雪. 东北雪很大 就需要给这个车的轮胎做防滑. 其实很简单只要给轮胎装上防滑链就可以了,这就属于是对轮胎的开放扩充. 在轮胎上面新增了防滑链,没有改变轮胎的原有功能. 我们不能因为为了给轮胎做防滑,而把汽车引擎换掉了.

944a7a68414b4dc9ede3671b17da4f96.jpeg

代码举例: 还是计算面积的功能,现在我们又有一个计算三角形面积的需求,应该怎么做呢?

方式1: 直接修改 AreaCalculator

630776a8ca0109f4ca212c1710db27a6.jpeg

上面的做法就违反了开闭原则,因为假设后面又增加了 计算正方形、圆形等等的需求的时候,就需要不断的修改该类的代码,增加新的函数.

想要满足开闭原则,就必须要使用顶层设计思维,来解决问题

顶层设计思维

抽象意识

封装意识

扩展意识

比如在这里我们先利用抽象思维,将各类图形进行抽取,设计一个接口来表示图形(抽象),然后利用扩展思维,在接口中有一个抽象的方法,该方法的功能是获取面积.

每增加一种图形,就去实现该接口,然后重写计算面积方法即可.

e57d11a346052d3580ada2cc828950b2.jpeg

有了抽象之后,就可以利用抽象,修改计算面积的方法,将参数改为接口类型,该程序的扩展性 就提升了,再有新的图形添加,也不需要修改计算程序的代码,只需添加新的类即可,从而实现了开闭原则.

f291c93e3851f273bb2fb4e8567407d2.jpeg

在写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整 体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩 展开放、对修改关闭”。

开放封闭原则的优点包括:

1. 提高代码的可维护性和可复用性:开放封闭原则要求我们使用扩展来增加功能,而不是直接修改原有代码,这样可以避免对原有代码的破坏,从而提高代码的可维护性和可复用性。

2. 降低代码的耦合度:当需要增加新的功能时,我们可以通过扩展来实现,而不是修改原有代码,这样可以避免不必要的代码耦合,从而提高代码的灵活性和可扩展性。

3. 提高代码的稳定性:当需要增加新的功能时,我们只需要扩展已有的代码,而不需要修改原有的代码,这样可以避免引入新的错误,从而提高代码的稳定性。

4. 提高代码的可测试性:使用开放封闭原则可以避免对原有代码的破坏,从而更容易进行测试和调试。

5. 提高代码的可维护性:使用开放封闭原则可以使得代码更加模块化,更容易进行维护和修改。

3) 接口隔离原则

客户端不应该依赖它不需要的接口。也就是说,我们应该将不同的接口拆分成更小的、更具体的接口,从而避免客户端依赖于它们不需要的方法。这样可以降低接口的复杂性,提高系统的可维护性和可扩展性。

生活中的例子

接口隔离原则: 客户端不应该依赖它不需要的接口。 我们拿汽车的方向盘举例, 我们通过转动方向盘,可以控制车辆的转向,对于我们开车的人来说只需要知道如何转动方向盘就可以了 , 例如 倒车入库: 1、左后视镜下沿与停止线重合,向右打死方向盘;2、看右后视镜,车身库角30cm到了,向左回一圈方向盘;3、当车门把手与库角重合,方向盘右打死;4、看左侧后视镜,看到后边库角露出10cm,方向盘回正。

用户操作方向盘并不需要知道车辆的引擎、刹车、变速器等部件的具体实现细节,它只需要提供一个简单的接口 就是方向盘,即让驾驶员可以方便地控制车辆的方向。

d59ed020ab8d9ace7ec12801ba7e1229.jpeg

下面的代码,就是没有遵守接口隔离原则,其中Rectangle类 实现了不应该它实现的方法.

848e0989fd8e976a859469d440e839c2.jpeg

接口隔离原则的优点包括:

提高代码的灵活性和可维护性:接口隔离原则要求我们定义精简的接口,这样可以使得代码更加灵活和可维护,因为我们只需要实现我们真正需要的接口即可。

2. 提高代码的可测试性:接口隔离原则可以使得代码更加容易进行测试和调试,因为我们可以只测试我们真正需要的接口。

降低模块之间的耦合度:当模块之间只依赖于真正需要的接口时,它们之间的耦合度更低,更容易进行组合和修改。

4. 提高代码的可复用性:当接口精简清晰时,代码的可复用性也会提高,因为我们可以更容易地将代码组合到不同的应用场景中。

5. 提高代码的安全性:接口隔离原则可以避免一些意外的依赖关系,从而提高代码的安全性。

延伸面试题: 接口隔离原则与单一职责原则的区别

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

4) 依赖倒置原则

高层模块不应该依赖于底层模块,而是应该依赖于抽象。也就是说,我们应该面向接口编程,而不是面向实现编程。

我们一起看一下下面代码有没有问题 ?

fa94cd0f09d760967301dcd31c74b1d9.jpeg

很显然上面的代码是有问题的, 我们应该遵守依赖倒置原则,高层模块不应该依赖于底层模块,而是应该依赖于抽象 .Car类是相对高层的,而QYEngine是底层的是具体实现.

e46feb39c1fde0c7527be85c4e8b073c.jpeg

依赖倒置原则的核心思想是:针对抽象编程,而不是针对具体实现编程。这样可以降低模块之间的耦合度,使系统更加灵活、可扩展和易于维护。同时,依赖倒置原则也可以促进面向对象设计的另一个原则——开闭原则的实现,即可以在不修改已有代码的情况下,通过添加新的实现来扩展系统的功能。

依赖倒置原则的优点包括:

1. 提高代码的灵活性和可维护性:依赖倒置原则要求我们依赖于抽象而不是具体实现,这样可以使得代码更加灵活、可扩展和可维护。

2. 降低模块之间的耦合度:当模块之间依赖于抽象而不是具体实现时,它们之间的耦合度更低,更容易进行组合和修改。

3. 提高代码的可测试性:依赖倒置原则可以使得代码更加容易进行测试和调试,因为我们可以使用抽象来代替具体实现,从而更容易进行模拟和测试。

4. 提高代码的可复用性:依赖倒置原则可以使得代码更加容易被复用,因为我们可以使用抽象来代替具体实现,从而更容易将代码组合到不同的应用场景中。

5. 提高代码的可扩展性:依赖倒置原则可以使得代码更加容易进行扩展,因为我们可以通过添加新的实现类来扩展代码的功能,而不需要修改已有的代码。

延伸面试题: 关于依赖倒置原则、依赖注入、控制反转这三者之间的区别与联系

1 ) 依赖倒置原则

依赖倒置是一种通用的软件设计原则, 主要用来指导框架层面的设计。

高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

2 ) 控制反转

控制反转与依赖倒置有一些相似, 它也是一种框架设计常用的模式,但并不是具体的方法。

“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。

Spring框架,核心模块IoC容器,就是通过控制反转这一种思想进行设计的

3 ) 依赖注入

依赖注入是实现控制反转的一个手段,它是一种具体的编码技巧。

我们不通过 new 的方式在类内部创建依赖的对象,而是将依赖的对象在外部创建好之后,通过构造函数等 方式传递(或注入)进来, 给类来使用。

依赖注入真正实现了面向接口编程的愿景,可以很方便地替换同一接口的不同实现,而不会影响到依赖这个接口的客户端。

5) 里氏替换原则

子类必须能够替换掉它们的父类。也就是说,在任何使用父类的地方,都应该能够使用子类来替代,而且程序不应该出现任何错误或异常

下面是一个 Rectangle 类,它是用表示矩形的,它有一个获取面积的方法

e501317ad91b4161de197396bae98d26.jpeg

接下来再设计一个 Square 类继承自 Rectangle 类表示正方形,该类的构造方法中只接收一个边长即可,但是要将继承子父类的长和宽属性 都进行填充.

并且 在 Square 类中,我们重写了父类 Rectangle 中的 setWidth 和setHeight 方法,这样可以确保 Square 的长和宽始终相等

ba834d48ff84665980e8eb3e0c2ed04e.jpeg

在测试方法中,我们创建了一个 Square 对象并将其赋值给一个 Rectangle 类型的变量 squareAsRectangle 。最后,我们调用了 printArea 方法来打印矩形和正方形的面积,可以看到程序输出正确的结果。

72b3d4e1d7bebdbdbc88278f00146770.jpeg

这个例子展示了里式替换原则的一个基本思想:子类应该能够替换掉父类并且不会影响程序的正确性。在这个例子中,我们可以将 Square 对象视为Rectangle 对象来使用,因为它们都有相同的方法和属性。这就是里式替换原则的应用。

a151abf868aec3bf5d5a692b4127ec1a.jpeg

里氏替换原则的优点包括:

1. 提高代码的灵活性:通过遵循里氏替换原则,我们可以在不影响程序正确性的情况下,更加灵活地使用不同的子类对象来实现不同的功能。

2. 提高代码的可扩展性:通过遵循里氏替换原则,我们可以更容易地向系统中添加新的子类,从而实现代码的扩展。

3. 提高代码的可维护性:通过遵循里氏替换原则,我们可以更容易地修改子类的实现,从而提高代码的可维护性。

4. 提高代码的可读性:通过遵循里氏替换原则,我们可以使得代码更加符合人类的思维方式,从而提高代码的可读性。

5. 降低代码的耦合度:通过遵循里氏替换原则,我们可以降低代码的耦合度,使得各个模块之间的关系更加清晰,更容易进行组合和修改。

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

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

相关文章

基于减法平均优化的BP神经网络(分类应用) - 附代码

基于减法平均优化的BP神经网络(分类应用) - 附代码 文章目录 基于减法平均优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.减法平均优化BP神经网络3.1 BP神经网络参数设置3.2 减法平均算法应用 4.测试结果…

筹备三年,自动驾驶L3标准将至,智驾产业链的关键一跃

‍作者|张祥威 编辑|德新 多位知情人士告诉HiEV,智能网联汽车准入试点通知,乐观预计将在一个月内发布。试点的推动,意味着国家层面的自动驾驶L3标准随之到来。 「L3标准内容大部分与主机厂相关,由工信部牵头,找了几家…

使用vite搭建前端项目

1、在vscode 终端那里执行创建前端工程项目,其中shop-admin为项目名称: npm init vite-app shop-admin 提示如需安装其他依赖执行npm install ....,否则忽略(第三步再讲)。 2、执行npm run dev 命令直接运行创建好的项目,在浏览器打开链接…

Spring底层原理(三)

Spring底层原理(三) Bean的生命周期 SpringBootApplication public class Application {public static void main(String[] args) {ConfigurableApplicationContext context SpringApplication.run(Application.class, args);context.close();} }Slf4j Component public cla…

Echarts自定义柱状图

目录 效果图 echarts官网找相似图 将柱状图引入html页面中 自定义柱状图 将不需要的属性删除 ​编辑 修改图形大小 grid 不显示x轴 ​编辑 不显示y轴线和相关刻度 ​编辑 y轴文字的颜色设置为自己想要的颜色 修改第一组柱子相关样式(条状) …

解决 /bin/bash^M: bad interpreter: No such file or directory

问题描述 linux 系统中知行*.sh 文件报/bin/bash^M: bad interpreter: No such file or directory 原因: .sh文件是在windows系统编写的,在linux执行就有问题 解决过程 转化下格式执行如下命令 # dos2unix app.sh 结果bash: dos2unix: command not …

性能优化:JIT即时编译与AOT提前编译

优质博文:IT-BLOG-CN 一、简介 JIT与AOT的区别: 两种不同的编译方式,主要区别在于是否处于运行时进行编译。 JIT:Just-in-time动态(即时)编译,边运行边编译:在程序运行时,根据算法计算出热点代码&#xf…

类图表示法

设计模式,用设计图表示的话,主要用到类图。常见UML类图如下: 1、类图:矩形框,代表一个类(Class)。类图分为三层,第一层显示类的名称,如果是抽象类,则用斜体显…

FFmpeg编译安装(windows环境)以及在vs2022中调用

文章目录 下载源码环境准备下载msys换源下载依赖源码位置 开始编译编译x264编译ffmpeg 在VS2022写cpp调用ffmpeg 下载源码 直接在官网下载压缩包 这个应该是目前(2023/10/24)最新的一个版本。下载之后是这个样子: 我打算添加外部依赖x264&a…

探索随机森林: 机器学习中的集成学习神器

机器学习 第七课 随机森林 概述机器学习机器学习的主要分类监督学习无监督学习强化学习 集成学习提高准确性增强稳定性提升泛化能力 集成学习的主要方法BaggingBoostingStacking 随机森林的理论基础决策树的基本原理随机森林的生成过程随机森林的优势与局限性 随机森林的实际应…

进阶JAVA篇-深入了解 Stream 流对象的创建与中间方法、终结方法

目录 1.0 Stream 流的说明 2.0 Stream 流对象的创建 2.1 对于 Collection 系列集合创建 Stream 流对象的方式 2.2 对于 Map 系列集合创建 Stream 流对象的方式 2.3 对于数组创建 Stream 流对象的方式 3.0 Stream 流的中间方法 3.1 Stream 流的 filter() 中间方法 3.2 Stream 流…

C++基类和派生类的内存分配,多态的实现

目录 基类和派生类的内存分配基类和派生类的成员归属多态的实现 基类和派生类的内存分配 类包括成员变量(data member)和成员函数(member function)。 成员变量分为静态数据(static data)和非静态数据&…

Git简明教程

1.Git的定位 在我们自己开发项目的过程中,经常会遇到这样的情况,为了防止代码丢失,或者新变更的代码影响到原有的代码功能,为了在失误后能恢复到原来的版本,不得不复制出一个副本,比如:“坦克大战1.0”“坦…

【Html】交通灯问题

效果 实现方式 计时器:setTimeout或setInterval来计时。setInterval和 setTimeout 在某些情况下可能会出现计时不准确的情况。这通常是由于JavaScript的事件循环机制和其他代码执行所需的时间造成的。 问询:通过getCurrentLight将每个状态的持续时间设置…

Git常用的命令有哪些?

一、前言 git 的操作可以通过命令的形式如执行,日常使用就如下图6个命令即可 实际上,如果想要熟练使用,超过60多个命令需要了解,下面则介绍下常见的的git 命令 二、有哪些 配置 Git 自带一个 git config 的工具来帮助设置控制…

Haproxy 服务

Haproxy:他也是常用的负载均衡软件 nginx 支持四层转发,七层转发 haproxy 也是四层和七层转发 LVS的DR和NAT都是基于四层转发 都是基于流量的转发。 tun:四层和七层都有。 基于四层的转发: 1,lvs 2,nginx 3&…

对python中切片详解

嗨喽,大家好呀~这里是爱看美女的茜茜呐 Python中什么可以切片 Python中符合序列的有序序列都支持切片(slice) 如:列表,字符,元祖 👇 👇 👇 更多精彩机密、教程,尽在下方,赶紧点击了解吧~ python源码、视…

“唯品会VIP商品API:一键获取奢侈品详情,尊享品质生活!“

要获取唯品会VIP商品的详细信息,您可以通过唯品会的API接口进行调用。 唯品会提供了多种商品选择,包括服装、美容护肤、鞋子、箱包、家居、母婴等等。在这些商品中,VIP奢侈品专区是唯品会的重要特色之一。 要获取VIP商品的详细信息&#xf…

计算机网络-计算机网络体系结构-应用层

目录 一、网络应用模型 客户/服务器模型(Client/Server) P2P模型(Peer-to-peer) 二、域名解析系统(DNS) 域名 域名服务器 解析过程 三、文件传输协议(FTP) FTP控制原理 四、电子邮件 组成结构 协议 SMTP MIME POP3 IMAP 五、万维网和HTTP协议 概述 HTTP 报…

程桌面管理软件Apple Remote Desktop mac中文介绍说明

Apple Remote Desktop mac是一款远程桌面管理软件。它可以让用户通过局域网或互联网连接到其他远程计算机,并实时监控和管理这些计算机。 使用Apple Remote Desktop,用户可以轻松远程操作和控制其他计算机的桌面。用户可以在远程计算机上查看、操控和键入…