🎯 设计模式
设计模式是在特定上下文中,针对反复出现的软件设计问题,总结出的可复用的解决方案模板。
设计模式 = 前人踩坑后留下的“通用解法套路”
设计模式的核心思想是把会变的部分从不变的部分中分离出来,让代码更易扩展、维护、组合、复用。
设计模式的本质就是为了管理变化,当项目增长到需要团队协作、可复用组件、可配置业务逻辑时,设计模式就开始发挥价值了。
📌 设计模式三大分类
1. 创建型模式
关注点:对象是“怎么被创建出来的”, 创建型模式的核心不是“new”,而是控制变化的创建过程
创建型模式的核心目标:
- 隔离创建逻辑
- 控制实例化过程
- 解耦“用什么” vs “怎么 new”
常见模式:
- Factory(工厂)⭐⭐⭐⭐
- Abstract Factory(抽象工厂)
- Singleton(单例)⭐⭐⭐
- Builder(建造者)⭐⭐
- Prototype(原型)
2. 结构型模式
关注点:对象/组件如何组合成更大的结构,结构型模式解决的是:“东西怎么拼起来不乱”
核心目标:
- 用组合代替继承
- 保持结构灵活
- 避免强耦合
常见模式:
- Decorator(装饰者)⭐⭐⭐⭐⭐(HOC / Hooks)
- Composite(组合)⭐⭐⭐⭐⭐(组件树)
- Adapter(适配器)⭐⭐⭐(接口适配)
- Facade(外观)
- Proxy(代理)
3. 行为型模式
关注点:对象之间如何通信、如何协作,行为型模式解决的是:“逻辑怎么跑才不炸”
核心目标:
- 解耦行为调用
- 封装变化的行为
- 管理复杂流程
常见模式:
- Strategy(策略)⭐⭐⭐⭐⭐
- Observer(观察者)⭐⭐⭐⭐⭐
- Command(命令)⭐⭐⭐
- State(状态)⭐⭐⭐⭐
- Iterator(迭代器)
📌 设计模式六大原则
1. 单一职责原则
❌ 错误理解:“只能写一个函数” 或者 “文件越小越好”
✅ 正确理解:变化原因只有一个,一个模块只负责一件事
👉 前端关注的“逻辑和渲染分离”,就是 SRP 的直接应用
2. 开闭原则 🔥🔥🔥
✅正确理解:对扩展开放,对修改关闭
考核重点:新需求来了,是加代码,还是改旧代码
👉前端典型实践:策略模式、插件化、配置驱动
❌ 反面教材:if else 嵌套
if(type==='A'){}
else if(type==='B'){}
3. 里氏替换原则
✅正确理解:子类必须能替换父类,并且行为不破坏正确性
简单判断法:用父类的地方,换成子类,程序还能不能正常跑
📌 前端场景:组件继承(不推荐)、接口实现(TS interface)
4. 接口隔离原则
✅正确理解:不要强迫客户端依赖它不需要的接口
📌 前端体现:Props 精简、Hook 拆分、小而专的接口
❌ 错误:一个接口中包含太多当前不需要的字段
interface BigComponentProps {
a: string
b: string
c: string
}
✅ 正确:
interface AProps { a: string }
interface BProps { b: string }
5. 依赖倒置原则
✅正确理解:依赖抽象,而不是依赖具体实现
📌 人话:高层模块不该知道底层细节
前端场景:依赖接口而不是 class、注入 service、Context / Provider
function useValidator(strategy: ValidationStrategy) {}
6. 迪米特法则(最少知道原则)
✅正确理解:一个对象应尽量少地了解其他对象
📌 前端表现:不跨层访问状态、不 props.xxx.yyy.zzz、不直接操作别人的内部结构
📌 设计模式与React
1. 策略模式
🧠 本质:将算法封装成独立对象,可以在运行时动态切换。主要思想是把变化抽离,让逻辑可替换
前端最常见的复杂代码形态:
if (conditionA) {
doA()
} else if (conditionB) {
doB()
} else if (conditionC) {
doC()
}
策略模式把条件判断变成组合规则。
🧠 模式结构(简化版)
Context(上下文)
└── Strategy(策略接口)
├── ConcreteStrategyA
├── ConcreteStrategyB
└── ConcreteStrategyC
🧪 React 语境映射
| 角色 | React |
|---|---|
| Context | Hook/容器组件 |
| Strategy | 接口/函数 |
| ConcreteStrategy | 独立模块/实现 |
🌰 React 实战案例:多类型内容渲染
- 定义策略接口
export interface RenderStrategy {
render(value: string): JSX.Element
}
2. 具体策略实现
export const TextStrategy: RenderStrategy = {
render: value => <p>{value}</p>
}
export const ImageStrategy: RenderStrategy = {
render: value => <img src={value} />
}
export const LinkStrategy: RenderStrategy = {
render: value => <a href={value}>{value}</a>
}
3. 上下文(Context)使用策略
const strategyMap: Record<string, RenderStrategy> = {
text: TextStrategy,
image: ImageStrategy,
link: LinkStrategy,
}
export function Content({ type, value }: Props) {
const strategy = strategyMap[type]
return strategy ? strategy.render(value) : null
}
😊 优点:
- 消除条件分支
- 扩展成本低
- 逻辑清晰
😭 缺点:
- 策略数量可能增多
- 需要一定设计成本
❌ 以下情况不能使用策略模式:
- 逻辑永远不会变化
- 只有 1 种实现
- 为了“显得高级”而使用
2. 简单工厂模式
🧠 本质:把创建对象的逻辑抽离到一个地方,不让调用方写一堆 if/else。(封装创建逻辑)由一个工厂类(或函数)根据条件创建并返回不同的对象实例,调用方不关心对象如何创建,只关心“我要用哪个”。
模式结构:
Factory(工厂)
└── 根据参数创建不同产品
Product(抽象产品)
ConcreteProductA
ConcreteProductB
ConcreteProductC
| 角色 | React |
|---|---|
| Product | 接口 / 抽象类型 |
| ConcreteProduct | 具体实现 |
| Factory | 普通函数/类 |
前端典型痛点:
if (type === 'email') return new EmailValidator()
if (type === 'password') return new PasswordValidator()
✅ 改造成简单工厂模式
class ValidatorFactory {
static create(type: string) {
switch (type) {
case 'email':
return new EmailStrategy()
case 'password':
return new PasswordStrategy()
}
}
}
调用方:
const validator = ValidatorFactory.create('email')
😊 好处:
- 创建逻辑集中
- 新类型只改工厂,不改调用逻辑
⚠️ 工厂 + 策略
核心思想:工厂模式负责“创建谁”,策略模式负责“怎么用”。
export class ValidatorFactory {
static create(type: ValidatorType): Validator {
const map = {
email: EmailValidator,
password: PasswordValidator,
}
return new map[type]()
}
}
const validator = ValidatorFactory.create(type)
validator.validate(value)
😊 优点
- 创建逻辑集中
- 调用方解耦
- 易于维护
- 适合中小规模扩展
❌ 缺点
- 工厂本身会变胖
- 新增类型仍需修改工厂
- 违反严格意义的开闭原则
3. 观察者模式
🧠 本质:当一个对象改变状态,订阅它的其他对象都会自动得到通知。定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。
发布-订阅模式的基本实现,把“谁关心”和“谁改变”解耦。
💡 前端典型场景:
- 表单 / UI 状态变化通知
- WebSocket / SSE 消息分发
- Redux / Zustand / MobX 状态订阅
- 自定义事件系统 / Plugin 系统
🧠 核心结构
Subject(被观察者)
└── attach(observer)
└── detach(observer)
└── notify()
Observer(观察者)
└── update()
React 语境映射
| 角色 | React |
|---|---|
| Subject | 数据源 / 状态容器 / WebSocket |
| Observer | 组件 / Hook / 回调函数 |
不解藕的情况下:一旦新增订阅者,需要改原函数
function changeState(newValue) {
doSomething()
doSomethingElse()
updateUI()
}
React 自定义事件订阅系统
- 定义subject
class EventEmitter {
private listeners: Record<string, Callback[]> = {}
on(event: string, cb: Callback) {
if (!this.listeners[event]) this.listeners[event] = []
this.listeners[event].push(cb)
}
off(event: string, cb: Callback) {
this.listeners[event] = this.listeners[event]?.filter(fn => fn !== cb)
}
emit(event: string, data?: any) {
this.listeners[event]?.forEach(fn => fn(data))
}
}
export const emitter = new EventEmitter()
2. React 组件作为观察者
mport { useEffect, useState } from 'react'
import { emitter } from './EventEmitter'
export function UserStatus() {
const [status, setStatus] = useState('offline')
useEffect(() => {
const callback = (newStatus: string) => setStatus(newStatus)
emitter.on('statusChange', callback)
return () => emitter.off('statusChange', callback)
}, [])
return <p>用户状态: {status}</p>
}
3. 发布状态
emitter.emit('statusChange', 'online')
结合 React Hook 封装
这样每个组件都可以直接调用 useEvent('statusChange'),不关心底层实现
function useEvent<T>(event: string) {
const [data, setData] = useState<T | null>(null)
useEffect(() => {
const cb = (payload: T) => setData(payload)
emitter.on(event, cb)
return () => emitter.off(event, cb)
}, [event])
return data
}
✅优点
- 高度解耦:发布者不关心订阅者
- 扩展性好:新增订阅者无需改动发布者
- 支持动态订阅
❌缺点
- 订阅者过多时可能性能下降
- 调试链条复杂
- 生命周期管理不当可能内存泄漏
4. 装饰者模式
🧠 本质:不修改原对象的情况下给对象增加额外的行为。动态地给一个对象添加额外的职责,而不改变其原有结构。
给组件 / 对象贴“外挂”,又不动原本代码
💡前端场景:
- UI 组件增加 Loading / Error / 权限控制
- 高阶组件(HOC)
- Hook 封装的逻辑增强
- 日志 / 埋点 / 域名 / 配置增强
不使用装饰者模式:
function Button(props) {
if (props.loading) return <Spinner />
if (props.disabled) return <DisabledButton />
return <button>{props.label}</button>
}
🎯 模式结构
Component(抽象对象)
ConcreteComponent(具体对象)
Decorator(抽象装饰器)
ConcreteDecorator(具体装饰器)
| 角色 | React |
|---|---|
| Component | 原始组件 |
| ConcreteComponent | 具体实现组件 |
| Decorator | 高阶组件 / Hook 封装 |
| ConcreteDecorator | 具体增强逻辑 |
React Button 增强
有一个基础组件 Button,但有天要加 Loading:
function withLoading(Component) {
return function Wrapped(props) {
return props.loading ? <Spinner /> : <Component {...props} />
}
}
const ButtonWithLoading = withLoading(Button)
React Hooks 封装实现装饰者
function useLoading(initial = false) {
const [loading, setLoading] = useState(initial)
const wrap = (fn: () => void) => () => {
setLoading(true)
fn()
setLoading(false)
}
return { loading, wrap }
}
export function SubmitButton({ onClick }: { onClick: () => void }) {
const { loading, wrap } = useLoading()
return <Button label="提交" onClick={wrap(onClick)} />
}
✅优点
- 动态增强组件
- 不破坏原有组件
- 可复用、可组合
- 高扩展性
❌缺点
- HOC 嵌套过深容易调试困难
- Hook 封装过多可能逻辑分散
- 需要规范命名 / props
5. 组合模式
🧠 本质:把一组对象当成单个对象来使用,形成树状结构。将对象组合成树形结构以表示“部分-整体”层次结构,使客户端可以统一对待单个对象和组合对象。
无论是叶子还是容器,调用方式一样
💡 前端例子:
- UI 组件树(Layout / Form / Menu / Tree)
- 树状数据渲染(文件管理 / 分类)
- 菜单系统 / 多层嵌套
模型结构
Root
├── Child 1
├── Child 2
├── Child 2.1
└── Child 2.2
每个节点既可以是 叶子,也可以是 容器。
在 React:
<Layout>
<Header />
<Main>
<Sidebar />
<Content />
</Main>
</Layout>
组合设计就是把“节点 + 容器” 当成统一行为。
React 多级菜单组件
- 叶子组件
function MenuLeaf({ item }: { item: MenuItemType }) {
return <li>{item.label}</li>
}
2. 容器组件
function MenuComposite({ items }: { items: MenuItemType[] }) {
return (
<ul>
{items.map(item =>
item.children ? (
<li key={item.key}>
{item.label}
<MenuComposite items={item.children} />
</li>
) : (
<MenuLeaf key={item.key} item={item} />
)
)}
</ul>
)
}
3. 调用
const menuData: MenuItemType[] = [
{ key: 'home', label: '首页' },
{ key: 'system', label: '系统管理', children: [
{ key: 'user', label: '用户管理' },
{ key: 'role', label: '角色管理' }
] }
]
<MenuComposite items={menuData} />
- 任意层级都统一处理
- 叶子和组合节点调用方式一致
- 易扩展,新增层级无需改核心逻辑
✅优点
- 层级处理统一
- 易扩展
- 树状结构天然适配递归
- 菜单、树、布局
❌缺点
- 递归渲染层级过深可能性能问题
- 状态管理需注意(展开/选中等)
6. 命令模式
🧠 本质:将执行动作封装为对象,使得命令可存储、撤销、重做。将一个请求封装为一个对象,从而使你可以用不同的请求、队列或日志来参数化客户端;同时支持可撤销操作。
把动作变成对象,让调用者不关心具体实现
💡 前端例子
- 富文本编辑器(撤销/重做)
- 可撤销操作(Canvas / 图表编辑)
- VSCode 插件 / IDE 命令
- 按钮动作统一管理
不实用命令模式:
function handleClick(action: string) {
if (action === 'save') save()
if (action === 'delete') deleteItem()
if (action === 'copy') copyItem()
}
🧠 模式结构
Command(抽象命令)
ConcreteCommand(具体命令)
Invoker(调用者)
Receiver(执行者)
React 按钮操作命令
1. 命令执行者
export class TextReceiver {
private text = ''
setText(newText: string) {
this.text = newText
console.log('文本更新为:', this.text)
}
getText() { return this.text }
}
2. 具体命令
export class UpdateTextCommand implements Command {
constructor(private receiver: TextReceiver, private newText: string, private prevText?: string) {}
execute() {
this.prevText = this.receiver.getText()
this.receiver.setText(this.newText)
}
undo() {
if (this.prevText !== undefined) this.receiver.setText(this.prevText)
}
}
3. 调用者
import { useState } from 'react'
export function CommandDemo() {
const [receiver] = useState(() => new TextReceiver())
const [history, setHistory] = useState<Command[]>([])
const executeCommand = (cmd: Command) => {
cmd.execute()
setHistory(prev => [...prev, cmd])
}
const undo = () => {
const last = history[history.length - 1]
last?.undo?.()
setHistory(prev => prev.slice(0, -1))
}
return (
<div>
<button onClick={() => executeCommand(new UpdateTextCommand(receiver, 'Hello'))}>
写 Hello
</button>
<button onClick={() => executeCommand(new UpdateTextCommand(receiver, 'World'))}>
写 World
</button>
<button onClick={undo}>撤销</button>
</div>
)
}
✅优点
- 调用者与执行者解耦
- 支持撤销/重做
- 支持操作队列 / 日志
- 高可扩展性
❌缺点
- 命令对象数量可能增多
- 设计稍微复杂
7. 状态模式
🧠 本质:明确把状态和行为分开,把状态的变化关系写成表或图。允许一个对象在其内部状态改变时改变其行为,对象看起来像改变了其类。
把状态和状态对应的行为封装起来,让对象根据状态自己切换行为
💡 典型场景:
- 表单多步骤流程(填写 → 提交 → 成功/失败)
- 复杂组件的 UI 状态切换(loading / error / empty / success)
- 动画/交互状态管理
- 游戏或 Canvas UI 状态管理
🧠 模式结构
Context(上下文对象)
└── State(抽象状态接口)
├── ConcreteStateA
├── ConcreteStateB
└── ConcreteStateC
表单状态机
interface FormState {
submit: () => void
render: () => JSX.Element
}
1. 具体状态实现
class IdleState implements FormState {
constructor(private context: FormContext) {}
submit() {
this.context.setState(new SubmittingState(this.context))
}
render() {
return <button>提交</button>
}
}
class SubmittingState implements FormState {
constructor(private context: FormContext) {}
submit() {
// 正在提交,不可再提交
}
render() {
return <button disabled>提交中...</button>
}
}
class SuccessState implements FormState {
constructor(private context: FormContext) {}
submit() {
// 提交完成,不可再提交
}
render() {
return <p>提交成功 🎉</p>
}
}
2. Context(状态容器)
class FormContext {
private state: FormState
constructor() {
this.state = new IdleState(this)
}
setState(state: FormState) {
this.state = state
}
submit() {
this.state.submit()
}
render() {
return this.state.render()
}
}
3. React UI 使用
export function FormComponent() {
const [context] = useState(() => new FormContext())
return (
<div>
{context.render()}
<button onClick={() => context.submit()}>提交</button>
</div>
)
}
✅优点
- 消除条件分支
- 状态逻辑集中封装
- 易于扩展新状态
- 面试高频题:表单 / 流程 / 组件状态
❌缺点
- 状态类数量可能增加
- 初期设计成本稍高
- 对小型组件可能过度设计
📌 设计模式之间的关系
| 分类 | 前端高频模式 | 核心作用 |
|---|---|---|
| 创建型 | 工厂模式、单例模式、建造者 | 控制对象创建,封装变化 |
| 结构型 | 装饰者、组合、适配器、代理、外观 | 组织对象/组件结构,实现复用和解耦 |
| 行为型 | 策略、观察者、状态、命令、迭代器 | 管理对象间行为和通信,封装算法或操作 |
| 模式 | 解决什么问题 | 前端常见用途 |
|---|---|---|
| Factory | 对象创建 | 消除 if-else |
| Strategy | 多种行为可替换 | 校验/渲染策略 |
| Observer | 发布订阅 | 事件 / 状态变化 |
| Decorator | 动态增强 | HOC / 逻辑增强 |
| Composite | 递归结构 | 组件树结构 |
| Command | 行为封装 | 可撤销命令 |
| State Machine | 状态转换 | UI/流程状态 |
- 原则导向
- 模式不是单独存在的,它们都是原则(SRP、OCP、DIP 等)的实现工具
- 组合使用是高级前端架构常态
- 模式组合实战
- 工厂 + 策略 → 动态生成行为
- 观察者 + 状态 → 状态变化驱动 UI
- 命令 + 状态 / 装饰者 → 操作解耦 + UI 增强
- 组合 + 策略 / 装饰者 → 树状结构灵活渲染