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

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

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

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.mzph.cn/news/117782.shtml

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

相关文章

Ant-Design-Pro-V5 :QueryFilter高级筛选组件、Table以及Pagination组件结合实现查询。

需求:根据 分类条件选择不同类型, table表格调取不同接口,展示不同数据。 代码: import React, { useRef, useState, Fragment, useEffect } from react; import { getNoticeInfo, getBannerList, delNotice } from ./service; …

windows系统ntp服务器一键开启

脚本 echo off REM 自动判断权限问题,主动获取管理员权限 echo off >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" if %errorlevel% NEQ 0 ( goto UACPrompt ) else ( goto gotAdmin ) …

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

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

vue2.0项目中组件和iframe之间如何传值

vue2.0项目中组件和iframe之间如何传值 一、vue组件二、iframe组件 一、vue组件 mounted() {// 注册 message 事件监听器,只注册一次window.addEventListener(message, this.handleFromIframeMessage) }, beforeDestroy() {// 移除事件监听器window.removeEventList…

C#实现数据导出任一Word图表的通用呈现方法及一些体会

疲惫的修改 应人才测评产品的需求,导出测评报告是其中一个重要的环节,报告的文件类型也多种多样,其中WORD输出也扮演了一个重要的角色。 实现方法比较简单,结合分析结果数据,通过WORD模板文件进行替换输出。在实现的…

关于报错java.util.ConcurrentModificationException: null的源码分析和解决

一般有这种问题,方法中至少会有List或者Map下的至少两个子类,有可能参数类型相同,也有可能不同都有可能触发这个问题!其主要原因是使用了ArrayList进行删除操作或者使用iterator遍历集合的同时对集合进行修改都有可能会出现这个问题 ArrayList属于List下的子类 需要区分的是Li…

qt的一些自绘控件

https://download.csdn.net/download/venice0708/88469835

嵌入式Linux_学习路线+基础知识

嵌入式Linux_学习路线基础知识 一、学习路线 说明:u-boot是一大块学起来需要三到六个月比较耗时,也属于比较落后的知识点,所以暂时不学习,内核也是如此暂时不学习,从应用层入手,先入门再深入 二、Shell指令…

MongoDB 的集群架构与设计

一、前言 MongoDB 有三种集群架构模式,分别为主从复制(Master-Slaver)、副本集(Replica Set)和分片(Sharding)模式。 Master-Slaver 是一种主从复制的模式,目前已经不推荐使用。Re…

ARM | 传感器必要总线IIC

IIC总线介绍 1.谈谈你对IIC总线理解? 1)IIC总线是串行半双工同步总线,主要用于连接整体电路 2)SCL/SDA作用:IIC是两线制,一根是时钟线SCK,用于控制什么时候进行进行数据传输,时钟信号由主机发出; 另一根是数据线SDA,用于进行数据传输,可以从…

C# Winform编程(9)网络编程

网络编程 HTTP网络编程IPAddress IP地址类WebClient类WebRequest类和WebResponse类 WebBrowser网页浏览器控件TCP网络编程TcpClient类TcpListener类NetworkStream类Socket类 HTTP网络编程 IPAddress IP地址类 IPAddress类代表IP地址,可在十进制表示法和实际的整数…

N——>BatchSize 数据维度理解和处理(chun, cat, squeeze, unsqueeze)

数据处理之N——>BatchSize N——>batch_size train_data TensorDataset(torch.Tensor(x_train).double(), torch.Tensor(y_train).double()) train_loader DataLoader(train_data, batch_sizeargs.bs, shuffleTrue, drop_lastTrue) for batch_idx, (inputs, results…

代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素。

用JAVA代码编写 704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12]…

Haproxy 服务

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

JAVA代码审计-纵向越权漏洞分析

查看这个cms系统后台管理员 添加用户的页面 点击添加管理员 这个模块只有管理员拥有,普通用户没有这个模块。 打开源码分析是否存在越权漏洞。 ------------------------------------------------------------------------------------------------------------ …

对python中切片详解

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

Python 自动化(十五)请求和响应

准备工作 将不同day下的代码分目录管理,方便后续复习查阅 (testenv) [rootlocalhost projects]# ls mysite1 (testenv) [rootlocalhost projects]# mkdir day01 day02 (testenv) [rootlocalhost projects]# cp -rf mysite1/ day01/ (testenv) [rootlocalhost proj…

Spring Cloud Config

Spring Cloud Config 服务端:一个集中化配置中心,可以是一个独立的服务,也可以注册到服务治理中心,它可以集中管理各个 微服务的配置; 作用原理是从某个地方读取(本地/云端)提供给其客户端作为配置; 客户端:作为一个服务端,通过读取Config的服务端来获取自己的配置文件; 服务…

计算机视觉中的数据预处理与模型训练技巧总结

计算机视觉主要问题有图像分类、目标检测和图像分割等。针对图像分类任务,提升准确率的方法路线有两条,一个是模型的修改,另一个是各种数据处理和训练的技巧(tricks)。图像分类中的各种技巧对于目标检测、图像分割等任务也有很好的作用&#…

【AGC】更新应用信息报未知错误解决方法

【问题描述】 最近有几个开发者遇到了一个问题,他们在AGC控制台配置好应用信息的图标和截图之后,点击保存按钮会弹出“未知错误,请稍后再试”的异常报错,导致无法正确保存应用配置信息。 出错页面如图所示。 ​​ 【解决方案】 …