当前位置: 首页 > article >正文

[设计模式]1_设计模式概览

摘要:设计模式原则、设计模式的划分与简要概括,怎么使用重构获得设计模式并改善代码的坏味道。

本篇作概览与检索用,后续结合源码进行具体模式深入学习。

目录

1、设计模式原理

核心原则(语言无关)

本质原理图

原则关联矩阵

2、设计模式分类

1. 创建型模式

2. 结构型模式

3. 行为型模式

3、重构获得模式

重构关键技法

静态——>动态

早绑定——>晚绑定

继承——>组合

编译时依赖——>运行时依赖

紧耦合——>松耦合

生产设计的原则倾向


什么样的代码算是好的代码呢?同好的服务一样(此处强行关联 《[微服务设计]1_微服务》)——功能正确、高效、可维护性强、健壮抗压、写作流畅。也可以总的来说,低耦合高内聚的高质量可维护代码,就是好的代码。

那么,有没有一些规范可以遵循的呢?

有的,设计模式就是这样的指导规范。

1、设计模式原理

核心原则(语言无关)

众所周知,设计模式有一些原则可以遵守:

单一职责一个模块/类应仅负责一个职责降低模块间耦合,提升可维护性。

里氏替换:所有引用基类的地方必须能透明地使用子类对象。这样利于抽象与类型封装。

开放封闭:对修改封闭、对拓展开放。

迪米特减少对象间不必要的直接交互,降低耦合,提高模块独立性

接口隔离原则客户端不应依赖未使用的接口将大接口拆分为小的、高内聚的接口。

依赖倒置:高层模块不应该依赖于底层模块,转换为二者都依赖于抽象。具体来说就是针对接口编程,而不是针对具体实现,这样可以减少各部分依赖关系,这也面向对象设计的亮点。

还有优先使用对象组合而不是类继承等等……

总的来说:从通用核心原则到实现层原则再到构造策略相关的原则,均追求代码模块的低耦合高内聚,降低复杂度。

本质原理图

原则关联矩阵

原则

变化控制

认知简化

系统弹性

典型模式

单一职责(SRP)

外观模式

开放封闭(OCP)

极高

极高

策略模式、 装饰器模式

里氏替换(LSP)

组合模式、代理模式

接口隔离(ISP)

极高

适配器模式、中介模式

依赖倒置(DIP)

极高

桥接模式、依赖注入模式

组合优先

极高

享元模式,职责链模式

2、设计模式分类

Gang of Four(四人组,GOF) 在经典著作《设计模式:可复用面向对象软件的基础》中提出的23种设计模式的分类方式,按功能和用途分为三大类——创建型、结构型、行为型。

1. 创建型模式

目的:解决对象的创建问题,封装实例化逻辑,提升代码复用性和灵活性。

核心思想:将对象的创建与使用解耦,通过统一接口或模板控制对象实例化过程。

  • 工厂方法 (Factory Method): 定义一个创建对象的接口,但由子类决定要实例化的类。工厂方法将类的实例化推迟到子类。

  • 抽象工厂 (Abstract Factory): 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。

  • 原型 (Prototype): 通过复制现有对象来创建新对象,而不是通过创建新实例。

  • 建造者 (Builder): 用于构建一个复杂对象的表示。使用多个步骤构建该对象,可以将构建过程与其表示分离。

2. 结构型模式

目的:处理类与对象的组合,优化系统结构,提高模块间的解耦性。

关注如何将对象组合成更大的结构,处理类和对象的关系,以实现更大的功能。

核心思想:通过组合、继承或接口,调整类与对象的结构,增强系统的灵活性和扩展性。

  • 装饰器 (Decorator): 动态地给对象添加额外的职责或行为,而不影响其他对象的功能。

  • 桥接 (Bridge): 将抽象部分与其实现部分分离,使它们可以独立变化。

  • 外观 (Facade): 为一组接口提供一个统一的高层接口,使得子系统更易使用。

  • 代理 (Proxy): 为其他对象提供一种代理以控制对这个对象的访问。

  • 中介者 (Mediator): 用于减少对象之间的通信复杂性,避免对象之间的直接引用。

  • 适配器 (Adapter): 在不修改源代码的情况下,允许将不兼容的接口连接起来。

  • 享元 (Flyweight): 通过共享对象来支持大量细粒度的对象,减少内存消耗。

3. 行为型模式

目的:定义对象间的通信机制,管理算法或职责的动态变化。

核心思想:通过解耦对象间的直接依赖,实现行为的动态组合、算法替换或事件驱动。

  • 模板方法 (Template Method): 在一个方法中定义一个操作的整体结构,而将某些步骤的实现延迟到子类中。

  • 策略 (Strategy): 定义一系列算法,将每个算法封装起来,并使它们可以互换,从而使其独立于使用它的客户端。

  • 观察者/事件 (Observer/Event): 定义对象间的一对多依赖,确保当一个对象变化时,其依赖对象也会被通知并自动更新。

  • 命令 (Command): 将请求封装为对象,从而允许参数化客户、队列请求以及记录请求日志。

  • 访问者 (Visitor): 表示一个作用于某种对象结构中的各元素的操作,并使其可以在不改变元素类的前提下定义新的操作。

  • 备忘录 (Memento): 捕获一个对象的内部状态,以便在以后恢复对象的状态,而不暴露对象的实现细节。

  • 状态 (State): 让一个对象在其内部状态改变时改变其行为,对象将表现得像是它的类发生了改变。

  • 组合 (Composite): 将对象组合成树形结构以表示部分-整体的层次,并使客户端对单个对象和组合对象的使用保持一致。

  • 迭代器 (Iterator): 提供一种方式访问一个集合对象中的元素,而无需暴露它的内部表示。

  • 责任链 (Chain of Responsibility): 将请求的发送者和接收者解耦。将多个对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它。

  • 解释器 (Interpreter): 为了一种语言定义一个文法,并提供一个解释器来使用该文法。

3、重构获得模式

需要强调的是:

设计模式是循序渐进的,如同架构设计一样,没有一步到位的设计模式,也少有单一的设计模式,要掌握应用时间、地点。

应对变化,提高复用,寻找变化点。

设计模式应在变化点处应用:设计模式是对动态变化点的设计,在变化、稳定中寻找隔离点,来分离他们,从而管理变化,以稳控变。

没有一个稳定的点,设计模式就没有意义。

设计模式有23种,慢慢理解嘛。

重构关键技法

在《重构-改善既有代码的设计》一书中,作者Martin Fowler有提到一些有效的重构手法,重构时遵守设计原则。

坏味道

重构手法

重复代码(Duplicated Code)

提炼函数、参数化、提取类。

过长函数(Long Method)

提炼函数、内联临时变量。

过大类(Large Class)

提炼类、搬移函数/字段。

数据泥团(Data Clumps)

封装为独立类。

条件逻辑复杂(Switch/If)

以多态取代条件、引入策略模式。

被拒绝的遗赠

函数下移、以委托取代子类。

冗余注释(Comments)

通过代码重构让注释多余(如提炼函数明确意图)。

技法对应原则如下:

静态——>动态

更好地适应变化与减少重复代码:动态结构可以通过配置或者接口灵活调整,可以服用逻辑,避免静态编码导致的重复。

设计模式

如策略模式(通过接口定义算法族,运行时选择具体策略)、观察者模式(事件驱动的动态通知机制)

重构手法

替换条件分支为多态(如将if-else替换为不同子类实现)。

引入参数化配置(如通过配置文件动态加载行为)。

示例如下:

// 静态:硬编码的条件判断
public void calculateTax(double income) {if (country == "USA") {// 美国税率逻辑} else if (country == "China") {// 中国税率逻辑}
}// 动态:使用策略模式
interface TaxStrategy {double calculate(double income);
}
class USATax implements TaxStrategy { ... }
class ChinaTax implements TaxStrategy { ... }public void calculateTax(TaxStrategy strategy, double income) {return strategy.calculate(income);
}

早绑定——>晚绑定

通过接口和运行时决策增强灵活性。

定义与背景

  • 早绑定:在编译时确定对象类型和方法调用(如直接调用具体类的方法)。

  • 晚绑定:在运行时动态确定对象类型和方法调用(如通过接口或多态)。

为什么需要转向晚绑定?

  • 灵活性:允许在运行时替换实现,支持扩展和插件化。

  • 解耦:调用者无需依赖具体实现类,仅依赖抽象接口。

如何实现?

  • 面向接口编程:定义接口,由子类实现不同行为。

  • 多态与虚函数:通过基类指针/引用调用虚方法。

  • 依赖注入:通过外部配置或工厂动态注入对象。

// 早绑定:直接依赖具体类
public void sendEmail(Email email) {EmailSender sender = new GmailSender();sender.send(email);
}// 晚绑定:通过接口实现多态
public interface EmailSender {void send(Email email);
}
public class GmailSender implements EmailSender { ... }
public class OutlookSender implements EmailSender { ... }public void sendEmail(Email email, EmailSender sender) {sender.send(email); // 运行时决定具体实现
}

继承——>组合

用组合替代继承,避免层级过深。

定义与背景

  • 继承:通过继承复用父类代码,子类与父类形成“is-a”关系。

  • 组合:通过组合其他对象复用功能,形成“has-a”关系。

为什么需要转向组合?

  • 减少耦合:继承导致子类与父类高度耦合,组合则通过接口或抽象类解耦。

  • 避免继承树复杂化:组合允许动态替换组件,而继承的层级过深易导致维护困难。

如何实现?

  • 组合复用:将功能封装为独立对象,通过字段或方法参数组合使用。

  • 策略模式:通过组合不同策略对象实现不同行为。

// 继承:硬编码行为
class Animal {void move() { ... }
}
class Bird extends Animal { ... }
class Fish extends Animal { ... }// 组合:通过接口解耦
interface MovementStrategy {void move();
}
class Bird {private MovementStrategy movement;public Bird(MovementStrategy movement) {this.movement = movement;}void fly() {movement.move(); // 组合不同策略}
}

编译时依赖——>运行时依赖

将依赖关系移到运行时,提高可测试性和扩展性。

定义与背景

  • 编译时依赖:依赖关系在代码中硬编码(如直接new具体类)。

  • 运行时依赖:依赖关系在运行时动态注入(如通过配置或工厂)。

为什么需要转向运行时依赖?

  • 可测试性:便于通过模拟对象(Mock)进行单元测试。

  • 灵活性:可替换依赖实现,无需修改代码。

如何实现?

  • 依赖注入(DI):通过构造函数、方法或字段注入依赖对象。

  • 工厂模式:通过工厂类动态创建对象。

// 编译时依赖:硬编码依赖
class Service {private Database database = new MySQLDatabase();void doWork() {database.query();}
}// 运行时依赖:通过构造函数注入
class Service {private Database database;public Service(Database database) {this.database = database; // 运行时注入}void doWork() {database.query();}
}

紧耦合——>松耦合

通过抽象接口和事件机制降低模块间依赖。

定义与背景

  • 紧耦合:类之间直接依赖具体实现,修改一个类可能影响多个类。

  • 松耦合:通过抽象接口或事件机制降低依赖,仅依赖抽象。

为什么需要转向松耦合?

  • 可维护性:降低修改代码的风险,模块独立。

  • 扩展性:新增功能无需修改现有代码(开闭原则)。

如何实现?

  • 接口与抽象类:定义抽象接口,类仅依赖接口。

  • 观察者模式:通过事件机制解耦对象间的直接通信。

  • 发布-订阅模式:对象通过中间事件总线通信。

// 紧耦合:直接调用具体类
class Button {void onClick() {Text text = new Text();text.update();}
}// 松耦合:通过接口解耦
interface Updatable {void update();
}
class Button {private Updatable listener;public Button(Updatable listener) {this.listener = listener;}void onClick() {listener.update();}
}

总结一下核心思想是动态化、低耦合高内聚、保证可读性与可维护性。

生产设计的原则倾向

低耦合、高内聚是软件设计的核心原则,但并非所有设计场景的最优先考虑的事情。

比如业务服务设计中,优先考虑组织架构所需求的迭代或者用户需求和业务目标,在架构设计中,优先考虑系统性能、可维护性。

需要依据具体场景灵活变动。

再次强调,无必要修改的“完美主义”重构,设计模式也不是最优先考虑的,优先完成,优先保证代码清晰。

相关文章:

[设计模式]1_设计模式概览

摘要:设计模式原则、设计模式的划分与简要概括,怎么使用重构获得设计模式并改善代码的坏味道。 本篇作概览与检索用,后续结合源码进行具体模式深入学习。 目录 1、设计模式原理 核心原则(语言无关) 本质原理图 原…...

ClickHouse总体学习

文章目录 一、简介1、OLAP 与 OLTP 的对比2、列式储存的好处3、DBMS 的功能4、多样化引擎5、高吞吐写入能力6、数据分区与线程级并行 二、Explain 查看执行计划三、建表优化1、数据类型2、分区和索引3、表参数4、写入和删除优化 四、常见配置CPU资源内存资源存储 五、ClickHous…...

Elasticsearch集群与日志系统实战部署指南

一、环境规划与初始化配置 1. 服务器资源分配 IP地址部署服务主机名172.25.23.7ES Kafka Zookeeper Kibananode1172.25.23.8ES Kafka Zookeeper Filebeatnode2172.25.23.9Kafka Zookeeper Apache Logstashnode3 系统要求: 配置:4核CPU / 4G…...

SFT数据处理部分的思考

SFT数据及处理的业内共识 1.prompt的质量和多样性远重要于数据量级,微调一个 30 b 量级的base model只需要 10 w 量级的数据即可 参考:《LIMA:Less Is More for Alignment》 2.合成数据很重要!一般需要通过…...

netsh实现TCP端口转发

服务器:192.168.31.9 端口:56000 客户端:192.168.31.2 端口:5600 客户端(本地端口5600)通过TCP连接服务器的56000端口 PC:192.168.31.5,PC实现客户端和服务器之间56000端口转发 1. …...

数据分布偏移检测:保障模型在生产环境中的稳定性

数据分布偏移检测:保障模型在生产环境中的稳定性 引言 在机器学习系统从开发环境部署到生产环境的过程中,数据分布偏移问题是影响模型性能的主要挑战之一。当训练数据与生产环境中的数据分布不一致时,即使是经过精心调优的模型也可能表现出明显的性能下降。本文将深入探讨…...

leetcode 75.颜色分类(荷兰国旗问题)

题目描述 题目分析 本题是经典的「荷兰国旗问题」,由计算机科学家 Edsger W. Dijkstra 首先提出。 要想单独解决这道题本身还是很简单的,统计0、1、2的数量然后按顺序赋值,或者手写一个冒泡排序,whatever。 但是在这一题中我们主…...

在windows上通过idea搭建doris fe的开发环境(快速成功版)

一、前置环境准备 1. 准备Linux环境,我起的虚机,使用CentOS8,4核、12G,磁盘50G 1.1.备份yum源 # 系统下载连接:magnet:?xturn:btih:9DB46A612D04763AA7DB02A0FF63EDE2EA555867&dnCentOS-8.1.1911-x86_64-dvd1.…...

MyBatis源码分析の配置文件解析

文章目录 前言一、SqlSessionFactoryBuilder1.1、XMLConfigBuilder1.2、parse 二、mappers标签的解析2.1、cacheElement2.1.1、缓存策略 2.2、buildStatementFromContext2.2.1、sql的解析 前言 本篇主要介绍MyBatis源码中的配置文件解析部分。MyBatis是对于传统JDBC的封装&…...

python爬虫笔记(一)

文章目录 html基础标签和下划线无序列表和有序列表表格加边框 html的属性a标签(网站)target属性换行线和水平分割线 图片设置宽高width,height html区块——块元素与行内元素块元素与行内元素块元素举例行内元素举例 表单from标签type属性pla…...

docker后台运行,便于后期用命令行进入它的终端

在 docker compose up --build -d 命令中,​**-d​(或 --detach)参数的作用是让容器以后台模式(detached mode)​**运行。以下是详细解释: ​**-d 参数的作用** ​后台运行容器: 默认情况下&a…...

剑指 Offer II 087. 复原 IP

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20087.%20%E5%A4%8D%E5%8E%9F%20IP/README.md 剑指 Offer II 087. 复原 IP 题目描述 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址&#xf…...

DC-6靶机详解

一、主机发现 arp-scan -l靶机ip为192.168.55.159 二、端口扫描、目录枚举、指纹识别、 2.1端口扫描 nmap 192.168.55.159发现没有开放特殊端口 看来信息收集的重点要放在网页中了 2.2目录枚举 dirb http://192.168.55.1592.3指纹识别 nmap 192.168.55.159 -sV -sC -O …...

Java构造方法详解:从入门到实战

目录 一、什么是构造方法? 二、构造方法的作用 三、构造方法分类与使用 1. 默认构造方法 2. 有参构造方法 3. 构造方法重载 四、注意事项(避坑指南) 五、经典面试题解析 六、实战应用场景 七、总结 一、什么是构造方法? …...

STM32-SPI通信外设

目录 一:SPI外设简介 SPI框图​编辑 SPI逻辑 ​编辑 主模式全双工连续传输 ​编辑 非连续传输 二:硬件SPI读写W25Q64 1.接线: 2. 代码 SPI外设的初始化 生成时序 一:SPI外设简介 STM32内部集成了硬件SPI收发电路&#…...

远程控制中的云电脑是什么意思?1分钟学会用

很多常用我们ToDesk远程控制的朋友们或许会注意到无论是在PC端还是移动端中都出现有【云电脑】【来云电脑爽玩-新用户免费1小时】这些词句等信息。那么这究竟是代表什么意思呐?云电脑是什么又怎么用呐?为什么要增加云电脑?以下小编就为大家科…...

【go】Go 语言中 errors.Is 和 errors.As 的区别

Go 语言中 errors.Is 和 errors.As 的区别 核心区别概述 errors.Is 和 errors.As 是 Go 1.13 引入的错误处理函数,它们有着不同的用途: errors.Is: 判断错误链中是否包含特定的错误值(错误相等性检查)errors.As: 尝试将错误转换…...

网络爬虫【简介】

我叫补三补四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲视图 一、网络爬虫的定义 网络爬虫(Web Crawler),又称为网络蜘蛛、网络机器人等,是一种按照一定规则自动抓取互联网信息的程序或脚本。它…...

JPA动态查询自定义排序规则

方法1&#xff1a;使用 CASE WHEN 语句排序 // 自定义排序逻辑 // 定义“离线”排在前&#xff0c;“在线”排在后 Expression<Object> caseExpression cb.selectCase().when(cb.equal(root.get("status"), "离线"), 0).when(cb.equal(root.get(…...

卫语句优化多层if else嵌套

一、卫语句的介绍 卫语句是一种编程实践&#xff0c;用于在函数或方法的开头快速处理不符合条件的情况&#xff0c;从而避免深层次的嵌套结构。它的核心思想是尽早返回&#xff0c;减少嵌套&#xff0c;使代码更加清晰易读。 二、卫语句的作用 提高可读性&#xff1a;卫语句将…...

2024华东师范大学计算机复试上机真题

2024华东师范大学计算机复试机试真题 2023华东师范大学计算机复试机试真题 2022华东师范大学计算机复试机试真题 2024华东师范大学计算机复试上机真题 2023华东师范大学计算机复试上机真题 2022华东师范大学计算机复试上机真题 在线评测&#xff1a;传动门&#xff1a;pgcode…...

3.15刷题

P6337 [COCI 2007/2008 #2] CRNE - 洛谷 #include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;//横加竖 最大。n/2,n/21if(n%20){cout<<(n/21)*(n/21);}else cout<<(n/22)*(n/21);return 0; }P6338 [COCI 2007/2008 #2] PRVA - 洛…...

14.使用各种读写包操作 Excel 文件:辅助模块

一 各种读写包 这些是 pandas 在底层使用的各种读写包。无须安装 pandas&#xff0c;直接使用这些读写包就能够读写 Excel 工作簿。可以尽可能地使用 pandas 来解决这类问题&#xff0c;只在 pandas 没有提供你所需要的功能时才用到读写包。 表中没有 xlwings &#xff0c;因为…...

设计心得——多态

一、设计上的多态 无论是在网上还是书籍上&#xff0c;还是自己的文章里都反复分析过多态的原理、应用和各种常见的情况。本篇重点从设计的角度来阐述一下多态&#xff0c;而不对多态的具体的用法进行说明。在前面的知识学习中可以知道&#xff0c;多态可以分为动多态和靜多态…...

【DeepSeek】本地部署DeepSeek的完整教程(Ollama+Docker+Open WebUI)

本地部署DeepSeek的完整教程 文章目录 本地部署DeepSeek的完整教程写在前面技术需求详细步骤一. 安装Ollama软件二. 安装DeepSeek-R1模型三. 安装Docker软件四. 配置Web UI界面问题解决1. 打开`docker desktop`时,一直显示`Docker Engine stopped`2. 用`Docker`拉取`Open WebU…...

Python数据分析之数据可视化

Python 数据分析重点知识点 本系列不同其他的知识点讲解&#xff0c;力求通过例子让新同学学习用法&#xff0c;帮助老同学快速回忆知识点 可视化系列&#xff1a; Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…...

1、操作系统引论

一、操作系统 会使用linux系统 建议大家先学会linux的基础指令&#xff0c;可以看菜鸟教程网站进行学习。 1、各种定义 操作系统定义 管理计算机的 硬件 和软件资源&#xff0c; 能对各类作业进行调度&#xff0c;方便用户使用计算机的程序集合。操作系统运行在内核态&#xf…...

DeepSeek 本地化新篇章:Ollama 兼容 OpenAI API 的深度解析与部署实践

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着大语言模型(LLM)的快速发展,开发者对本地化部署和 API 兼容性的需求日益增加。Ollama 作为一个轻量级开源框架,通过兼容 OpenAI AP…...

【PTA题目解答】7-4 数气球 (20分)

1.题目 天空上有n个气球&#xff0c;第i个气球的颜色为colori​&#xff08;为全由小写字母组成的字符串&#xff09; 请你数出每种颜色的气球的数量&#xff0c;并按照颜色出现的先后顺序进行排序输出。 输入格式: 测试数据有T组(1≤T≤100)。 对于每组样例&#xff0c;第一…...

Swift 中 associatedtype 的用法详解

目录 前言 1.什么是associatedtype 2.associatedtype 的作用 1.让协议支持泛型 2.让协议支持不同的数据类型 3.结合 where 关键字限制类型 4.什么时候使用 associatedtype 5.总结 前言 在 Swift 语言中&#xff0c;泛型&#xff08;Generics&#xff09;是一个非常强大…...