DESIGN PATTERNS · 编程思想 / 复用之道
设计模式速查
从创建型、结构型到行为型,梳理 12 个最常用的 GoF 设计模式及其适用场景,结合 Spring、JDK 等真实案例,帮你写出更可复用、更易维护的代码。
12经典模式
3大类型
35速查条目
📖 速查表
点击展开各小节
🏗️ 创建型设计模式
| 模式 | 类型 | 说明 | 适用场景 |
|---|---|---|---|
| Singleton 单例 | 创建型 | 确保一个类只有一个实例,并提供一个全局访问点。Java 中常用 enum 或双重检查锁定实现 | 配置管理器、线程池、数据库连接池、日志记录器 |
| Factory 工厂 | 创建型 | 定义一个创建对象的接口,让子类决定实例化哪个类。将客户端与具体类解耦 | 日志框架(SLF4J)、DAO 工厂、跨平台 UI 组件创建 |
| Builder 建造者 | 创建型 | 将复杂对象的构建与表示分离,允许通过相同的构建过程创建不同的表示。支持链式调用 | Lombok @Builder、构造复杂配置对象、SQL 查询构建器 |
| Prototype 原型 | 创建型 | 通过复制已有对象来创建新对象,而不是通过 new。Java 中实现 Cloneable | 对象创建成本高、需要保留对象副本的场景(如缓存快照) |
🔌 结构型设计模式
| 模式 | 类型 | 说明 | 适用场景 |
|---|---|---|---|
| Adapter 适配器 | 结构型 | 将一个类的接口转换成客户端期望的另一个接口,让不兼容的类可以一起工作 | 兼容旧系统接口、不同日志框架统一封装、Spring MVC 处理器适配器 |
| Proxy 代理 | 结构型 | 为另一个对象提供一个替身或占位符以控制对它的访问。分为静态代理和动态代理 | Spring AOP(JDK 动态代理 / CGLIB)、延迟加载、权限控制、日志记录 |
| Decorator 装饰器 | 结构型 | 动态地给对象添加额外的职责,比继承更灵活。Java I/O 流是经典示例 | BufferedInputStream 包装 FileInputStream、Spring Security 过滤器链 |
🔄 行为型设计模式
| 模式 | 类型 | 说明 | 适用场景 |
|---|---|---|---|
| Observer 观察者 | 行为型 | 定义对象间一对多的依赖关系,当一个对象状态变化时,所有依赖者都会收到通知并自动更新 | Spring 事件监听(ApplicationListener)、消息队列发布订阅、Vue 响应式系统 |
| Strategy 策略 | 行为型 | 定义一系列算法,将每个算法封装起来,并使它们可以互相替换。算法独立于使用它的客户端 | 支付方式选择、排序策略、压缩算法、认证方式(OAuth/JWT/Basic) |
| Template Method 模板方法 | 行为型 | 在父类中定义算法的骨架,将一些步骤延迟到子类中实现。子类可以重定义算法的特定步骤 | Spring 中的 JdbcTemplate、RestTemplate、工作流引擎 |
| Command 命令 | 行为型 | 将请求封装为对象,从而可以用不同的请求对客户进行参数化、队列请求或记录日志 | 操作撤销/重做、事务操作、任务队列、GUI 按钮回调 |
| State 状态 | 行为型 | 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类 | 订单状态流转、工作流审批、游戏角色状态 |
📜 六大设计原则
前五条缩写即 SOLID,加上迪米特与合成复用共七条;讲原则时重点讲清「违反信号」,比背定义更能体现理解。
| 原则 | 缩写 / 类别 | 一句话 | 违反信号 |
|---|---|---|---|
| 单一职责原则 SRP | S · 职责划分 | 一个类只应有一个引起它变化的原因,只做一件事 | 一个类同时负责格式化、网络请求与数据库存储,改任何功能都要动它 |
| 开闭原则 OCP | O · 扩展方式 | 对扩展开放、对修改关闭:新增功能靠加代码,不改稳定的旧代码 | 每加一种支付方式都要改主流程里的 switch / if-else 判断 |
| 里氏替换原则 LSP | L · 继承约束 | 所有使用父类的地方,换成子类后行为必须仍然正确 | 子类重写方法抛出 UnsupportedOperationException 或收紧前置条件 |
| 接口隔离原则 ISP | I · 接口设计 | 不强迫客户端依赖它们用不到的方法,胖接口应拆细 | 实现类被迫给一堆用不到的接口方法写空实现 |
| 依赖倒置原则 DIP | D · 依赖方向 | 依赖抽象(接口/抽象类)而非具体实现,高层不依赖低层细节 | 业务逻辑里直接 new MySQLDao(),换存储实现要改业务代码 |
| 迪米特法则 LoD | 最少知识 · 耦合控制 | 只与直接朋友通信,不与「陌生对象」深入交互,降低类间耦合 | 链式深挖 a.getB().getC().doSomething(),中间类变化牵连一片 |
| 合成复用原则 CRP | 组合优先 · 复用方式 | 优先用组合/聚合复用已有对象,其次才是继承 | 为了复用一个工具方法随意继承工具类,继承树越堆越高 |
🔗 模式选型与实战
| 条目 | 类型 / 场景 | 说明 / 辨析 | 典型示例 |
|---|---|---|---|
| 代理 vs 装饰器 易混对比 | 结构型 | 结构几乎相同,意图不同:代理重在「控制访问」(权限、延迟、远程),装饰器重在「动态增强职责」 | AOP 代理拦截方法调用;BufferedInputStream 层层包装增强读流 |
| 适配器 vs 桥接 易混对比 | 结构型 | 适配器是「事后补救」:让不兼容接口协同工作;桥接是「事前设计」:分离抽象与实现两个独立变化的维度 | 适配器统一多家日志框架 API;桥接如 JDBC Driver 架构(接口 ↔ 驱动实现) |
| Observer 观察者 JDK / Spring 实例 | 行为型 | 事件驱动解耦:发布方只管发事件,无需知道有哪些订阅方 | Spring ApplicationEvent + @EventListener 事件机制、Guava EventBus |
| Strategy 策略 JDK / Spring 实例 | 行为型 | 同一接口多实现按条件注入切换,消灭 if-else 分支 | Spring Resource 资源加载策略、Comparator 排序策略、支付渠道选择 |
| Proxy 代理 JDK / Spring 实例 | 结构型 | 不修改目标类织入事务、日志等横切逻辑 | Spring AOP:@Transactional 底层 JDK 动态代理 / CGLIB |
| Template Method 模板方法 JDK / Spring 实例 | 行为型 | 骨架固定(获取连接/异常处理/释放资源),差异步骤用回调或子类下沉 | Spring JdbcTemplate、RestTemplate、AQS 的模板骨架 |
🧑🍳 模式组合拳
单模式解决单点问题,真实系统靠组合:两两搭配各管一段,才是模式的正确打开方式。
| 组合 | 协作方式 | 实战示例 |
|---|---|---|
| 工厂 + 策略 最常用 | 启动时把全部策略注册进 Map(key → 实现),运行时按键取用,消除 if-else 分支 | 支付渠道路由:策略接口多实现 + 工厂按渠道码分发,新增渠道只加类不改主流程 |
| 装饰器 + 代理 | 结构同形、按意图分工:控制访问(鉴权/延迟/远程)选代理,叠加增强(缓冲/压缩)选装饰器,二者可串联 | 网关先代理做鉴权限流,请求进入服务后再用装饰器层层包装加日志与压缩 |
| 模板方法 + 钩子 框架骨架 | 父类定骨架流程,钩子方法(默认空实现或返回 true 的开关)让子类按需介入 | AQS 把 acquire 定成模板、tryAcquire 留成钩子;Servlet 的 service 分发到 doGet/doPost |
| 观察者 + 领域事件 | 聚合只管发布「已发生的事实」,监听方各自响应,模块之间零直接依赖 | 下单成功发 OrderPaid 事件:发通知、加积分、更新报表各挂各的监听,互不阻塞 |
| 组合 + 迭代器 树形遍历 | 组合模式统一「叶子与容器」的接口,迭代器统一遍历方式,调用方不感知层级 | 文件夹 / 菜单树递归渲染:对文件和目录一视同仁地 for-each,无需 if 判断类型 |
🚫 模式滥用警报
模式是药也是毒:出现以下任一信号,就要停下来重新审视设计。
| 警报信号 | 典型后果 | 纠偏方式 |
|---|---|---|
| 为模式而模式 首恶 | 简单需求硬套多层抽象,读一个功能要穿过三层接口 | 先写最简实现,等第二个真实变化点出现再重构引入模式 |
| 单例滥用全局状态 | 到处 Singleton 持有可变状态:测试难隔离、并发埋雷、依赖隐形 | 生命周期交给 IoC 容器管理,单例只留给真正无状态的组件 |
| 继承层级超 2 层 改组合 | 为复用一路 extends,改父类牵连整棵树,子类越写越像补丁 | 合成复用原则:改成组合 + 策略 / 回调注入,继承只留一层 |
| 中介者变上帝类 | 所有模块交互都汇入中介者,膨胀成千行大类、职责失焦 | 只让它协调真正的多对多网状交互,简单的两两调用直连即可 |
| 策略类爆炸 参数化替代 | 一个小分支一个类,实现类几十个,查代码像翻电话簿 | 无状态小策略改用枚举 / Lambda + Map 注册表,参数化替代建类 |