欢迎光临
我们一直在努力

23个设计模式一文搞懂

本笔记涵盖 23 种经典设计模式,按创建型、结构型、行为型三大类整理。每个模式配有生活类比、核心思想、结构图解、实现要点、真实应用案例、适用场景、优缺点分析及常见面试题。


目录

  • 设计原则(SOLID)
  • 创建型模式(5种)
  • 结构型模式(7种)
  • 行为型模式(11种)
  • 模式之间的关系
  • 模式速查表

一、设计原则(SOLID)

S — 单一职责原则(Single Responsibility Principle)

一个类只应该承担一种职责,只做一件事。如果一个类承担了多种职责,那么它发生变化的原因就会有多个,违反了高内聚低耦合的原则。

应用案例:在电商系统中,OrderService 只负责订单的创建、查询、取消等订单相关操作,不应该同时负责发送邮件通知或库存管理。邮件通知交给 NotificationService,库存管理交给 InventoryService。

O — 开闭原则(Open/Closed Principle)

软件实体对扩展开放、对修改关闭。当需要增加新功能时,应该通过增加新代码来实现,而不是修改已有的代码。这是设计模式中最核心的原则。

应用案例:支付系统中,新增"微信支付"时只需要新增 WechatPayStrategy 类,不需要修改已有的 PayService 代码。通过策略模式实现开闭原则。

L — 里氏替换原则(Liskov Substitution Principle)

所有引用父类的地方必须能够透明地替换为子类的对象,且程序行为不变。子类不能改变父类已有方法的语义。

应用案例:Bird 类有 fly() 方法,但 Penguin(企鹅)继承 Bird 后不能飞,重写 fly() 抛出异常——这违反了里氏替换原则。正确做法是设计 FlyingBird 和 Penguin 两个独立的类。

I — 接口隔离原则(Interface Segregation Principle)

客户端不应该被迫依赖它不使用的接口。接口应该小而专,而不是大而全。一个类对另一个类的依赖应该建立在最小的接口上。

应用案例:不应该设计一个庞大的 IEmployee 接口包含 work()、eat()、sleep() 方法,因为机器人员工不需要 eat() 和 sleep()。应该拆分为 IWorkable、IEatable、ISleepable 三个小接口。

D — 依赖倒置原则(Dependency Inversion Principle)

高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。简单说就是"面向接口编程,而不是面向实现编程"。

应用案例:OrderService 依赖 IRepository 接口而不是具体的 MySQLRepository。当需要切换到 MongoDBRepository 时,只需要更改注入的实现类,OrderService 完全不用改。


二、创建型模式(5种)— 关注"怎么创建对象"

创建型模式将对象的创建和使用分离,降低了代码的耦合度。


01 单例模式 Singleton

一句话理解

整个程序运行期间,某个类只能有一个实例对象。

生活类比

全国只能有一个皇帝。不管谁去请旨,面对的都是同一个人。如果有人想再立一个皇帝,那是谋反。

核心思想

构造方法私有化 + 静态变量 + 全局访问点 = 单例。

关键点说明
private 构造方法 外部不能 new
static 实例变量 全局唯一
getInstance() 唯一获取入口
实现方式详解

方式一:双重检查锁(最常用)

  • 第一次检查:如果已经创建了,直接返回,不需要进锁(性能优化)
  • 第二次检查:防止多个线程同时通过了第一次检查后重复创建
  • 必须加 volatile 关键字:instance = new Singleton() 不是原子操作,JVM 可能重排序为先赋值引用再初始化对象,volatile 可防止此问题

方式二:枚举方式(最简洁,推荐)

  • 天然防止反射攻击(反射无法创建枚举实例)
  • 天然线程安全(JVM 保证)
  • 代码最简洁,是 Effective Java 推荐的方式
真实应用案例
应用说明
Runtime 类 Java 的 Runtime.getRuntime() 就是单例,整个 JVM 只有一个运行时实例
Spring Bean Spring 容器中默认 scope="singleton",一个 Bean 只有一个实例
数据库连接池 HikariCP、Druid 等连接池都是单例,全局共享一个池
日志框架 Log4j、Logback 的 Logger 实例通常是单例
配置中心 Apollo、Nacos 的配置客户端是单例,全局读取同一份配置
线程池 Executors.newFixedThreadPool() 返回的线程池是单例
Windows 回收站 系统中只有一个回收站实例
网站访问计数器 全局唯一的计数器实例
适用场景
场景说明
数据库连接池 全局共享一个连接池,避免重复创建
线程池 统一管理线程资源
配置管理器 全局读取同一份配置
日志对象 统一日志输出,保证线程安全
缓存管理器 全局共享缓存实例
优缺点
优点缺点
节省内存(只创建一次) 不利于单元测试(全局状态难以 mock)
避免重复创建对象 反射可以破坏(枚举方式除外)
数据共享方便 违反单一职责(既管业务又管生命周期)
懒加载节省资源 在分布式系统中需要特殊处理
常见面试题
  • 双重检查锁为什么要加 volatile? instance = new Singleton() 不是原子操作,JVM 可能重排序。加 volatile 可防止此问题。
  • 反射能破坏单例吗? 可以,通过 Constructor.setAccessible(true) 可以调用私有构造方法。但枚举方式天然防御反射。
  • 单例在分布式系统中有什么问题? 单例是 JVM 级别的,不同 JVM 有不同实例。分布式系统需要使用分布式锁或 Redis 等方案保证全局唯一。

02 工厂模式 Factory

一句话理解

不直接 new 对象,而是通过工厂方法来创建对象。

生活类比

你去餐厅不会自己进厨房做菜,而是告诉服务员"来一份宫保鸡丁"。厨师(工厂)根据你的需求做出对应的菜(对象)。

三种工厂模式对比

简单工厂 → 工厂方法 → 抽象工厂
(一个工厂 (每个产品 (每个产品族
创所有产品) 有自己的工厂) 有自己的工厂)

一、简单工厂 (Simple Factory)

  • 一个工厂类,根据参数(通常是 switch-case)创建不同的产品
  • 优点:逻辑集中,使用简单
  • 缺点:每新增一种产品就要修改工厂代码,违反开闭原则
  • 也被称为"静态工厂方法"

二、工厂方法 (Factory Method)

  • 每种产品有自己的工厂,新增产品只需新增工厂类
  • 优点:符合开闭原则,新增产品不改已有代码
  • 缺点:类的数量增多

三、抽象工厂 (Abstract Factory)

  • 一个工厂生产一整套相关产品(产品族)
  • 例如:精灵族的城堡+国王+军队,兽人族的城堡+国王+军队
  • 优点:保证产品族风格统一
  • 缺点:新增产品类型需要修改所有工厂
真实应用案例
应用工厂类型说明
JDK 集合框架 工厂方法 List.of()、Set.of()、Map.of() 根据参数返回不同的集合实现
日志框架 简单工厂 LoggerFactory.getLogger() 根据类名返回不同的 Logger 实例
Spring BeanFactory 抽象工厂 根据配置创建不同类型的 Bean
数据库驱动 工厂方法 DriverManager.getConnection() 根据 URL 创建不同的数据库连接
跨平台 UI 抽象工厂 Windows/Mac/Linux 各有 Button、TextBox、Checkbox 的工厂
消息发送 工厂方法 根据类型创建 SMS、Email、Push 的发送器
支付系统 简单工厂 根据支付方式返回微信、支付宝、银行卡的支付处理器
JUnit 工厂方法 @Parameterized 测试通过工厂方法生成测试数据
棋盘游戏 抽象工厂 不同棋类(象棋、围棋、五子棋)各有棋盘、棋子、规则的工厂
三种工厂怎么选?
模式适用场景举例
简单工厂 产品种类少,创建逻辑简单 根据类型返回不同硬币
工厂方法 产品种类多,会频繁扩展 不同品牌各自创建自己的手机
抽象工厂 需要一整套风格统一的产品 游戏中不同种族的城堡+国王+军队
优缺点
优点缺点
解耦:调用方不需要知道具体类名 增加新产品时需要增加新的工厂类
符合开闭原则(工厂方法/抽象工厂) 类的数量会增多
统一管理对象创建 简单工厂违反开闭原则
代码可维护性高 抽象工厂新增产品类型困难

03 建造者模式 Builder

一句话理解

把复杂对象的"组装过程"和"最终产品"分离开。

生活类比

你去肯德基点套餐:先选汉堡,再选饮料,最后选薯条。服务员按你的要求一步步组装,最后给你一个完整的套餐。你不需要知道汉堡怎么做、饮料怎么调——你只管选,服务员(Builder)负责组装。

核心角色
角色职责
Director(指挥者) 调用 Builder 的步骤,定义组装流程
Builder(建造者) 负责一步步组装产品
Product(产品) 最终得到的对象
核心特点
  • 链式调用:每个构建方法返回 this,支持 builder.buildType().buildRegion().build() 这样的流畅写法
  • 分步构建:将复杂对象的创建分解为多个简单的步骤
  • 产品复制:build() 方法中复制结果返回新对象,防止外部修改 Builder 内部状态
真实应用案例
应用说明
Lombok @Builder 自动生成 Builder 代码,简化 POJO 对象创建
Java StringBuilder new StringBuilder().append("a").append("b").toString() 就是链式构建
Guava ImmutableList ImmutableList.builder().add(1).add(2).build() 构建不可变集合
OkHttp Request new Request.Builder().url(…).header(…).build() 构建 HTTP 请求
Retrofit new Retrofit.Builder().baseUrl(…).addConverterFactory(…).build()
AlertDialog.Builder Android 中构建对话框
Notification.Builder Android 中构建通知
Document Object Model XML/HTML 文档的构建过程
Protobuf Protocol Buffers 的消息构建器
MyBatis SqlBuilder 动态 SQL 构建
Builder vs 工厂模式
对比Builder工厂
关注点 对象怎么组装 对象创建哪个
产品 通常是同一个类的不同配置 不同的类
复杂度 参数多、步骤多 参数简单
适用场景
场景说明
HTTP 请求构建 RequestBuilder().url(…).method(…).header(…).build()
SQL 查询构建 QueryBuilder().select(…).from(…).where(…).build()
复杂对象创建 参数很多、有很多可选参数时
不可变对象 对象创建后不能修改
消息/邮件构建 构建包含多个字段的消息对象
优缺点
优点缺点
可读性好,代码清晰 需要额外创建 Builder 类
可以灵活控制创建过程 对简单对象来说过度设计
支持链式调用
可以创建不可变对象
参数校验更灵活

04 原型模式 Prototype

一句话理解

通过"复制"已有对象来创建新对象,而不是从零 new。

生活类比

你写了一份 Word 文档,觉得不错,Ctrl+C、Ctrl+V 复制了一份。这就是原型模式——从已有的"原型"复制出新对象。

核心思想

实现 Cloneable 接口 → 重写 clone() 方法 → 决定用浅拷贝还是深拷贝。

浅拷贝 vs 深拷贝

浅拷贝:
Level A → Monster ← Level B

两个 Level 指向同一个 Monster!
改 A 的 Monster,B 也会变

深拷贝:
Level A → Monster A
Level B → Monster B (完全独立)

对比项浅拷贝深拷贝
基本类型字段 复制值 复制值
引用类型字段 复制引用(指向同一对象) 复制整个对象(完全独立)
实现方式 Object.clone() 序列化/反序列化
安全性 不安全(共享引用) 安全(完全独立)
深拷贝的实现方式
  • 递归 clone:容易遗漏嵌套的引用类型,不推荐
  • 序列化/反序列化:一次性解决所有层级的拷贝,推荐
  • JSON 转换:通过 JSON 序列化再反序列化实现深拷贝
真实应用案例
应用说明
Spring Bean 原型作用域 scope="prototype" 每次获取都创建新实例
Excel 复制工作表 复制整个工作表的结构和数据
Word 文档模板 基于模板创建新文档,保留格式和内容
游戏中的技能特效 从技能模板复制出多个特效实例,微调参数
邮件模板 基于模板克隆出多封邮件,修改收件人和内容
CAD 设计 复制已有组件,修改参数创建新组件
数据库备份 复制数据库结构和数据创建测试环境
JVM 对象复制 Object.clone() 的底层实现
Protobuf 消息复制 深拷贝消息对象避免副作用
适用场景
场景说明
游戏怪物生成 从模板复制大量怪物,微调属性
创建成本大 数据库连接、网络请求等创建很贵
大量相似对象 与其一个个 new,不如 clone 模板
保护性拷贝 防止外部修改内部状态
对象缓存 缓存对象的克隆版本
优缺点
优点缺点
避免重复的初始化操作 需要实现 Cloneable 接口
比 new 更快(尤其是复杂对象) 深拷贝实现较复杂
简化对象创建过程 有循环引用时深拷贝会出问题
保护性拷贝防止数据泄露 需要处理 CloneNotSupportedException
常见面试题
  • 浅拷贝和深拷贝的区别? 浅拷贝只复制对象本身和基本类型字段,引用类型字段仍指向原对象;深拷贝把引用类型也复制一份,完全独立。
  • 为什么推荐序列化方式做深拷贝? 递归 clone 容易遗漏嵌套的引用类型,序列化/反序列化一次性解决所有层级的拷贝。
  • 原型模式和拷贝构造函数的区别? 原型模式通过 clone() 方法实现,拷贝构造函数通过构造方法实现。拷贝构造函数更灵活,可以处理 final 字段。

三、结构型模式(7种)— 关注"怎么组合类和对象"

结构型模式关注如何将类和对象组合成更大的结构,以实现新功能或简化设计。


05 适配器模式 Adapter

一句话理解

把一个类的接口转换成另一个类需要的接口——就像电源适配器。

生活类比

你从美国带回来一个插头(美标),在中国用不了。怎么办?买个转换头(适配器),插上就能用了。转换头没有改变插头本身,也没有改变墙上的插座,只是在中间做了"翻译"。

核心结构

目标接口(客户端期望的) ← 适配器 ← 被适配者(已有的、不兼容的)

两种适配器对比
类型实现方式优缺点
对象适配器 用组合(持有被适配者引用) 推荐,更灵活
类适配器 用继承(继承被适配者) Java 只能继承一个类,不灵活
真实应用案例
应用说明
Java InputStreamReader 将字节流 InputStream 适配为字符流 Reader
Java Arrays.asList() 将数组适配为 List 接口
Spring HandlerAdapter 将不同类型的 Handler 适配为统一的处理接口
Log4j 适配 Logback 旧代码用 Log4j API,通过适配器切换到 Logback 实现
第三方 SDK 整合 将第三方 SDK 的接口适配为项目统一的接口
旧系统接口兼容 新系统调用旧系统的接口时,用适配器做转换
USB-C 转接头 将 USB-C 接口适配为 USB-A、HDMI 等接口
数据格式转换 将 XML 数据适配为 JSON 接口
适用场景
场景说明
整合第三方库 第三方库接口和你的不一样,写适配器转换
兼容旧系统 旧接口改名了,用适配器兼容
统一接口 多个不同接口的类,用适配器统一成一个接口
数据格式转换 XML → JSON,List → Map
框架整合 Spring MVC 的 HandlerAdapter
优缺点
优点缺点
复用已有类,不改源码 增加类的数量
符合开闭原则 适配器太多会很混乱
灵活(对象适配器) 增加系统复杂度
降低耦合度

06 桥接模式 Bridge

一句话理解

把"抽象"和"实现"分开,让它们可以独立变化。

生活类比

遥控器(抽象)和电视(实现)是两个独立的东西。一个遥控器可以控制不同品牌的电视,一个电视也可以被不同遥控器控制。它们通过"信号"这座桥连接。

遥控器(抽象)──── 桥 ──── 电视(实现)
│ │
电视遥控器 海信电视
万能遥控器 小米电视

核心价值

如果不用桥接模式,要为每种存储方式写一个 Persistence 类:DbPersistence、FilePersistence……类爆炸。桥接后:N×M 变成 N+M。

核心结构

抽象层(Persistence) ──── 桥接 ──── 实现层(PersistenceImplementor)
│ │
高级操作 底层存储
saveData() DataBase
updateData() FileSystem

真实应用案例
应用说明
JDBC 驱动 Driver 接口是抽象,各数据库厂商提供实现(MySQL、Oracle、PostgreSQL)
Java AWT Shape 抽象和 Graphics 实现分离,不同平台有不同的图形实现
消息发送系统 消息类型(文本、图片、视频)是抽象,发送方式(短信、邮件、微信)是实现
跨平台应用 功能逻辑是抽象,平台实现(Windows、Mac、Linux)是实现
日志框架 日志级别(INFO、WARN、ERROR)是抽象,输出方式(控制台、文件、数据库)是实现
支付系统 支付方式(微信、支付宝)是抽象,支付场景(PC、移动端)是实现
游戏角色系统 角色类型(战士、法师)是抽象,武器类型(剑、法杖)是实现
适用场景
场景说明
跨平台应用 抽象=功能,实现=平台(Windows/Mac)
多种数据库 抽象=业务操作,实现=MySQL/Oracle
消息发送 抽象=消息类型,实现=短信/邮件/微信
图形绘制 抽象=形状,实现=颜色
设备驱动 抽象=设备操作,实现=具体驱动
优缺点
优点缺点
避免类爆炸(N×M 变成 N+M) 理解成本较高
符合开闭原则和单一职责 需要设计时就考虑好维度
抽象和实现可以独立扩展 增加系统复杂度
提高可扩展性
常见面试题
  • 桥接模式和适配器模式的区别? 桥接模式是设计时就把抽象和实现分开;适配器模式是事后解决已有类的接口不兼容问题。

07 组合模式 Composite

一句话理解

把"单个对象"和"容器对象"统一处理——像文件和文件夹一样。

生活类比

你操作文件时,删一个文件和删一个文件夹,操作是一样的(右键→删除)。文件夹里可以有文件,也可以有其他文件夹——这就是树形结构。

核心结构

LetterComposite(抽象类)
├── children: List<LetterComposite> ← 持有子节点
├── add() ← 添加子节点
└── print() ← 统一操作(递归)
├── Letter(叶子节点,没有子节点)
└── Sentence(容器节点,包含 Letter)

关键设计
  • 抽象组件:定义叶子和容器的公共接口,包含 add()、print() 等方法
  • 叶子节点:实现自己的操作,没有子节点
  • 容器节点:持有子节点列表,print() 方法递归调用所有子节点的 print()
  • 钩子方法:printThisBefore() 和 printThisAfter() 提供扩展点
真实应用案例
应用说明
文件系统 文件和文件夹统一操作(复制、删除、移动)
DOM 树 HTML 元素和容器元素(div、ul)统一处理
GUI 组件树 JPanel 包含 JButton、JLabel,统一处理事件
菜单系统 菜单项和子菜单统一处理点击事件
组织架构 部门包含子部门和员工,统一查询和统计
XML/JSON 解析 节点和叶子节点统一遍历和处理
Java 集合框架 Collection 接口定义了 add() 和 iterator()
Git 分支 目录和文件的树形结构管理
UI 框架 React/Vue 的组件树结构
适用场景
场景说明
文件系统 文件和文件夹统一操作(复制、删除)
UI 组件树 按钮、面板、窗口的嵌套
菜单系统 菜单项和子菜单
组织架构 部门包含子部门和员工
XML/JSON 解析 节点和叶子节点统一处理
优缺点
优点缺点
统一处理单个对象和组合对象 设计较复杂
新增节点类型容易 叶子节点不需要的方法也要实现
天然的树形结构 递归调用可能影响性能
简化客户端代码

08 装饰器模式 Decorator

一句话理解

不修改原有类,动态地给对象"穿上新功能"——像给手机贴膜、加壳。

生活类比

你买了一杯咖啡(基础对象),可以加糖、加奶、加冰——每加一种就是一个装饰器。加完之后还是咖啡,但功能更强了。

咖啡 → 加糖咖啡 → 加糖加奶咖啡 → 加糖加奶加冰咖啡
↑ ↑ ↑
基础 装饰器1 装饰器2

核心结构

Component(统一接口)
├── ConcreteComponent(被装饰者)
└── Decorator(装饰器基类,持有 Component 引用)
├── ConcreteDecoratorA(加攻击)
└── ConcreteDecoratorB(加防御)

关键设计
  • 统一接口:被装饰者和装饰器实现同一个接口
  • 装饰器基类:持有被装饰者的引用,默认行为是转发调用
  • 具体装饰器:继承装饰器基类,重写方法增强功能
  • 自由组合:多个装饰器可以嵌套使用
真实应用案例
应用说明
Java IO 流 BufferedInputStream 包装 FileInputStream,DataInputStream 再包装
Java Collections Collections.unmodifiableList() 返回不可变的装饰器
Java Servlet Filter 每个 Filter 装饰 Request/Response,层层增强
Spring AOP AOP 本质上是装饰器模式的动态代理实现
日志增强 给已有方法加上日志记录,不修改原代码
权限校验 在方法执行前加权限检查
性能监控 在方法前后加耗时统计
缓存层 给数据访问加上缓存装饰器
加密/压缩 GZIPInputStream 装饰 FileInputStream
装饰器 vs 继承
对比装饰器继承
灵活性 运行时自由组合 编译时固定
类数量 多(组合爆炸)
符合原则 开闭原则 违反开闭原则
适用场景
场景说明
Java IO 流 BufferedInputStream 包装 FileInputStream
日志增强 给已有方法加上日志记录
权限校验 在方法执行前加权限检查
性能监控 在方法前后加耗时统计
缓存 给数据访问加上缓存层
优缺点
优点缺点
不修改原有代码 多层装饰时调试困难
可以自由组合功能 包装层数多了代码变复杂
符合开闭原则 会产生很多小类
运行时动态增强

09 外观模式 Facade

一句话理解

为复杂的子系统提供一个简单的"一键操作"入口。

生活类比

按一下电脑的开机键,CPU 启动、内存检测、硬盘加载、显示器点亮——你不需要一个一个手动操作,开机键就是"外观"。

核心结构

客户端 → Facade(外观类)→ 子系统A
→ 子系统B
→ 子系统C

客户端只需要调用 Facade,不需要知道子系统的细节。

真实应用案例
应用说明
Spring JdbcTemplate 封装了获取连接、执行 SQL、处理异常、关闭资源的复杂流程
SLF4J 统一日志 API,屏蔽 Log4j、Logback 等底层实现
Hibernate Session 封装了 JDBC 的复杂操作
Spring MVC DispatcherServlet 统一处理 HTTP 请求,分发到不同的 Handler
微服务网关 为多个微服务提供统一的入口
智能家居 "回家模式"一键开启灯光、空调、窗帘
ORM 框架 MyBatis/Hibernate 封装数据库操作
文件上传 封装文件校验、存储、返回 URL 等复杂流程
适用场景
场景说明
简化复杂 API 把多个 API 调用封装成一个方法
分层架构 每层对外只暴露一个 Facade
第三方库整合 封装第三方库的复杂调用
模块间解耦 模块通过 Facade 通信,不直接依赖
简化客户端使用 隐藏子系统复杂性
优缺点
优点缺点
简化调用方使用 不符合开闭原则(加子系统要改 Facade)
解耦客户端和子系统 可能变成上帝类(God Class)
提高安全性(隐藏内部细节) 不利于子系统扩展
减少依赖关系
常见面试题
  • 外观模式和代理模式的区别? 外观模式是简化复杂系统的调用;代理模式是控制对对象的访问(权限、缓存、延迟加载等)。
  • 什么时候不用外观模式? 子系统本身就不复杂,或者客户端需要直接操作子系统的细节时。

10 代理模式 Proxy

一句话理解

给对象找一个"替身",通过替身来控制对原对象的访问。

生活类比

明星不会直接接通告,而是通过经纪人(代理)。经纪人会帮你过滤、审核、安排——你和明星之间多了一层控制。

三种代理对比
对比静态代理JDK 动态代理CGLIB 代理
需要接口
实现方式 手动写代理类 反射 + InvocationHandler 字节码生成
性能 最好 一般 较好
灵活性 低(每个接口都要写) 高(一个代理处理所有方法)
三种代理详解

静态代理:手动写一个代理类,实现与目标对象相同的接口。优点是简单直接;缺点是每个接口都要写一个代理类,很啰嗦。

JDK 动态代理(推荐):基于接口,运行时生成代理类。所有方法调用都会走到 invoke() 方法,可以在这里统一做权限校验、日志记录等。一个 Proxy 处理所有方法。

CGLIB 代理:基于继承,运行时生成子类。不需要接口,通过 Enhancer 创建目标类的子类并重写方法。Spring AOP 默认对有接口的类用 JDK 动态代理,对没有接口的类用 CGLIB。

真实应用案例
应用代理类型说明
Spring AOP JDK/CGLIB 动态代理 实现日志、事务、权限等横切关注点
Hibernate 延迟加载 静态代理 返回代理对象,访问时才查询数据库
MyBatis Mapper JDK 动态代理 接口方法调用代理对象执行 SQL
RPC 远程调用 动态代理 本地代理远程服务,屏蔽网络细节
Nginx 反向代理 静态代理 代理后端服务器,负载均衡
CDN 静态代理 代理源站,缓存静态资源
数据库连接池 静态代理 代理数据库连接,管理连接复用
权限控制 动态代理 方法执行前检查用户权限
事务管理 动态代理 方法前后开启/提交事务
日志记录 动态代理 记录方法调用日志
适用场景
场景说明
权限控制 方法执行前检查权限
日志记录 记录方法调用日志
事务管理 方法前后开启/提交事务
延迟加载 图片代理,点击时才加载
缓存 先查缓存,没有再查数据库
RPC 远程调用 本地代理远程服务
优缺点
优点缺点
不修改原代码增加功能 增加了类的数量
符合开闭原则 请求处理变慢(多一层调用)
控制访问权限 JDK 动态代理需要接口
灵活性高

11 享元模式 Flyweight

一句话理解

把相同的部分共享复用,减少内存占用——像 String 常量池。

生活类比

游戏中有 1000 个哥布林,但它们的模型、贴图、动画都是一样的,只有位置不同。如果每个哥布林都加载一套模型,内存就爆了。享元模式的做法:1000 个哥布林共享同一个模型数据(内部状态),只有位置不同(外部状态)。

核心概念
概念说明例子
内部状态 不随环境改变,可共享 哥布林的模型、贴图
外部状态 随环境改变,不可共享 哥布林的位置、血量
享元工厂 缓存已创建的对象,避免重复创建 PotionFactory
内存对比

不用享元:
100 个治疗药水 = 100 个 Potion 对象 → 占用 100 份内存

用享元:
100 个治疗药水 = 1 个 HealingPotion 对象(共享)+ 100 个引用 → 只占 1 份内存

真实应用案例
应用说明
String 常量池 "hello" 只创建一次,多个引用共享同一个对象
Integer 缓存 -128~127 的 Integer 被缓存,超出范围才创建新对象
Byte 缓存 -128~127 的 Byte 被缓存
线程池 线程创建成本高,复用已有线程
数据库连接池 连接复用,避免频繁创建销毁
游戏粒子系统 大量相同特效共享一份资源
棋盘游戏 棋子对象共享(黑白两种,位置是外部状态)
字符处理 处理大量字符时,相同字符共享对象
正则表达式 Pattern 对象被缓存复用
字体渲染 相同字符的字形数据共享
与单例的区别
  • 单例:一个类只有一个实例(如全局配置)
  • 享元:一个类可以有多个实例,但相同的内部状态共享同一个对象(如 String 常量池中有多个不同的字符串对象)
适用场景
场景说明
String 常量池 "hello" 只创建一次,多个引用共享
Integer 缓存 -128~127 的 Integer 被缓存
线程池 线程创建成本高,复用已有线程
数据库连接池 连接复用
游戏粒子系统 大量相同特效共享一份资源
优缺点
优点缺点
大幅减少内存使用 增加了系统复杂度
提高运行效率 外部状态需要额外管理
减少 GC 压力 不适合外部状态变化频繁的场景
共享资源节省成本
常见面试题
  • 享元模式和单例模式的区别? 单例是一个类只有一个实例;享元是一个类可以有多个实例,但相同的内部状态共享同一个对象。
  • String s = "abc" 和 String s = new String("abc") 的区别? 前者使用常量池(享元),后者每次都创建新对象。

四、行为型模式(11种)— 关注"对象之间怎么通信"

行为型模式关注对象之间的职责分配和通信方式,定义对象间的交互规则。


12 责任链模式 Chain of Responsibility

一句话理解

请求沿着一条链传递,每个节点决定"处理"还是"传递给下一个"。

生活类比

请假流程:实习生 → 组长 → 经理 → 总监 → CEO。你的请假请求沿着这条链传递,每个人只有权限处理一定天数的请假,超过了就交给上级。

请假 1 天 → 组长批准 ✓(结束)
请假 3 天 → 组长转经理 → 经理批准 ✓(结束)
请假 7 天 → 组长转经理 → 经理转总监 → 总监批准 ✓(结束)

核心结构

RequestHandler(处理者基类)

├── next(指向下一个处理者)
└── handleRequest()(处理或传递)

OrcSoldier → OrcOfficer → OrcCommander

关键设计
  • 抽象处理者:持有下一个处理者的引用(链表结构),提供默认的传递逻辑
  • 具体处理者:判断请求类型,能处理就处理,不能处理就调用 super.handleRequest() 传递
  • 组装责任链:通过构造函数嵌套创建链 new A(new B(new C(null)))
真实应用案例
应用说明
Servlet Filter javax.servlet.Filter 链,每个 Filter 处理或传递请求
Spring Interceptor Spring MVC 的拦截器链
Netty ChannelPipeline Netty 的处理器链,每个 Handler 处理或传递数据
审批系统 请假、报销、采购等审批流程
异常处理 try-catch 链,每个 catch 处理特定类型的异常
日志级别 DEBUG → INFO → WARN → ERROR 的过滤链
权限校验 多层权限检查(登录→角色→资源)
DOM 事件冒泡 事件从子元素向父元素传递
Struts2 拦截器 拦截器栈执行前后处理
OkHttp 拦截器 请求/响应的拦截器链
适用场景
场景说明
审批流程 请假、报销、采购审批
Web 过滤器 Servlet Filter 链
异常处理 try-catch 链
日志级别 DEBUG → INFO → WARN → ERROR
拦截器 Spring Interceptor
优缺点
优点缺点
请求发送者和处理者解耦 请求可能到达链尾也没人处理
可以灵活调整链的结构 链太长时性能下降
符合开闭原则 调试不方便
动态组合处理逻辑

13 命令模式 Command

一句话理解

把"操作"封装成对象——可以保存、撤销、排队执行。

生活类比

餐厅点单:你告诉服务员"来一份宫保鸡丁",服务员把你的需求写在菜单上(命令对象)。厨师拿到菜单才开始做。菜单可以排队、可以取消、可以记录今天做了什么菜。

核心结构

Invoker(触发者:按钮/遥控器)


Command(命令对象:execute() + undo())


Receiver(实际执行者:编辑器/电视)

关键设计
  • 命令接口:定义 execute() 方法,可选定义 undo() 方法
  • 具体命令:封装 Receiver 和具体操作,持有 Receiver 的引用
  • 触发者(Invoker):持有 Command 引用,调用 command.execute()
  • 撤销功能:命令对象中保存被删除的内容,undo() 时恢复
真实应用案例
应用说明
文本编辑器 Ctrl+Z 撤销、Ctrl+Y 重做
Git 操作 commit、revert、cherry-pick 都是命令对象
事务管理 多个操作封装为一个命令,全部成功才提交
任务队列 异步任务排队执行(线程池的 execute)
宏命令 一键执行多个操作(如 Photoshop 的动作录制)
遥控器 电视遥控器的每个按钮绑定一个命令
游戏操作 游戏中的技能释放、道具使用封装为命令
数据库事务 多条 SQL 封装为一个事务命令
撤销重做栈 编辑器的 Undo/Redo 栈
适用场景
场景说明
撤销/重做 Ctrl+Z / Ctrl+Y
宏命令 一键执行多个操作
任务队列 异步任务排队执行
事务 操作全部成功才提交
遥控器 一个遥控器绑定多个命令
优缺点
优点缺点
支持撤销/重做 命令类数量增多
解耦调用者和执行者 简单操作过度设计
可以序列化命令(持久化)
支持队列和日志

14 迭代器模式 Iterator

一句话理解

提供一种统一的方式来遍历集合,不需要知道集合内部怎么存储。

生活类比

你用遥控器换台,只需要按"上一个"、“下一个”,不需要知道电视内部是用数组还是链表存储频道的。

核心结构

Collection(集合)
└── iterator() → 返回 Iterator

Iterator(迭代器)
├── hasNext() → 还有下一个吗?
├── next() → 获取下一个
└── remove() → 删除当前元素

自定义分页迭代器

笔记中实现了一个分页迭代器,支持:

  • next() / previous():翻页
  • curr():获取当前页
  • goFirst() / goLast():跳到首页/末页
  • goPage(int pi):跳到指定页
  • pages():计算总页数
  • setPageSize(int size):设置每页大小
真实应用案例
应用说明
Java 集合框架 List、Set、Map 都实现了 Iterable 接口
for-each 语法糖 本质是迭代器模式的语法简化
分页查询 数据库分页、列表分页
ResultSet JDBC 的结果集就是迭代器
Stream API Java 8 的流式操作底层基于迭代器
数据库游标 游标逐行遍历查询结果
文件逐行读取 BufferedReader 的 readLine()
XML SAX 解析 逐个遍历 XML 节点
适用场景
场景说明
分页查询 数据库分页、列表分页
遍历集合 List、Set、Map 的遍历
树形遍历 文件系统目录遍历
数据库结果集 ResultSet 就是迭代器
优缺点
优点缺点
遍历逻辑与集合解耦 增加了类的数量
统一的遍历接口 简单集合用 for-each 更方便
支持多种遍历方式
简化集合接口

15 中介者模式 Mediator

一句话理解

用一个"中介"来协调多个对象之间的交互,避免它们直接互相引用。

生活类比

房产中介:买房的人和卖房的人不直接联系,都通过中介沟通。这样双方不需要知道对方的细节,中介负责协调。

没有中介:
买家 ←→ 卖家 (直接耦合)

有中介:
买家 ←→ 中介 ←→ 卖家 (通过中介解耦)

核心结构

ComponentA ──→ Mediator ←── ComponentB

ComponentC ────────┘

组件之间不直接通信,都通过 Mediator 转发。

关键设计
  • 中介者接口:定义 triggerEvent() 方法,组件通过事件名通知中介者
  • 组件基类:持有 Mediator 引用,变化时通知中介者
  • 具体中介者:实现事件分发逻辑,协调组件间的交互
真实应用案例
应用说明
聊天室 用户通过聊天室服务器通信,不直接互发消息
飞机塔台 飞机通过塔台协调起降,不直接通信
MVC Controller Controller 充当 Model 和 View 的中介
MQ 消息队列 生产者和消费者通过消息队列解耦
微服务网关 服务之间通过网关通信
表单联动 多个输入框之间的联动(选择省份→更新城市列表)
GUI 事件总线 组件通过事件总线通信
Actor 模型 Actor 之间通过消息通信
适用场景
场景说明
聊天室 用户通过聊天室服务器通信
飞机塔台 飞机通过塔台协调起降
表单验证 多个输入框之间的联动
GUI 组件 按钮、文本框、下拉框联动
MVC 框架 Controller 充当 Model 和 View 的中介
优缺点
优点缺点
减少对象间的直接依赖 中介者可能变成上帝类
集中控制交互逻辑 中介者复杂时难以维护
符合迪米特法则 增加了中介者的复杂度
易于扩展新组件

16 观察者模式 Observer

一句话理解

一个对象状态变了,自动通知所有关注它的对象——像微信公众号推送。

生活类比

你关注了"人民日报"公众号。每次它发新文章,你都会收到推送通知。你不需要每天去看,公众号会主动告诉你。

公众号(Subject) 粉丝(Observer)
│ │
├── 发布文章 ──→ 自动推送给所有粉丝

├── 粉丝A
├── 粉丝B
└── 粉丝C

核心结构

Subject(被观察者)
├── observers: List<Observer>
├── addObserver()
├── removeObserver()
└── notifyAll() ← 状态变化时通知所有观察者

Observer(观察者)
└── update() ← 接收通知,更新自己

关键设计
  • Subject:维护观察者列表,提供添加/移除方法,状态变化时遍历通知
  • Observer:定义 update() 接口,接收通知并更新自己
  • 松耦合:Subject 不知道 Observer 具体是谁,只知道它实现了 Observer 接口
  • 游戏案例:角色使用技能时,根据阵营过滤观察者(敌方扣血,友方不受影响)
真实应用案例
应用说明
Java Swing 事件 ActionListener 监听按钮点击事件
Java beans PropertyChange JavaBean 的属性变化监听
Spring ApplicationEvent Spring 的事件发布/监听机制
Guava EventBus Google 的事件总线
RxJava 响应式编程,基于观察者模式
WebSocket 服务端推送消息给所有订阅者
消息队列 发布/订阅模式
股票行情 股价变化通知多个客户端
Vue/React 响应式 数据变化自动更新视图
日志框架 日志事件通知多个 Appender
适用场景
场景说明
事件监听 按钮点击事件、鼠标事件
消息推送 微信公众号、邮件通知
股票行情 股价变化通知多个客户端
数据绑定 MVC 中 Model 变化通知 View
监控系统 服务器状态变化通知运维
优缺点
优点缺点
松耦合(Subject 不知道 Observer 具体是谁) 通知顺序不确定
支持广播通信 可能造成循环依赖
符合开闭原则(新增观察者不用改 Subject) 观察者过多时通知效率低
运行时动态订阅

17 状态模式 State

一句话理解

对象在不同状态下有不同的行为——状态变了,行为也跟着变。

生活类比

游戏角色有多种状态:站立、行走、攻击、受伤、死亡。在"站立"状态下可以"行走";在"攻击"状态下会自动回到"站立";"死亡"状态下什么也不能做。

站立 ──→ 攻击 ──→ 站立
│ │
↓ ↓
行走 受伤 ──→ 死亡

核心结构

Context(上下文,持有当前 State)


State(状态基类)
├── StandState(站立状态)
├── AttackState(攻击状态)
├── HurtState(受伤状态)
└── DeadState(死亡状态)

每个 State 知道自己该做什么,以及何时切换到下一个状态。

关键设计
  • 状态基类:定义 onEnter()(进入状态时执行)和 execute()(执行状态行为)
  • 上下文:持有当前 State 引用,changeState() 切换状态并触发 onEnter()
  • 状态转换:由具体状态类决定(如攻击状态执行后自动切换到站立状态)
  • 消除 if-else:不同状态的行为分散到各个状态类中,而不是用大量 if-else 判断
状态转换图

┌─────────────┐
│ StandState │
└──────┬──────┘
│ 攻击
┌──────▼──────┐
│ AttackState │
└──────┬──────┘
│ 被打
┌──────▼──────┐
│ HurtState │ ──→ DeadState(血量为 0 时)
└──────┬──────┘
│ 恢复
┌──────▼──────┐
│ StandState │
└─────────────┘

真实应用案例
应用说明
游戏角色状态 站立、行走、攻击、受伤、死亡
TCP 连接状态 LISTEN、ESTABLISHED、CLOSE_WAIT、TIME_WAIT
订单状态机 待支付→已支付→已发货→已签收→已完成
线程状态 NEW→RUNNABLE→BLOCKED→WAITING→TERMINATED
电梯状态 运行、停止、开门、关门
播放器状态 播放、暂停、停止、快进
审批流程 草稿→审核中→已通过→已拒绝
游戏角色 AI 巡逻→追击→攻击→逃跑
工作流引擎 任务的不同状态和流转
适用场景
场景说明
游戏角色状态 站立、行走、攻击、受伤、死亡
TCP 连接状态 LISTEN、ESTABLISHED、CLOSE_WAIT
订单状态 待支付→已支付→已发货→已完成
线程状态 NEW→RUNNABLE→BLOCKED→TERMINATED
电梯状态 运行、停止、开门、关门
优缺点
优点缺点
状态转换逻辑清晰 状态类数量增多
符合开闭原则(新增状态不改已有代码) 状态转换关系复杂时难维护
消除大量 if-else
状态转换规则集中管理
与策略模式的区别
  • 状态模式:状态转换由状态对象自身决定(如攻击后自动回到站立)
  • 策略模式:策略选择由客户端决定(如客户端选择用攻击策略还是防御策略)

18 策略模式 Strategy

一句话理解

定义一组可以互换的算法,让客户端在运行时选择用哪个。

生活类比

出行方式:去公司可以开车、坐公交、骑自行车。每种方式都是一个"策略",你可以根据天气、心情随时换。

出行策略接口
├── 开车策略(快但堵车)
├── 公交策略(慢但便宜)
└── 骑车策略(自由但累)

核心结构

Context(上下文,持有策略引用)


Strategy(策略接口)
├── ConcreteStrategyA
├── ConcreteStrategyB
└── ConcreteStrategyC

运行时可以随时切换策略。

策略模式 vs if-else
对比if-else策略模式
扩展性 每次加新策略都要改已有代码 加新策略只需写一个新类
符合原则 违反开闭原则 符合开闭原则
可读性 策略多时代码很长 每个策略独立一个类
真实应用案例
应用说明
Java Comparator Collections.sort(list, comparator) 通过不同的比较器实现不同排序
Java ThreadPool 不同的拒绝策略(AbortPolicy、CallerRunsPolicy 等)
Spring Resource 不同的资源加载策略(classpath、file、url)
支付方式 微信、支付宝、银行卡各自实现支付接口
折扣计算 满减、打折、会员价各自实现折扣接口
排序算法切换 冒泡、快排、归并按需选择
验证策略 手机号、邮箱、身份证不同验证逻辑
导航路线 最短路线、最快路线、最便宜路线
压缩算法 ZIP、GZIP、Snappy 按需选择
适用场景
场景说明
排序算法切换 冒泡、快排、归并按需选
支付方式 微信、支付宝、银行卡
折扣计算 满减、打折、会员价
验证策略 手机号、邮箱、身份证不同验证逻辑
导航路线 最短路线、最快路线、最便宜路线
优缺点
优点缺点
消除 if-else 策略类数量增多
运行时切换算法 客户端需要知道有哪些策略
符合开闭原则
算法复用

19 模板方法 Template Method

一句话理解

在父类中定义"算法骨架",子类填充具体步骤。

生活类比

泡茶和泡咖啡的流程是一样的:

  • 烧水 → 2. 冲泡 → 3. 倒进杯子 → 4. 加配料
  • 步骤一样,但"冲泡"和"加配料"不同(茶用茶叶,咖啡用咖啡粉)。父类定义流程骨架,子类填充差异部分。

    泡饮模板(父类):
    烧水() ← 通用
    冲泡() ← 抽象,子类实现
    倒进杯子() ← 通用
    加配料() ← 抽象,子类实现

    泡茶(子类):冲泡=放茶叶,加配料=加柠檬
    泡咖啡(子类):冲泡=放咖啡粉,加配料=加牛奶

    核心结构

    public abstract class AbstractClass {
    // 模板方法:定义算法骨架(用 final 防止子类修改)
    public final void templateMethod() {
    step1(); // 固定步骤
    step2(); // 可变步骤(抽象)
    step3(); // 可变步骤(抽象)
    hook(); // 钩子方法(可选覆盖)
    }

    private void step1() { /* 通用实现 */ }
    protected abstract void step2(); // 子类必须实现
    protected abstract void step3(); // 子类必须实现
    protected void hook() { } // 钩子,子类可选覆盖
    }

    关键设计
    • 模板方法:用 final 修饰,防止子类修改算法骨架
    • 抽象方法:子类必须实现的步骤
    • 钩子方法(Hook):有默认实现,子类可选覆盖,提供扩展点
    • 旅行案例:performTrip() 定义了 goTransport() → day1/2/3() → backTransport() 的流程,通用步骤在父类实现,具体行程由子类决定
    真实应用案例
    应用说明
    JUnit setUp() → testXxx() → tearDown()
    Servlet doGet() / doPost() 由子类实现
    Spring JdbcTemplate 模板封装了获取连接、执行 SQL、关闭资源的流程
    Spring Data JPA SimpleJpaRepository 定义了 CRUD 的模板
    AbstractQueuedSynchronizer AQS 定义了同步器的模板方法
    HttpServlet service() 方法定义模板,doGet/doPost 由子类实现
    Java IO InputStream 的 read() 方法是模板方法
    游戏循环 init() → update() → render() 的游戏主循环
    经典应用
    应用说明
    JUnit setUp() → testXxx() → tearDown()
    Servlet doGet() / doPost()
    数据库访问 连接() → 执行SQL() → 关闭()
    Spring JdbcTemplate 模板封装了获取连接、执行、关闭的流程
    流水线 步骤固定,每步的具体实现不同
    优缺点
    优点缺点
    代码复用(通用步骤写在父类) 增加了类的数量
    流程清晰,易维护 继承关系不够灵活
    符合开闭原则 父类添加抽象方法会影响所有子类
    钩子方法提供扩展点

    20 访问者模式 Visitor

    一句话理解

    在不修改元素类的前提下,为它们添加新操作。

    生活类比

    体检:医生(访问者)拿着体检表去各个科室(元素)检查。每个科室接受检查,但科室本身不用改——改的是医生的检查方式。

    体检报告(Visitor)去访问:
    内科(Element)→ 接受体检
    外科(Element)→ 接受体检
    眼科(Element)→ 接受体检

    核心结构

    Visitor(访问者) → 定义对每种元素的操作
    Visitable(可访问元素)→ 定义 accept(Visitor)

    双重分派:
    element.accept(visitor) ← 第一次分派:选择元素类型
    └── visitor.visit(this) ← 第二次分派:选择访问者类型

    关键设计
    • 双重分派:element.accept(visitor) 调用 visitor.visit(this),根据 element 和 visitor 的实际类型决定调用哪个 visit() 方法
    • 可访问元素:定义 accept(IVisitor visitor) 方法
    • 访问者接口:为每种元素类型定义一个 visit() 方法
    • 具体访问者:实现具体的访问逻辑(如生成报告)
    真实应用案例
    应用说明
    编译器 AST 遍历语法树,做类型检查、代码生成
    Eclipse JDT Java 编译器的 AST 访问
    XML DOM 遍历 NodeVisitor 遍历 XML 节点
    文件格式转换 PDF→Word、XML→JSON
    报表生成 对不同数据类型做不同统计
    代码分析工具 SonarQube 分析代码结构
    AST 解释器 遍历 AST 执行计算
    适用场景
    场景说明
    编译器 AST 遍历语法树,做类型检查、代码生成
    文件格式转换 PDF→Word、XML→JSON
    报表生成 对不同数据类型做不同统计
    对象结构稳定 元素类型不常变,但操作常变
    优缺点
    优点缺点
    新增操作不改元素类 新增元素类型要改所有 Visitor
    相关操作集中在一个访问者 双重分派理解成本高
    符合开闭原则(针对操作) 元素暴露内部细节给访问者

    21 解释器模式 Interpreter

    一句话理解

    为一种语言定义文法,并建立解释器来解析和执行语句。

    生活类比

    计算器输入 12 + 34,计算器(解释器)解析这个表达式,理解"12"是数字、"+"是运算符、"34"是数字,然后计算出结果 46。

    核心结构

    Expression(表达式)
    ├── TerminalExpression (终结符:具体的数字/变量)
    └── NonTerminalExpression (非终结符:组合表达式,如加法)

    Context(上下文):存储待解析的字符串和解析结果

    罗马数字解析器示例

    笔记中实现了一个罗马数字解析器,从高位到低位依次解析:

    • 千位表达式:M = 1000
    • 百位表达式:C=100, CD=400, D=500, CM=900
    • 十位表达式:X=10, XL=40, L=50, XC=90
    • 个位表达式:I=1, IV=4, V=5, IX=9

    解析 “MCMXCIV” → 1000 + 900 + 90 + 4 = 1994

    抽象语法树(AST)

    +
    / \\
    * C
    / \\
    A B

    解释器遍历这棵树,计算 (A * B) + C

    真实应用案例
    应用说明
    SQL 解析器 把 SQL 字符串解析成可执行的查询
    正则表达式引擎 解析正则模式进行匹配
    SpEL Spring 的表达式语言
    MVEL / OGNL 表达式语言框架
    数学公式引擎 计算器、Excel 公式
    SQL 注入防护 解析 SQL 语法防止注入
    DSL(领域特定语言) 自定义语言的解析执行
    配置文件解析 解析 XML/JSON/YAML 配置
    模板引擎 Thymeleaf、FreeMarker 的模板解析
    编译器 词法分析、语法分析
    适用场景
    场景说明
    SQL 解析 把 SQL 字符串解析成可执行的查询
    正则表达式 解析正则模式进行匹配
    数学表达式 计算器、公式引擎
    配置文件 解析 XML/JSON 配置
    编译器 词法分析、语法分析
    优缺点
    优点缺点
    语法容易修改和扩展 复杂语法的解释器很难维护
    每个文法规则是一个类 每条规则至少一个类,类数量爆炸
    易于实现简单文法 执行效率低于直接解析

    22 备忘录模式 Memento

    一句话理解

    保存对象的内部状态,以便日后恢复——就是"存档/读档"。

    生活类比

    玩游戏时的存档功能:打 Boss 前先存个档,打输了就读档重来。存档保存了游戏的完整状态(角色位置、血量、装备等)。

    存档(Memento):保存棋盘当前状态
    读档(Restore):恢复到之前的状态

    核心角色
    角色职责对应类
    Originator(发起人) 要被保存/恢复的对象 Chessboard(棋盘)
    Memento(备忘录) 保存状态的数据结构 ChessboardMemento
    Caretaker(管理者) 管理存档的容器 GameCore(用栈保存历史)
    关键设计
    • 保存状态:Originator 的 save() 方法创建 Memento,深拷贝防止外部修改
    • 恢复状态:Originator 的 restore() 方法从 Memento 读取状态
    • 历史管理:Caretaker 用 Stack 保存多个 Memento,支持多次撤销
    • 悔棋案例:每次落子前先存档(push),悔棋时读档(pop)
    真实应用案例
    应用说明
    文本编辑器 Ctrl+Z 撤销、Ctrl+Y 重做
    游戏存档 保存和恢复游戏进度
    浏览器历史 前进/后退按钮
    Git 版本控制 commit 保存快照,checkout 恢复
    数据库事务 操作失败时 rollback 到之前的状态
    虚拟机快照 保存虚拟机状态,随时恢复
    编辑器历史 IntelliJ IDEA 的 Local History
    Photoshop 历史记录 多步撤销
    Excel 撤销 多步撤销操作
    适用场景
    场景说明
    游戏存档/读档 保存和恢复游戏进度
    Ctrl+Z 撤销 编辑器的撤销功能
    数据库事务回滚 操作失败时恢复到之前的状态
    浏览器前进/后退 保存历史页面状态
    版本控制 Git 的 commit/checkout
    优缺点
    优点缺点
    不暴露对象内部细节就能保存状态 消耗内存(存档越多越大)
    简化了 Originator 的代码 深拷贝实现复杂
    符合单一职责和开闭原则
    保存/恢复操作简单
    常见面试题
    • 备忘录模式和序列化有什么区别? 序列化是把对象转成字节流用于持久化存储;备忘录模式是内存中保存状态快照用于恢复,关注点是"保存/恢复"这个行为模式。
    • 备忘录模式和命令模式的区别? 命令模式的 undo() 方法需要调用备忘录来保存和恢复状态,两者经常配合使用。

    五、模式之间的关系

    关系说明
    工厂模式 → 单例 可以用单例模式实现工厂类
    策略模式 ↔ 状态模式 都是替换行为,结构相似,区别在于状态转换的决策者
    代理模式 ↔ 装饰器模式 都包装原对象,结构相似,区别在于目的(控制访问 vs 增强功能)
    模板方法 → 工厂方法 模板方法中用工厂方法创建子类对象
    观察者模式 → 中介者模式 中介者模式中常用观察者来通知组件
    组合模式 ↔ 迭代器模式 组合模式的树形结构常用迭代器来遍历
    装饰器模式 ↔ 代理模式 装饰器侧重功能增强,代理侧重访问控制
    命令模式 → 备忘录模式 命令模式的 undo 通常需要备忘录保存状态
    责任链模式 ↔ 装饰器模式 都是链式处理,但责任链每个节点决定是否处理,装饰器层层增强
    访问者模式 ↔ 组合模式 访问者模式常用于遍历组合模式的树形结构

    六、模式速查表

    模式一句话关键词分类
    单例 Singleton 全局唯一实例 private 构造、static、getInstance 创建型
    工厂 Factory 通过工厂创建对象 switch、接口、多态 创建型
    建造者 Builder 分步构建复杂对象 链式调用、Director、Builder 创建型
    原型 Prototype 复制已有对象 Cloneable、clone、深/浅拷贝 创建型
    适配器 Adapter 接口转换 包装、转换、兼容 结构型
    桥接 Bridge 抽象与实现分离 组合、N+M 结构型
    组合 Composite 树形结构统一处理 递归、叶子+容器 结构型
    装饰器 Decorator 动态添加功能 包装、叠加、Java IO 结构型
    外观 Facade 简化复杂系统入口 Facade、封装 结构型
    代理 Proxy 控制对象访问 替身、权限、AOP 结构型
    享元 Flyweight 共享细粒度对象 内部状态、外部状态、缓存 结构型
    责任链 Chain of Responsibility 请求沿链传递 链表、处理或传递 行为型
    命令 Command 操作封装成对象 撤销、排队、宏命令 行为型
    迭代器 Iterator 统一遍历集合 hasNext、next、for-each 行为型
    中介者 Mediator 协调多对象交互 解耦、集中控制 行为型
    观察者 Observer 状态变化自动通知 推送、订阅、松耦合 行为型
    状态 State 不同状态不同行为 状态机、if-else 消除 行为型
    策略 Strategy 可互换的算法 运行时切换、消除 if-else 行为型
    模板方法 Template Method 父类定义骨架 final、hook、子类填充 行为型
    访问者 Visitor 不改元素加新操作 双重分派、accept+visit 行为型
    解释器 Interpreter 定义语言文法并解析 AST、终结符、非终结符 行为型
    备忘录 Memento 保存/恢复状态 存档/读档、Originator+Caretaker 行为型
    赞(0)
    未经允许不得转载:171主机测评 » 23个设计模式一文搞懂
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址