【设计模式之美】重构(三)之解耦方法论:如何通过封装、抽象、模块化、中间层等解耦代码?
文章目录
- 一. “解耦”概述
- 二. 如何给代码“解耦”?
- 1. 封装与抽象
- 2. 中间层
- 2.1. 引入中间层能**简化模块或类之间的依赖关系**。
- 2.2. 引入中间层可以起到过渡的作用,能够让开发和重构同步进行,不互相干扰。
- 3. 模块化
- 4. 其他设计思想和原则
- 4.1. 单一职责原则
- 4.2. 基于接口而非实现编程
- 4.3. 依赖注入
- 4.4. 多用组合少用继承
- 4.5. 迪米特法则
一. “解耦”概述
重构可以分为大规模高层重构(简称“大型重构”)和小规模低层次重构(简称“小型重构”)。
通过解耦对代码重构,就是保证代码不至于复杂到无法控制的有效手段。
代码是否需要“解耦”?
- 看修改代码会不会牵一发而动全身。
- 依赖关系是否复杂
把模块与模块之间、类与类之间的依赖关系画出来,根据依赖关系图的复杂性来判断是否需要解耦重构。
二. 如何给代码“解耦”?
1. 封装与抽象
封装和抽象作为两个非常通用的设计思想,可以应用在很多设计场景中,比如系统、模块、lib、组件、接口、类等等的设计。封装和抽象可以有效地隐藏实现的复杂性,隔离实现的易变性,给依赖的模块提供稳定且易用的抽象接口。
比如,Unix 系统提供的 open() 文件操作函数,我们用起来非常简单,但是底层实现却非常复杂,涉及权限控制、并发控制、物理存储等等。
- 我们通过将其封装成一个抽象的 open() 函数,能够有效控制代码复杂性的蔓延,将复杂性封装在局部代码中。
- 因为 open() 函数基于抽象而非具体的实现来定义,所以我们在改动 open() 函数的底层实现的时候,并不需要改动依赖它的上层代码,也符合我们前面提到的“高内聚、松耦合”代码的评判标准。
2. 中间层
2.1. 引入中间层能简化模块或类之间的依赖关系。
下面这张图是引入中间层前后的依赖关系对比图。在引入数据存储中间层之前,A、B、C 三个模块都要依赖内存一级缓存、Redis 二级缓存、DB 持久化存储三个模块。在引入中间层之后,三个模块只需要依赖数据存储一个模块即可。
从图上可以看出,中间层的引入明显地简化了依赖关系,让代码结构更加清晰。

2.2. 引入中间层可以起到过渡的作用,能够让开发和重构同步进行,不互相干扰。
比如,某个接口设计得有问题,我们需要修改它的定义,同时,所有调用这个接口的代码都要做相应的改动。如果新开发的代码也用到这个接口,那开发就跟重构冲突了。为了让重构能小步快跑,我们可以分下面四个阶段来完成接口的修改。
- 引入一个中间层,包裹老的接口,提供新的接口定义。
- 新开发的代码依赖中间层提供的新接口。
- 将依赖老接口的代码改为调用新接口。
- 确保所有的代码都调用新接口之后,删除掉老的接口。
这样,每个阶段的开发工作量都不会很大,都可以在很短的时间内完成。重构跟开发冲突的概率也变小了。
3. 模块化
合理地划分模块能有效地解耦代码,提高代码的可读性和可维护性。所以,我们在开发代码的时候,一定要有模块化意识,将每个模块都当作一个独立的 lib 一样来开发,只提供封装了内部实现细节的接口给其他模块使用,这样可以减少不同模块之间的耦合度。
实际上,从刚刚的讲解中我们也可以发现,模块化的思想无处不在,像 SOA、微服务、lib 库、系统内模块划分,甚至是类、函数的设计,都体现了模块化思想。
如果追本溯源,模块化思想更加本质的东西就是分而治之。
4. 其他设计思想和原则
4.1. 单一职责原则
高内聚会让代码更加松耦合,而实现高内聚的重要指导原则就是单一职责原则。模块或者类的职责设计得单一,而不是大而全,那依赖它的类和它依赖的类就会比较少,代码耦合也就相应的降低了。
4.2. 基于接口而非实现编程
基于接口而非实现编程能通过接口这样一个中间层,隔离变化和具体的实现。这样做的好处是,在有依赖关系的两个模块或类之间,一个模块或者类的改动,不会影响到另一个模块或类。
实际上,这就相当于将一种强依赖关系(强耦合)解耦为了弱依赖关系(弱耦合)。
4.3. 依赖注入
跟基于接口而非实现编程思想类似,依赖注入也是将代码之间的强耦合变为弱耦合。尽管依赖注入无法将本应该有依赖关系的两个类,解耦为没有依赖关系,但可以让耦合关系没那么紧密,容易做到插拔替换。
4.4. 多用组合少用继承
- 继承是一种强依赖关系,父类与子类高度耦合,且这种耦合关系非常脆弱,牵一发而动全身,父类的每一次改动都会影响所有的子类。
- 组合关系是一种弱依赖关系,这种关系更加灵活,所以,对于继承结构比较复杂的代码,利用组合来替换继承,也是一种解耦的有效手段。
4.5. 迪米特法则
迪米特法则讲的是,不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。从定义上,我们明显可以看出,这条原则的目的就是为了实现代码的松耦合。
《设计模式之美》-- 王争
相关文章:
【设计模式之美】重构(三)之解耦方法论:如何通过封装、抽象、模块化、中间层等解耦代码?
文章目录 一. “解耦”概述二. 如何给代码“解耦”?1. 封装与抽象2. 中间层2.1. 引入中间层能**简化模块或类之间的依赖关系**。2.2. 引入中间层可以起到过渡的作用,能够让开发和重构同步进行,不互相干扰。 3. 模块化4. 其他设计思想和原则4.…...
Spring MVC学习之——Controller类中方法的返回值
Controller类中方法的返回值 1.返回ModelAndView RequestMapping("/hello")public ModelAndView hello(){//封装了页面和数据ModelAndView view new ModelAndView();//对这个请求的页面添加属性(数据)view.addObject("hello",&quo…...
IDEA中启动项目报堆内存溢出或者没有足够内存的错误
1.报错现象 java.lang.OutOfMemoryError: Java heap space 或者 Could not reserve enough space for object heap 2.解决办法 在运行配置中VM选项后加下面的配置: -server -XX:MaxHeapSize256m -Xms512m -Xmx512m -XX:PermSize128M -XX:MaxPermSize256m 3.JVM虚…...
Angular: DOCUMENT
不用原生的 document,是因为不利于后端渲染,所以避免使用原生浏览器的对象 import { DOCUMENT } from angular/common; import { Directive, Inject, Input, OnChanges, Output, Renderer2, SimpleChanges } from angular/core;Directive({selector: [a…...
mybatis-plus批量保存异常及效率优化
最近基于自己公司内部服务维护,发现其中调度中心近期出现不少错误日志,但是该任务却是正常执行,生成的报表数据也是正常的,所以很多天没有发现问题 这就匪夷所思了, 经仔细排查发现,是触发了feign超时hyst…...
查找局域网树莓派raspberry的mac地址和ip
依赖python库: pip install socket pip install scapy运行代码: import socket from scapy.layers.l2 import ARP, Ether, srpdef get_hostname(ip_address):try:return socket.gethostbyaddr(ip_address)[0]except socket.herror:# 未能解析主机名ret…...
乐观锁与悲观锁:高并发场景下的选择
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
vue2 省市区联动组件封装
在element ui中有级联选择器el-cascader,其实已经够用了,但是在实际需求中,发现el-cascader如果有三级,数据数组必须得三个才能完全展示,所以不符合实际需求,还是自定义封装吧 需求:省市区联动数组,有多少个显示多少个 这里使用element ui得el-select组件,思路是使用…...
VScode远程开发
VScode远程开发 在SSH远程连接一文中,我么介绍了如何使用ssh远程连接Jetson nano端,但是也存在诸多不便,比如:编辑文件内容时,需要使用vi编辑器,且在一个终端内,无法同时编辑多个文件。本节将介绍一较为实用…...
芯片设计重要工具—— IBM LSF 分布式高性能计算调度平台
IBM Spectrum LSF Suites 是面向分布式高性能计算 (HPC) 的工作负载管理平台和作业调度程序。基于 Terraform 的自动化现已可用,该功能可在 IBM Cloud 上为基于 IBM Spectrum LSF 的集群供应和配置资源。 借助我们针对任务关键型 HPC 环境的集成解决方案࿰…...
RDMA Scatter Gather List详解
1. 前言 在使用RDMA操作之前,我们需要了解一些RDMA API中的一些需要的值。其中在ibv_send_wr我们需要一个sg_list的数组,sg_list是用来存放ibv_sge元素,那么什么是SGL以及什么是sge呢?对于一个使用RDMA进行开发的程序员来说&#…...
【动态规划】24子数组系列_最长湍流子数组_C++
题目链接:最长湍流子数组 目录 题目解析: 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析: 题目让我们求返回 arr 的 最大湍流子数组的长度 由题可得: 如果比较符号在子数组中的…...
fastJson和jackson的日期数据处理
目录 1.jackson 2.fastjson 3.总结 1.jackson jackson是spring mvc默认的JSON解析方法,前端的数据序列化处理之后,后端经过反序列化处理可以直接使用实体对象进行接收。后端接口返回实体对象,经过序列化处理后前端可以接收并进行处理。 …...
书生·浦语大模型实战营第五节课笔记及作业
LMDeploy 大模型量化部署实践 1 大模型部署背景 1.1 模型部署及大模型特点 1.2 大模型部署挑战及方案 2 LMDeploy简介 2.1 核心功能-量化 2.2 核心功能-推理引擎TurboMind 2.1 核心功能-推理服务api server 3 动手实践及作业 按照文档LMDeploy 的量化和部署中的步骤在Intern…...
如何在CentOS 7 中基于OpenSSL 3.0 搭建Python 3.0 环境
1、OpenSSL 1.1 原因 [rootlocalhost ~]# openssl version OpenSSL 1.0.2k-fips 26 Jan 2017 [rootlocalhost ~]#通过执行openssl version可知Linux系统已经安装了OpenSSL,但该版本较低;Python 3 要求 OpenSSL版本不能低于1.1.1,否则安装P…...
爬虫接口获取外汇数据(汇率,外汇储备,贸易顺差,美国CPI,M2,国债利率)
akshare是一个很好用的财经数据api接口,完全免费!!和Tushare不一样。 除了我标题显示的数据外,他还提供各种股票数据,债券数据,外汇,期货,宏观经济,基金,银行…...
Spring Cloud和微服务架构的关系
大话Spring Cloud 在Java悠久的历史长河中(其实也就十来年),有一个框架自诞生之初就成了Java企业级开发领域的弄潮儿,它以开放的姿态不断引领着技术改革(我们管他叫Java领域的“改革开放”),它就是久经考验的企业级开发框架,改革…...
C++:通过ofstream写入二进制文件内容
C++:通过ifstream读取二进制文件内容_c++ ifstream 二进制读取-CSDN博客 介绍了读取二进制文件的方法。 本文介绍一下写入二进制数据到文件的方法: 1.通过write #include <fstream> #include <string> using namespace std; int main() {int data = 0x0102030…...
系统配置dns主从服务器
一、准备两台主机,区分主从 二、完全区域传送 1、主DNS服务器配置 #安装相关的包 [rootoula1 ~]# yum install bind -y#关闭防火墙 [rootoula1 ~]# systemctl stop firewalld [rootoula1 ~]# setenforce 0#修改配置主文件 [rootoula1 ~]# vim /etc/named.conf opt…...
【git】解决网络连接问题
ssh: connect to host github.com port 22: Connection timed out $ ssh: connect to host github.com port 22: Connection timed out fatal: Could not read from remote repository. bash: ssh:: command not found bash: fatal:: command not found无效 检查网络…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
