设计模式怎样识别?——从问题到解决方案的实战指南
📖 目录导读
为什么你需要识别设计模式?
场景还原:你接手了一段遗留代码,发现类结构混乱、方法冗长、对象间耦合严重,同事说“这里用了工厂模式”“那里是观察者”,但你看着代码一头雾水——哪些地方真的使用了设计模式?哪些只是巧合的结构相似?
核心观点:识别设计模式不是为了“贴标签”,而是为了理解设计意图、评估重构代价、预测扩展方向,真正的模式识别,本质是“从代码中读出架构师的设计决策”。
识别设计模式的五大核心维度
根据GoF《设计模式》及行业最佳实践,我们总结出以下判断标准:
1 意图匹配度(最优先)
- 问自己:这段代码解决的具体问题是什么?
- 如果是为了避免重复创建相同对象 → 检查是否使用了单例模式
- 如果是为了统一处理不同算法 → 检查是否使用了策略模式
- 如果是为了在不修改现有类的前提下增加功能 → 检查是否使用了装饰器模式
2 结构特征(代码静态分析)
- 类关系:
- 大量抽象类/接口 + 多个子类 → 策略、模板方法、工厂
- 一个类持有另一个类的引用,并调用其方法 → 代理、装饰、适配器
- 一个类维护多个观察者列表 → 观察者模式
- 方法签名:
createObject()→ 工厂方法handle(Request)→ 职责链getInstance()→ 单例
3 行为特征(运行时动态)
- 创建对象:
- 客户端通过工厂类获取对象,而非直接
new→ 抽象工厂/工厂方法 - 通过克隆已有对象创建新实例 → 原型模式
- 客户端通过工厂类获取对象,而非直接
- 通信方式:
- 对象间通过消息传递而非直接调用 → 观察者、中介者
- 请求被多次传递直到被处理 → 职责链
4 变化点位置(可扩展性分析)
- 哪里会变化?
- 算法变化 → 策略模式
- 对象结构变化 → 组合模式
- 行为附着对象变化 → 装饰器模式
- 创建过程变化 → 创建型模式
5 参与者数量与角色
- 模式命名提示:
- “工厂” → 至少1个工厂类 + 1个产品接口
- “适配器” → 1个适配器类 + 1个目标接口 + 1个被适配者
- “桥接” → 2个独立层级结构
经验公式:如果代码中至少有3个类存在“抽象-具体-抽象”的层级关系,且其中一个类通过委托其他类完成主要功能,那么大概率是某种结构型模式。
从代码气味反推设计模式
有时候设计模式并非显式实现,而是隐藏在“代码坏味”中,以下是常见模式与反向气味的映射:
| 代码气味(Code Smell) | 可能对应的模式 | 分析逻辑 |
|---|---|---|
大量 if-else / switch 分支 |
策略模式 / 状态模式 | 每个分支代表一种算法或状态,可提取为独立类 |
| 类继承层次过深(>4层) | 装饰器模式 / 组合模式 | 深继承通常意味着“动态组合”需求被强行用静态继承实现 |
| 一个类修改后需要改动多个类 | 观察者模式 / 中介者模式 | 对象间直接耦合导致变更传播,应通过事件/通知解耦 |
| 构造函数参数数量过多 | 建造者模式 | 复杂对象的构建过程应分步进行 |
多处重复的 new 操作 |
工厂模式 | 对象创建逻辑分散,应集中管理 |
实战技巧:先找出代码中最明显的“气味”,然后尝试用设计模式重构它——看重构后的结构是否与某个模式的标准UML图吻合。
实战案例:一个订单系统的模式识别过程
背景
某电商系统有一块订单处理代码,核心类 OrderProcessor 包含以下特征:
- 根据订单类型(普通、促销、批发)计算价格
- 支持优惠券、满减、运费规则叠加
- 每个规则都有独立的计算类:
CouponCalculator、FullReductionCalculator、ShippingCalculator - 客户端通过
OrderProcessor.setRule(new CouponCalculator())动态添加规则
识别步骤
Step 1:意图分析
- 问题:不同场景下价格计算逻辑不同 → 算法可替换 → 策略模式雏形
- 问题:多个规则可叠加 → 装饰器模式?但此处是组合执行而非装饰增强
Step 2:结构分析
- 存在
Calculator接口,多个实现类 → 策略模式标准结构 OrderProcessor持有Calculator引用 → 上下文 + 策略
Step 3:行为分析
- 客户端通过
setRule动态替换计算策略 → 策略模式运行态行为 - 多个策略通过链表或集合依次执行 → 职责链模式的变形(伪链)
Step 4:结论
- 主模式:策略模式(管理不同订单类型的计价策略)
- 辅助模式:装饰器模式(用于组合多个规则,注意此处需要通过装饰器包装策略链)
修正后的设计:
PriceCalculator (接口)
├── NormalPriceCalculator (策略)
├── PromotionPriceCalculator (策略)
└── RuleDecorator (装饰器基类)
├── CouponDecorator
├── FullReductionDecorator
└── ShippingDecorator
常见误判与避坑指南
❌ 误判1:看到工厂类就直接写“工厂模式”
- 真相:很多工厂只是普通工具类,并非实现工厂模式(需要满足“创建对象但不指定具体类”)。
- 验证方法:如果工厂类内部直接
new了具体对象,且客户端知道具体类名 → 只是“简单工厂”,不属于GoF模式。
❌ 误判2:看到接口 + 多个实现就说是“策略模式”
- 真相:策略模式要求上下文对象通过组合方式使用策略,且策略可互换,如果接口只用于提供默认实现,则是“模板方法”或“普通多态”。
❌ 误判3:代理模式和装饰器模式混为一谈
- 关键区别:代理模式控制访问(如权限、延迟加载),装饰器模式增强功能,判断依据:是否改变了原对象的行为路径。
✅ 自检清单
| 检查项 | 合格标准 |
|---|---|
| 模式意图 | 代码解决的问题与模式定义问题一致 |
| 参与者 | 模式角色齐全(如:策略需要Context + 策略接口 + 具体策略) |
| 变化点 | 符合该模式的“变化封装”原则 |
| 扩展方式 | 无需修改现有类即可增加新功能(开闭原则) |
问答环节
Q1:有没有一种方法能快速判断代码中是否存在设计模式?
A:有,看类名和方法名,如果类名包含 Factory、Builder、Adapter、Proxy 等词,大概率是模式实现,但更可靠的是:创建一个新需求,看改代码时是否需要改动多个位置。——如果只需要加一个新类即可完成,说明当前代码符合某种模式;如果需要改大量已有类,说明设计模式未被正确应用。
Q2:同一个代码段可能同时包含多种设计模式吗?
A:当然会,实际项目中,一个类可能同时扮演工厂模式的工厂角色和单例模式的唯一实例角色,识别时需要分层看:整体架构意图是什么?内部细节意图是什么?ThreadPoolExecutor 同时实现了命令模式(任务对象)和生产者-消费者模式(队列)。
Q3:看到网上很多教程说“这里用了设计模式但代码很烂”,怎么回事?
A:设计模式是工具,不是目的,烂代码强行套用模式会导致过度设计,识别时要注意:模式应该让代码更易理解、易扩展,而不是制造复杂性,如果一个模式加入后,代码反而更难懂了,那它可能是误用。
Q4:不记得所有模式的名称和定义,还能识别吗?
A:能,记住模式的核心原则即可:对变化进行封装,面向接口编程,组合优于继承,当你看到代码通过接口解耦了变化点、用组合替代了硬编码、允许动态扩展功能时,你已经在“无意识地应用模式思想”了,此时你再查GoF分类,会发现它对应某个具体名称。
延伸阅读:
- 《设计模式:可复用面向对象软件的基础》(GoF)
- 《重构:改善既有代码的设计》(Martin Fowler)
- 可以在
yourwebsite.com/design-patterns下载我们整理的模式对比表(PDF)
标签: 设计模式识别