文章目录
- 前言
- 一、单例模式
-
- 1.1 定义
- 1.2 应用场景
- 二、观察者模式 vs 发布订阅模式
-
- 2.1 观察者模式
- 2.2 发布订阅模式
- 2.3 区别
- 三、策略模式
-
- 3.1 定义
- 3.2 表单验证
- 四、代理模式 vs 装饰器模式
-
- 4.1 代理模式
- 4.2 装饰器模式
- 4.3 区别
- 五、工厂模式
-
- 5.1 简单工厂
- 5.2 工厂方法
- 六、与 Redux、EventBus 的映射
-
- 6.1 Redux 中的设计模式
- 6.2 EventBus 中的设计模式
- 七、易混淆点
- 八、思考与练习
- 总结
前言
设计模式是解决特定问题的成熟方案,在前端开发中广泛应用。本篇会讲清楚:
- 单例模式
- 观察者模式 vs 发布订阅模式
- 策略模式
- 代理模式 vs 装饰器模式
- 工厂模式
- 与 Redux、EventBus 等的映射关系
一、单例模式
1.1 定义
保证一个类只有一个实例,并提供全局访问点。
class Singleton {
static instance = null
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton()
}
return Singleton.instance
}
}
const a = Singleton.getInstance()
const b = Singleton.getInstance()
console.log(a === b) // true
1.2 应用场景
// 1. 全局状态管理(Redux Store)
const store = createStore(reducer) // 全局唯一
// 2. 数据库连接池
class DatabasePool {
static instance = null
static getInstance() {
if (!DatabasePool.instance) {
DatabasePool.instance = new DatabasePool()
}
return DatabasePool.instance
}
}
// 3. 弹窗管理器
class ModalManager {
static instance = null
static getInstance() {
if (!ModalManager.instance) {
ModalManager.instance = new ModalManager()
}
return ModalManager.instance
}
}
二、观察者模式 vs 发布订阅模式
2.1 观察者模式
对象直接订阅目标,目标变化时通知所有观察者。
class Subject {
observers = []
subscribe(observer) {
this.observers.push(observer)
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer)
}
notify(data) {
this.observers.forEach(observer => observer.update(data))
}
}
class Observer {
update(data) {
console.log('收到通知:', data)
}
}
// 使用
const subject = new Subject()
const observer1 = new Observer()
const observer2 = new Observer()
subject.subscribe(observer1)
subject.subscribe(observer2)
subject.notify('数据更新了')
2.2 发布订阅模式
发布者和订阅者不直接通信,通过事件中心调度。
class EventEmitter {
events = {}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
}
off(event, callback) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
emit(event, …args) {
if (!this.events[event]) return
this.events[event].forEach(cb => cb(…args))
}
}
// 使用
const emitter = new EventEmitter()
emitter.on('data', (data) => console.log('收到:', data))
emitter.emit('data', { name: 'Alice' })
2.3 区别
| 耦合度 | 直接引用,紧耦合 | 事件中心解耦 |
| 通信方式 | 目标直接通知观察者 | 通过事件中心调度 |
| 典型应用 | Vue 响应式(watcher) | EventBus、Redux |
// 观察者模式:Vue 响应式
// dep(目标)直接通知 watcher(观察者)
// 发布订阅模式:EventBus
// 通过 eventEmitter 解耦发布者和订阅者
三、策略模式
3.1 定义
定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。
// 策略对象
const strategies = {
'A': (salary) => salary * 4,
'B': (salary) => salary * 2,
'C': (salary) => salary
}
// 上下文
function calculateBonus(level, salary) {
return strategies[level](salary)
}
calculateBonus('A', 10000) // 40000
calculateBonus('B', 10000) // 20000
3.2 表单验证
// 策略
const validators = {
required: (value) => value !== '' || '此项必填',
minLength: (min) => (value) => value.length >= min || `最少 ${min} 个字符`,
email: (value) => /\\S+@\\S+\\.\\S+/.test(value) || '邮箱格式不正确'
}
// 使用
function validate(value, rules) {
for (const rule of rules) {
const result = rule(value)
if (result !== true) return result
}
return true
}
validate('', [validators.required]) // '此项必填'
validate('ab', [validators.minLength(3)]) // '最少 3 个字符'
四、代理模式 vs 装饰器模式
4.1 代理模式
为对象提供代理以控制对对象的访问。
// 图片懒加载代理
class ImageLoader {
constructor(fileName) {
this.fileName = fileName
}
display() {
console.log(`显示图片: ${this.fileName}`)
}
}
class ProxyImage {
constructor(fileName) {
this.fileName = fileName
this.realImage = null
}
display() {
if (!this.realImage) {
this.realImage = new ImageLoader(this.fileName)
}
this.realImage.display()
}
}
// 使用:首次调用才创建真实对象
const image = new ProxyImage('photo.jpg')
image.display() // 创建 ImageLoader 并显示
image.display() // 直接使用缓存的 ImageLoader
4.2 装饰器模式
动态地给对象添加新功能。
// 原始函数
function fetchData() {
console.log('获取数据')
return { data: [1, 2, 3] }
}
// 装饰器:添加日志
function withLogging(fn) {
return function(…args) {
console.log('开始执行')
const result = fn(…args)
console.log('执行结束')
return result
}
}
// 装饰器:添加计时
function withTiming(fn) {
return function(…args) {
const start = Date.now()
const result = fn(…args)
console.log(`耗时: ${Date.now() – start}ms`)
return result
}
}
// 使用
const enhancedFetch = withTiming(withLogging(fetchData))
enhancedFetch()
4.3 区别
| 目的 | 控制访问 | 增强功能 |
| 接口 | 保持原接口 | 保持原接口 |
| 典型应用 | 懒加载、缓存、权限控制 | 日志、计时、数据转换 |
五、工厂模式
5.1 简单工厂
class UserFactory {
static create(type) {
switch (type) {
case 'admin':
return { role: 'admin', permissions: ['read', 'write', 'delete'] }
case 'user':
return { role: 'user', permissions: ['read'] }
default:
throw new Error('Unknown user type')
}
}
}
const admin = UserFactory.create('admin')
const user = UserFactory.create('user')
5.2 工厂方法
class Button {
render() {
throw new Error('必须实现 render 方法')
}
}
class PrimaryButton extends Button {
render() {
return '<button class="primary">Primary</button>'
}
}
class SecondaryButton extends Button {
render() {
return '<button class="secondary">Secondary</button>'
}
}
class ButtonFactory {
static create(type) {
switch (type) {
case 'primary': return new PrimaryButton()
case 'secondary': return new SecondaryButton()
default: throw new Error('Unknown button type')
}
}
}
const btn = ButtonFactory.create('primary')
btn.render() // '<button class="primary">Primary</button>'
六、与 Redux、EventBus 的映射
6.1 Redux 中的设计模式
// 1. 单例模式:Store 全局唯一
const store = createStore(rootReducer)
// 2. 发布订阅模式:subscribe / dispatch
store.subscribe(() => {
console.log('状态变化:', store.getState())
})
store.dispatch({ type: 'INCREMENT' })
// 3. 策略模式:reducer 根据 action.type 选择处理逻辑
function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT': return state + 1
case 'DECREMENT': return state – 1
default: return state
}
}
6.2 EventBus 中的设计模式
// 发布订阅模式
class EventBus {
events = {}
on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
}
emit(event, …args) {
if (!this.events[event]) return
this.events[event].forEach(cb => cb(…args))
}
off(event, callback) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
// Vue 2 的 EventBus
const bus = new Vue()
bus.$on('message', (data) => console.log(data))
bus.$emit('message', 'Hello')
七、易混淆点
八、思考与练习
1. 观察者模式和发布订阅模式的区别是什么?
解析:
- 观察者模式:目标直接维护观察者列表,变化时直接通知,紧耦合
- 发布订阅模式:通过事件中心解耦,发布者和订阅者不直接通信
2. Redux 使用了哪些设计模式?
解析:
- 单例模式:Store 全局唯一
- 发布订阅模式:subscribe / dispatch
- 策略模式:reducer 根据 action.type 选择处理逻辑
3. 如何实现一个简单的 EventBus?
class EventBus {
events = {}
on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
}
emit(event, …args) {
if (!this.events[event]) return
this.events[event].forEach(cb => cb(…args))
}
off(event, callback) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
4. 策略模式和 if-else 的区别是什么?
解析:
- if-else:逻辑耦合在一起,难以扩展和维护
- 策略模式:算法独立封装,可互换、可扩展、易于测试
5. 代理模式和装饰器模式的区别是什么?
解析:
- 代理模式:控制对对象的访问(懒加载、缓存、权限控制)
- 装饰器模式:动态添加功能(日志、计时、数据转换)
总结
- 单例模式:保证一个类只有一个实例(Redux Store、全局管理器)
- 观察者 vs 发布订阅:前者直接通知紧耦合,后者通过事件中心解耦
- 策略模式:算法封装可互换(表单验证、计算规则)
- 代理 vs 装饰器:前者控制访问,后者增强功能
- 工厂模式:封装对象创建逻辑(组件工厂、用户工厂)
- Redux:单例 + 发布订阅 + 策略模式的组合




