我一开始把设计模式当成要背的名单,后来在项目里反复改同一段 if-else,才慢慢知道什么时候该用哪个。
Q1: 设计模式的历史?为什么前端也需要它?
1
1994 年: GoF 经典著作诞生
Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides 四人(被称为”四人帮”,GoF)出版了《设计模式:可复用面向对象软件的基础》,总结了 23 种经典设计模式,分为创建型、结构型、行为型三大类。
2
前端早期: jQuery 时代的隐性应用
jQuery 的
$.ajax 本质上是工厂模式,$(document).on() 是观察者模式。前端开发者每天都在用设计模式,只是当时没有人把它们”名字化”。3
组件化时代: 模式的显性化
React/Vue 的出现让前端从”操作 DOM”进化到”管理状态”。组件本身就是组合模式的体现,Hooks 的引入则让策略模式和装饰器模式变得无处不在。
设计模式不是”高级语法”,而是”高级沟通方式”——它让团队用同一套词汇描述代码结构。
🚫 没有设计模式
一家医院没有科室分类。所有病人都挤在一个大厅里,内科、外科、眼科混在一起。医生想找个骨科设备,得翻遍整个大厅。每来一个新医生,都要花一个月搞清楚”东西放在哪里”。
✅ 引入设计模式
有了科室分类后,骨科设备放在骨科,眼科设备放在眼科。新来的医生一入职就知道”骨折去骨科、眼睛疼去眼科”。不是设备变好了,而是组织方式变好了。
Q2: 工厂模式 —— 对象创建的标准化
工厂模式的核心思想是:把对象的创建过程封装起来,调用方不需要知道具体的创建细节。简单工厂 vs 工厂方法 vs 抽象工厂
工厂模式在前端框架中的应用
React 的createElement 就是一个工厂——它根据 type 参数创建不同的 VNode:
Q3: 观察者模式 —— 对象间的通信机制
观察者模式的核心思想是:当一个对象状态变化时,自动通知所有依赖它的对象。 它是前端框架响应式系统的基础。观察者模式 vs 发布-订阅模式
很多人会混淆这两个概念,但它们有本质区别:Vue 响应式系统中的观察者模式
Vue 的响应式系统是观察者模式的教科书级应用:Q4: 单例模式 —— 全局唯一的实例
单例模式的核心思想是:保证一个类只有一个实例,并提供一个全局访问点。单例模式在前端的应用场景
单例模式的本质是”控制实例数量”——不是语法特性,而是设计决策。
Q5: 策略模式 —— 消灭 if-else 的利器
策略模式的核心思想是:把算法(策略)封装成独立的函数或对象,让它们可以互相替换。策略模式在表单校验中的应用
策略模式 vs 状态模式
Q6: 装饰器模式 —— 不修改原始代码的扩展
装饰器模式的核心思想是:在不修改原始对象的情况下,给它添加新功能。 它是 AOP(面向切面编程)的基础。ES 装饰器语法(TC39 Stage 3)
装饰器模式 vs 继承
Q7: 代理模式 —— 控制对象的访问
代理模式的核心思想是:为一个对象提供代理,通过代理控制对原始对象的访问。 ES6 的Proxy 就是语言层面对代理模式的原生支持。
Vue 3 响应式中的代理模式
Vue 3 用Proxy 替代了 Vue 2 的 Object.defineProperty,这是代理模式的经典应用:
代理模式的常见变体
Q8: 组合模式 —— 树形结构的统一处理
组合模式的核心思想是:把对象组织成树形结构,让”单个对象”和”组合对象”具有一致的接口。 调用方不需要关心处理的是叶子节点还是组合节点。React 组件树就是组合模式
底层源码:框架是如何应用设计模式的?
Redux 内部用了哪些设计模式?
Redux 是设计模式的集大成者,它同时运用了多种经典模式:
Vue 的编译器中用了哪些设计模式?
Vue 3 的编译器大量运用了访问者模式(Visitor Pattern)来遍历和转换 AST:
十、及格回答 vs 满分回答
问题:“你在项目中用过哪些设计模式?”
及格回答(60-70分):“我用过单例模式做全局状态管理,用过观察者模式做事件通知,用过策略模式消除 if-else。”问题出在:背了定义但没有深度。 满分回答(90-100分):
我在项目中最常用的是策略模式和观察者模式。策略模式主要用于表单校验和支付方式选择——把每种校验规则封装成独立的策略函数,通过 Map 映射调用,新增规则只需要加一个函数,不需要修改已有的校验逻辑,符合开闭原则。[1]
观察者模式我在封装 EventBus 和处理 WebSocket 消息分发时用过。本质上就是发布-订阅的解耦——消息发送方不需要知道有多少订阅者,订阅者也可以随时取消订阅。Vue 的响应式系统底层也是这个模式,Dep 作为 Subject,Watcher 作为 Observer。[2]
我还会用装饰器模式做函数增强——比如给 API 请求函数统一添加日志、错误处理、loading 状态管理。不修改原始函数,通过组合的方式叠加功能。这在 Express/Koa 的中间件中也很常见,中间件本质上就是装饰器模式的链式应用。[3]
设计模式对我来说不是”背了 23 种然后找地方用”,而是”遇到问题时,发现这个问题别人已经解决过了,直接用现成的方案模板”。关键是理解每个模式解决的是什么问题,而不是它的类图长什么样。
常见追问
观察者模式和发布-订阅模式有什么区别?
观察者模式和发布-订阅模式有什么区别?
答题要点:
核心区别在于是否有”事件中心”这个中介。高分回答:
“观察者模式中,Subject 直接持有 Observer 的引用,状态变化时直接调用 Observer 的 update 方法。发布-订阅模式多了一个事件中心(Event Channel),发布者和订阅者完全解耦,互不知道对方的存在。前端最常见的
EventEmitter(Node.js)和 addEventListener(浏览器 DOM)都是发布-订阅模式,而不是严格的观察者模式。因为它们都有一个中间层(事件中心)来管理注册和触发。Vue 2 的 watch 更接近观察者模式——Watcher 直接被 Dep 持有。”策略模式和工厂模式经常一起出现,它们的关系是什么?
策略模式和工厂模式经常一起出现,它们的关系是什么?
答题要点:
工厂模式负责”创建”,策略模式负责”选择”。高分回答:
“工厂模式解决的是’创建什么对象’的问题,策略模式解决的是’用什么算法’的问题。在实际项目中,它们经常组合使用——工厂决定创建哪个策略,策略执行具体逻辑。举个例子:支付模块中,工厂根据用户选择创建’支付宝策略’或’微信策略’,然后策略模式执行各自的支付逻辑。工厂管创建,策略管行为,职责清晰。”
装饰器模式和代理模式有什么区别?
装饰器模式和代理模式有什么区别?
答题要点:
目的不同:装饰器是”增强功能”,代理是”控制访问”。高分回答:
“从结构上看,装饰器和代理很像——都是包装原始对象。但意图不同:
- 装饰器模式:关注功能增强,可以叠加多层装饰器,每层添加一个新功能(日志、缓存、重试等)
- 代理模式:关注访问控制,通常只有一层代理,目的是拦截、过滤或延迟访问
如何用设计模式实现一个中间件系统?
如何用设计模式实现一个中间件系统?
答题要点:
洋葱模型 = 装饰器模式 + 组合模式。高分回答:
“Koa 的中间件系统是装饰器模式的经典实现。核心是 每个中间件接收
compose 函数,它把多个中间件组合成一个洋葱模型:ctx(上下文)和 next(下一个中间件),可以在 next() 前后添加逻辑。这就是装饰器模式的链式组合——每一层包装上一层,添加新功能。”在大型项目中如何选择合适的设计模式?
在大型项目中如何选择合适的设计模式?
答题要点:
不是”选模式”,而是”识别问题”。高分回答:
“选择设计模式的流程应该是:先识别问题,再匹配模式。
- 如果发现大量 if-else 分支 → 策略模式
- 如果发现对象间一对多的通知需求 → 观察者模式
- 如果发现需要全局唯一实例 → 单例模式
- 如果发现需要不修改原代码就扩展功能 → 装饰器模式
- 如果发现需要控制对象的访问 → 代理模式
前端框架中有哪些设计模式的经典应用?
前端框架中有哪些设计模式的经典应用?
答题要点:
结合具体框架源码举例。高分回答:
“前端框架是设计模式的教科书:
- React: 组合模式(组件树)、策略模式(reconciler 的 diff 策略)、工厂模式(createElement)
- Vue: 观察者模式(响应式系统 Dep/Watcher)、代理模式(Proxy 实现 reactive)、策略模式(编译器的 transform 策略)
- Redux: 单例模式(Store)、观察者模式(subscribe)、中间件模式(applyMiddleware = 装饰器链)
- Express/Koa: 中间件模式(装饰器 + 组合模式的洋葱模型)”
🔥 设计模式会导致过度设计吗?什么时候不该用?
🔥 设计模式会导致过度设计吗?什么时候不该用?
答题要点:
过度设计比不用设计模式更危险。高分回答:
“会。设计模式最大的风险就是过度设计。我的判断标准是:
- YAGNI 原则(You Ain’t Gonna Need It):如果当前只有 2 个分支且确定不会扩展,用 if-else 就够了,不需要策略模式
- 三振原则:当你第三次写类似的 if-else 时,才考虑重构为策略模式
- 团队认知成本:如果团队成员不熟悉某个模式,引入它反而增加维护成本
- 一个简单的工具函数被包装了 3 层装饰器
- 只有一个实现的接口和抽象类
- 为了’可扩展性’预留了 10 个扩展点,但 3 年来一个都没用过
🔥 如何手写一个支持中间件的洋葱模型?
🔥 如何手写一个支持中间件的洋葱模型?
答题要点:
用递归 + Promise 实现,核心是 next 函数的传递。高分回答:
“洋葱模型的核心是:每个中间件可以决定’是否继续执行下一个’,且每个中间件可以在 next() 前后都有逻辑。这个模型的价值在于:每个中间件职责单一(日志、鉴权、缓存、错误处理),通过组合形成完整的请求处理管道。“
延伸阅读与引用参考
- 《设计模式:可复用面向对象软件的基础》:GoF 经典原著,23 种设计模式的权威定义。[1]
- Patterns.dev:现代前端设计模式的最佳实践,结合 React/Vue 实际案例。[2]
- MDN: Proxy:ES6 Proxy 的官方文档,理解代理模式的语言级支持。[3]
