设计模式实战:别背 23 种,先掌握这 6 个
设计模式不是背出来的知识点,而是「给常见问题起的名字」。真正高频的只有六七个——这篇按「痛点 → 形状 → 场景 → 误用」讲清它们,也讲清什么时候不该用。
一、先立观念:模式是「问题的名字」
GoF 二十三种设计模式,多数人的经历是:背了忘、忘了背,面试能说两句,写代码时一个都想不起来。问题不在记性,而在学习顺序——模式是从「反复出现的问题」里总结出来的名字,先有痛点,才有模式。反过来先背名字再找地方用,就是过度设计的起点。
模式真正的价值是沟通与识别:当你在一段代码里看到「每加一种类型就要改一次 switch」,能说出「这里是个策略问题」,比说「这里有点乱」有用得多。用之前先问三个问题:这里有没有稳定不变的变化点?同样的判断是否已经重复出现三次以上?抽象出来的收益是否大于它带来的间接层?三个都答"是",才值得引入模式。
模式不是目标,是解决特定问题的通用形状——先有痛点,再找模式。
二、六个高频模式:按痛点对号入座
| 模式 | 解决什么问题 | 一句话形状 | 常见误用 |
|---|---|---|---|
| 策略 Strategy | 分支随业务无限膨胀 | 把算法抽成可替换对象 | 只有一种实现也上策略 |
| 工厂 Factory | 创建逻辑散落、依赖具体类型 | 集中创建,调用方只认接口 | 为「将来可能换」提前抽象 |
| 观察者 Observer | 状态变化要通知多处 | 订阅发布,双向解耦 | 事件链路过长,难追踪 |
| 装饰器 Decorator | 要给对象叠加增强 | 层层包装,顺序可控 | 包装层数失控,调试困难 |
| 适配器 Adapter | 新旧接口不兼容、要隔离第三方 | 中间转换层 | 为核心代码自造外设接口 |
| 单例 Singleton | 全局唯一资源 | 全局唯一入口 | 滥用成全局状态,难以测试 |
策略:把「越长越多的 if」变成可替换对象
痛点:每上线一种新的计费规则、折扣类型、审批流程,就要回到同一个函数里再加一个分支。形状:把每个分支抽成独立对象(或函数),用一张表或注册表把它们组织起来。
// 之前:分支会一直长
if (type === "vip") { ... }
else if (type === "coupon") { ... }
// 之后:规则是数据,新增不改旧代码
const DISCOUNTS = { vip: calcVip, coupon: calcCoupon };
const discount = (type, amount) => (DISCOUNTS[type] ?? identity)(amount);
装饰器:不改原类,叠加能力
痛点:同一个处理逻辑要按场景叠加日志、缓存、鉴权、限流,全都塞进原函数会变成一团乱麻。形状:把增强写成一层层的包装,每一层只做一件事、只关心顺序。**Web 框架的「中间件」就是装饰器思想最成功的落地**——顺序可插拔、职责单一切。
适配器:把第三方 SDK 挡在核心代码之外
痛点:支付、短信、对象存储各家 SDK 接口各不相同,核心业务里到处是某个厂商的专有类型——换供应商等于重写业务。形状:为核心代码定义一个自己的接口,再为每个厂商写一个适配器把调用转过去。**核心代码从此不依赖任何外部 SDK,换供应商只写一个新的适配器。**
三、观察者、工厂、单例:三个「用对场景很香」的模式
- 观察者:订单创建后要触发发券、通知、积分、埋点——直接串行调用会让订单逻辑依赖所有下游。改成发布一个事件、下游各自订阅,新增下游不动订单代码。注意:事件链路别拉太长,否则排查问题要跳五个文件
- 工厂:当创建对象的逻辑开始散落(到处 new 具体类型、带一堆初始化参数),把创建收进一个地方。但要警惕「为将来可能换数据库而提前抽象」——没有第二种实现之前,工厂就是多余的一层
- 单例:只有真正意义上「全局唯一」的资源才配得上它(进程级配置、连接池管理器)。它的本质是全局状态,会让测试互相污染、依赖关系隐形——能用依赖注入解决的,不要用单例
四、什么时候不该用:四种过度设计
- 只有一个实现就上策略 / 工厂:多写三个文件换来"以后好扩展",而那个以后大概率不会来
- 为扩展性提前抽象:结果每个需求都要改三层代码才能落地,抽象的间接成本立刻显现
- 模式叠模式:工厂里套策略、策略里套观察者——读不懂的设计就是坏设计,跟用了多少模式无关
- 单例随手用:测试无法隔离、并发下共享可变状态、依赖关系藏起来——出问题时最难查的就是它
五、什么时候该抽象了:三个信号
与其问"该用哪个模式",不如看信号:三次法则(同一段逻辑第三次出现,就该抽象);稳定变化点(同一处代码总是因为同一类需求被反复修改);具体类型判断散落(同一个 switch 判断在多处重复出现)。信号出现,模式的名字自然会浮现——这时候引入才是"解决问题",而不是"套模板"。
六、在代码评审里怎么谈模式
评审时别用模式当教条压人("你这没用工厂模式"),而是把问题命名出来、把选择权交给作者:「这里每加一种类型都要改这段,用一张策略表会不会更好读?」「这段直接 new 了第三方客户端,要不要加个适配器把它挡在核心之外?」——描述好处、给出选项,比宣布结论更容易被接受,也更符合"让代码更好"而不是"让代码按我的写法来"。
速查卡
| 你看到的现象 | 可以考虑的模式 |
|---|---|
| 一个函数的分支随业务无限增长 | 策略(把分支抽成可替换对象) |
| 到处 new 具体类型、初始化逻辑散落 | 工厂(集中创建) |
| 状态变化要通知很多下游 | 观察者(事件订阅) |
| 同一条链路要按场景叠加增强 | 装饰器(中间件) |
| 核心代码到处是第三方 SDK 类型 | 适配器(转换层) |
| 需要进程内唯一资源 | 单例(优先改用依赖注入) |
写在最后
设计模式最好的用法,是把它当成一套给坏味道命名的词汇表:看到重复的分支、散落的创建、失控的通知链,你能叫出问题的名字,也知道成熟的解法长什么样。至于用不用、用几个,永远由"这个具体问题"决定——先解问题,后提模式;宁少一层,不多一件。
评论(0)