前端开发中的六种经典设计模式
设计模式是软件开发中经过验证的解决方案模板,能够帮助我们编写更加可维护、可扩展的代码。本文将深入介绍前端开发中最常用的六种设计模式:观察者模式、单例模式、工厂模式、策略模式、装饰者模式 和 代理模式。
设计模式概览
| 模式 | 核心思想 | 典型应用场景 |
|---|---|---|
| 观察者模式 | 一对多依赖,状态变化自动通知 | 事件总线、状态管理 |
| 单例模式 | 全局唯一实例 | 缓存管理、配置中心 |
| 工厂模式 | 统一接口创建对象 | 组件工厂、服务创建 |
| 策略模式 | 算法封装可互换 | 表单验证、支付方式 |
| 装饰者模式 | 动态扩展功能 | 请求拦截、日志增强 |
| 代理模式 | 控制对象访问 | 延迟加载、权限控制 |
一、观察者模式 (Observer)
模式定义
"观察者模式定义了对象之间一对多的依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知并自动更新。
核心实现
typescript// EventBus.ts - 事件总线实现 class EventBus { private events = new Map<string, Function[]>(); // 订阅事件 on(eventName: string, callback: Function) { if (!this.events.has(eventName)) { this.events.set(eventName, []); } this.events.get(eventName)!.push(callback); } // 触发事件 emit(eventName: string, ...args: any[]) { if (this.events.has(eventName)) { this.events.get(eventName)?.forEach(cb => cb(...args)); } else { console.error(`事件 ${eventName} 不存在`); } } // 取消订阅 off(eventName: string, callback?: Function) { if (!callback) { this.events.delete(eventName); } else { const callbacks = this.events.get(eventName)!.filter(cb => cb !== callback); this.events.set(eventName, callbacks); } } // 单次订阅 once(eventName: string, callback: Function) { const wrapper = (...args: any[]) => { callback(...args); this.off(eventName, wrapper); }; this.on(eventName, wrapper); } } export default EventBus;
React 组件中的应用
tsximport { useEffect, useState } from 'react'; import EventBus from './EventBus'; // 创建全局事件总线实例 const bus = new EventBus(); // 组件A:订阅用户登录事件 function HeaderComponent() { const [user, setUser] = useState<{ name: string } | null>(null); useEffect(() => { const handleLogin = (userData: { name: string }) => { console.log('用户登录,更新头部信息', userData); setUser(userData); }; bus.on('user-login', handleLogin); // 清理订阅 return () => bus.off('user-login', handleLogin); }, []); return ( <header> {user ? `欢迎,${user.name}` : '请登录'} </header> ); } // 组件B:触发登录事件 function LoginButton() { const handleLogin = () => { // 模拟登录成功 bus.emit('user-login', { name: 'Alice', id: 123 }); }; return <button onClick={handleLogin}>登录</button>; }
应用场景
- 跨组件通信:非父子组件间的消息传递
- 全局状态同步:用户登录状态、主题切换
- 解耦模块:降低组件间的直接依赖
二、单例模式 (Singleton)
模式定义
"单例模式确保一个类只有一个实例,并提供一个全局访问点。
核心实现
typescript// CacheManager.ts - 缓存管理器 class CacheManager { private static instance: CacheManager; private cache = new Map<string, any>(); private expireTime = new Map<string, number>(); // 私有构造函数,禁止外部实例化 private constructor() {} // 获取唯一实例 static getInstance(): CacheManager { if (!CacheManager.instance) { CacheManager.instance = new CacheManager(); } return CacheManager.instance; } // 设置缓存(支持过期时间) set(key: string, value: any, ttl?: number) { this.cache.set(key, value); if (ttl) { this.expireTime.set(key, Date.now() + ttl); } } // 获取缓存 get<T>(key: string): T | null { // 检查是否过期 const expireAt = this.expireTime.get(key); if (expireAt && Date.now() > expireAt) { this.delete(key); return null; } return this.cache.get(key) ?? null; } // 删除缓存 delete(key: string) { this.cache.delete(key); this.expireTime.delete(key); } // 清空所有缓存 clear() { this.cache.clear(); this.expireTime.clear(); } // 获取缓存大小 get size(): number { return this.cache.size; } } export default CacheManager;
使用示例
typescript// 任何地方获取的都是同一个实例 const cache1 = CacheManager.getInstance(); const cache2 = CacheManager.getInstance(); console.log(cache1 === cache2); // true // 设置缓存 cache1.set('user', { name: 'Alice' }); cache1.set('token', 'abc123', 3600000); // 1小时过期 // 获取缓存 console.log(cache2.get('user')); // { name: 'Alice' }
应用场景
- 全局配置管理:应用配置、环境变量
- 缓存服务:HTTP 请求缓存、数据缓存
- 日志服务:统一的日志收集器
- WebSocket 连接:确保只有一个连接实例
三、工厂模式 (Factory)
模式定义
"工厂模式通过提供一个创建对象的接口来创建对象,而无需指定具体的类。可以根据输入参数决定创建哪种类型的对象。
核心实现
typescript// 定义产品接口 interface Component { render(): JSX.Element; validate?(): boolean; } // 具体产品:文本输入框 class TextInput implements Component { constructor( private label: string, private placeholder: string ) {} render() { return ( <div className="form-field"> <label>{this.label}</label> <input type="text" placeholder={this.placeholder} /> </div> ); } validate() { return true; } } // 具体产品:下拉选择框 class SelectInput implements Component { constructor( private label: string, private options: { value: string; label: string }[] ) {} render() { return ( <div className="form-field"> <label>{this.label}</label> <select> {this.options.map(opt => ( <option key={opt.value} value={opt.value}> {opt.label} </option> ))} </select> </div> ); } } // 具体产品:日期选择器 class DatePicker implements Component { constructor(private label: string) {} render() { return ( <div className="form-field"> <label>{this.label}</label> <input type="date" /> </div> ); } } // 表单字段配置类型 interface FieldConfig { type: 'text' | 'select' | 'date'; label: string; placeholder?: string; options?: { value: string; label: string }[]; } // 工厂类 class FormFieldFactory { static create(config: FieldConfig): Component { switch (config.type) { case 'text': return new TextInput(config.label, config.placeholder || ''); case 'select': return new SelectInput(config.label, config.options || []); case 'date': return new DatePicker(config.label); default: throw new Error(`未知字段类型: ${config.type}`); } } // 批量创建 static createMany(configs: FieldConfig[]): Component[] { return configs.map(config => this.create(config)); } } export { FormFieldFactory, type FieldConfig };
使用示例
tsx// 动态生成表单 function DynamicForm() { const fieldConfigs: FieldConfig[] = [ { type: 'text', label: '姓名', placeholder: '请输入姓名' }, { type: 'select', label: '性别', options: [ { value: 'male', label: '男' }, { value: 'female', label: '女' } ]}, { type: 'date', label: '出生日期' } ]; const fields = FormFieldFactory.createMany(fieldConfigs); return ( <form> {fields.map((field, index) => ( <div key={index}>{field.render()}</div> ))} </form> ); }
应用场景
- 动态表单生成:根据配置创建不同类型的表单字段
- 图表组件:根据数据类型创建不同类型的图表
- 消息通知:根据类型创建不同样式的通知组件
四、策略模式 (Strategy)
模式定义
"策略模式定义了一系列算法,并将每一个算法封装起来,让它们可以互换。让算法的变化独立于使用算法的客户。
核心实现
typescript// 策略接口 interface ValidationStrategy { validate(value: string): boolean; message: string; } // 具体策略:必填验证 class RequiredStrategy implements ValidationStrategy { message = '此字段为必填项'; validate(value: string): boolean { return value.trim().length > 0; } } // 具体策略:邮箱验证 class EmailStrategy implements ValidationStrategy { message = '请输入有效的邮箱地址'; validate(value: string): boolean { const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/; return emailRegex.test(value); } } // 具体策略:手机号验证 class PhoneStrategy implements ValidationStrategy { message = '请输入有效的手机号码'; validate(value: string): boolean { const phoneRegex = /^1[3-9]d{9}$/; return phoneRegex.test(value); } } // 具体策略:密码强度验证 class PasswordStrategy implements ValidationStrategy { message = '密码需包含大小写字母和数字,至少8位'; validate(value: string): boolean { const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*d).{8,}$/; return passwordRegex.test(value); } } // 具体策略:身份证验证 class IdCardStrategy implements ValidationStrategy { message = '请输入有效的身份证号码'; validate(value: string): boolean { const idCardRegex = /(^d{15}$)|(^d{18}$)|(^d{17}(d|X|x)$)/; return idCardRegex.test(value); } } // 验证器上下文 class Validator { private strategies: ValidationStrategy[] = []; // 添加验证策略 addStrategy(strategy: ValidationStrategy): this { this.strategies.push(strategy); return this; // 支持链式调用 } // 执行验证 validate(value: string): { valid: boolean; errors: string[] } { const errors: string[] = []; for (const strategy of this.strategies) { if (!strategy.validate(value)) { errors.push(strategy.message); } } return { valid: errors.length === 0, errors }; } } // 策略工厂 const ValidationStrategies = { required: () => new RequiredStrategy(), email: () => new EmailStrategy(), phone: () => new PhoneStrategy(), password: () => new PasswordStrategy(), idCard: () => new IdCardStrategy() }; export { Validator, ValidationStrategies };
使用示例
typescript// 创建验证器并组合策略 const emailValidator = new Validator() .addStrategy(ValidationStrategies.required()) .addStrategy(ValidationStrategies.email()); const passwordValidator = new Validator() .addStrategy(ValidationStrategies.required()) .addStrategy(ValidationStrategies.password()); // 执行验证 console.log(emailValidator.validate('')); // { valid: false, errors: ['此字段为必填项', '请输入有效的邮箱地址'] } console.log(emailValidator.validate('test@example.com')); // { valid: true, errors: [] } console.log(passwordValidator.validate('weak')); // { valid: false, errors: ['密码需包含大小写字母和数字,至少8位'] } console.log(passwordValidator.validate('Strong123')); // { valid: true, errors: [] }
应用场景
- 表单验证:不同字段使用不同的验证规则
- 支付方式:微信、支付宝、银行卡等不同支付策略
- 排序算法:根据数据特点选择不同排序策略
- 价格计算:会员折扣、满减优惠等不同计价策略
五、装饰者模式 (Decorator)
模式定义
"装饰者模式在不修改对象结构的情况下,动态地为对象扩展功能。
核心实现
typescript// 基础请求接口 interface HttpService { request<T>(url: string, options?: RequestInit): Promise<T>; } // 基础请求类 class BaseHttpService implements HttpService { async request<T>(url: string, options?: RequestInit): Promise<T> { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } return response.json(); } } // 装饰器基类 abstract class HttpServiceDecorator implements HttpService { constructor(protected service: HttpService) {} abstract request<T>(url: string, options?: RequestInit): Promise<T>; } // 装饰器:缓存功能 class CacheDecorator extends HttpServiceDecorator { private cache = new Map<string, { data: any; timestamp: number }>(); private ttl: number; constructor(service: HttpService, ttl = 60000) { super(service); this.ttl = ttl; } async request<T>(url: string, options?: RequestInit): Promise<T> { const cacheKey = `${options?.method || 'GET'}-${url}`; // 检查缓存 const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.ttl) { console.log(`[Cache] 命中缓存: ${url}`); return cached.data; } // 请求并缓存 const data = await this.service.request<T>(url, options); this.cache.set(cacheKey, { data, timestamp: Date.now() }); console.log(`[Cache] 已缓存: ${url}`); return data; } } // 装饰器:重试功能 class RetryDecorator extends HttpServiceDecorator { private maxRetries: number; private delay: number; constructor(service: HttpService, maxRetries = 3, delay = 1000) { super(service); this.maxRetries = maxRetries; this.delay = delay; } async request<T>(url: string, options?: RequestInit): Promise<T> { let lastError: Error | null = null; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { return await this.service.request<T>(url, options); } catch (error) { lastError = error as Error; console.log(`[Retry] 第 ${attempt} 次请求失败,${ attempt < this.maxRetries ? `${this.delay}ms 后重试` : '已达最大重试次数' }`); if (attempt < this.maxRetries) { await new Promise(resolve => setTimeout(resolve, this.delay)); } } } throw lastError; } } // 装饰器:日志功能 class LoggingDecorator extends HttpServiceDecorator { async request<T>(url: string, options?: RequestInit): Promise<T> { const startTime = performance.now(); console.log(`[Log] 开始请求: ${options?.method || 'GET'} ${url}`); try { const data = await this.service.request<T>(url, options); const duration = (performance.now() - startTime).toFixed(2); console.log(`[Log] 请求成功: ${url} (${duration}ms)`); return data; } catch (error) { const duration = (performance.now() - startTime).toFixed(2); console.error(`[Log] 请求失败: ${url} (${duration}ms)`, error); throw error; } } } // 装饰器:Token 认证 class AuthDecorator extends HttpServiceDecorator { constructor( service: HttpService, private getToken: () => string | null ) { super(service); } async request<T>(url: string, options?: RequestInit): Promise<T> { const token = this.getToken(); const headers = new Headers(options?.headers); if (token) { headers.set('Authorization', `Bearer ${token}`); } return this.service.request<T>(url, { ...options, headers }); } } export { BaseHttpService, CacheDecorator, RetryDecorator, LoggingDecorator, AuthDecorator };
使用示例
typescript// 组合多个装饰器 const getToken = () => localStorage.getItem('token'); // 基础服务 -> 认证 -> 重试 -> 缓存 -> 日志 const httpService = new LoggingDecorator( new CacheDecorator( new RetryDecorator( new AuthDecorator( new BaseHttpService(), getToken ), 3, // 最大重试3次 1000 // 重试间隔1秒 ), 60000 // 缓存60秒 ) ); // 使用增强后的服务 const users = await httpService.request<User[]>('/api/users'); // 控制台输出: // [Log] 开始请求: GET /api/users // [Cache] 已缓存: /api/users // [Log] 请求成功: /api/users (125.32ms)
应用场景
- HTTP 请求增强:添加缓存、重试、日志、认证
- 组件功能扩展:为组件添加 loading、错误边界
- 数据处理管道:数据格式化、验证、转换
六、代理模式 (Proxy)
模式定义
"代理模式为其他对象提供一个代理对象以控制对该对象的访问。适用于延迟加载、权限控制、缓存等场景。
核心实现
typescript// 图片加载代理 - 实现懒加载 interface ImageLoader { load(url: string): Promise<HTMLImageElement>; } // 真实图片加载器 class RealImageLoader implements ImageLoader { async load(url: string): Promise<HTMLImageElement> { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = url; }); } } // 代理图片加载器 - 添加占位图和懒加载 class LazyImageProxy implements ImageLoader { private realLoader: RealImageLoader; private loadedImages = new Map<string, HTMLImageElement>(); private placeholderUrl: string; constructor(placeholderUrl = '/placeholder.svg') { this.realLoader = new RealImageLoader(); this.placeholderUrl = placeholderUrl; } async load(url: string): Promise<HTMLImageElement> { // 检查是否已加载 if (this.loadedImages.has(url)) { console.log(`[Proxy] 使用已加载图片: ${url}`); return this.loadedImages.get(url)!; } // 先返回占位图 console.log(`[Proxy] 显示占位图,开始加载: ${url}`); // 异步加载真实图片 const img = await this.realLoader.load(url); this.loadedImages.set(url, img); console.log(`[Proxy] 图片加载完成: ${url}`); return img; } } // API 访问代理 - 实现权限控制 interface ApiService { getData(endpoint: string): Promise<any>; postData(endpoint: string, data: any): Promise<any>; deleteData(endpoint: string): Promise<any>; } // 真实 API 服务 class RealApiService implements ApiService { async getData(endpoint: string) { const response = await fetch(endpoint); return response.json(); } async postData(endpoint: string, data: any) { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } async deleteData(endpoint: string) { const response = await fetch(endpoint, { method: 'DELETE' }); return response.json(); } } // 权限代理 type UserRole = 'admin' | 'editor' | 'viewer'; class AuthorizedApiProxy implements ApiService { private realService: RealApiService; private userRole: UserRole; // 权限配置 private permissions: Record<string, UserRole[]> = { getData: ['admin', 'editor', 'viewer'], postData: ['admin', 'editor'], deleteData: ['admin'] }; constructor(userRole: UserRole) { this.realService = new RealApiService(); this.userRole = userRole; } private checkPermission(action: string): boolean { const allowedRoles = this.permissions[action] || []; return allowedRoles.includes(this.userRole); } async getData(endpoint: string) { if (!this.checkPermission('getData')) { throw new Error('权限不足:无法读取数据'); } return this.realService.getData(endpoint); } async postData(endpoint: string, data: any) { if (!this.checkPermission('postData')) { throw new Error('权限不足:无法创建数据'); } return this.realService.postData(endpoint, data); } async deleteData(endpoint: string) { if (!this.checkPermission('deleteData')) { throw new Error('权限不足:无法删除数据'); } return this.realService.deleteData(endpoint); } } export { LazyImageProxy, AuthorizedApiProxy, type UserRole };
使用示例
typescript// 图片懒加载 const imageProxy = new LazyImageProxy('/loading.gif'); // 在视口内时才加载 const observer = new IntersectionObserver(async (entries) => { for (const entry of entries) { if (entry.isIntersecting) { const img = entry.target as HTMLImageElement; const realImg = await imageProxy.load(img.dataset.src!); img.src = realImg.src; observer.unobserve(img); } } }); // 权限控制 const viewerApi = new AuthorizedApiProxy('viewer'); const adminApi = new AuthorizedApiProxy('admin'); // viewer 只能读取 await viewerApi.getData('/api/posts'); // 成功 await viewerApi.postData('/api/posts', {}); // 抛出错误:权限不足 // admin 可以执行所有操作 await adminApi.getData('/api/posts'); // 成功 await adminApi.postData('/api/posts', {}); // 成功 await adminApi.deleteData('/api/posts/1'); // 成功
应用场景
- 延迟加载:图片懒加载、组件按需加载
- 权限控制:API 访问控制、功能权限
- 缓存代理:缓存计算结果、API 响应
- 日志代理:记录对象的访问和操作
七、核心原则
在实际项目中应用设计模式时,需要遵循以下原则:
1. 避免过度设计
typescript// 不好的做法:简单场景使用复杂模式 class SimpleDataFactory { static create(type: string) { if (type === 'user') return { name: '', age: 0 }; } } // 更好的做法:直接使用简单对象 const createUser = () => ({ name: '', age: 0 });
2. 关注代码可读性
设计模式是为了提升代码的可维护性,而非炫技。如果模式的引入让代码更难理解,那就不应该使用。
3. 结合框架特性
typescript// React Hooks 可以替代部分传统模式 // 观察者模式 -> useContext + useState const UserContext = createContext<User | null>(null); function useUser() { const context = useContext(UserContext); if (!context) throw new Error('useUser must be used within UserProvider'); return context; } // 单例模式 -> 模块级变量 // cache.ts const cache = new Map(); export const getCache = () => cache;
设计模式选择指南
需要解耦组件通信? → 观察者模式
需要全局唯一实例? → 单例模式
需要动态创建对象? → 工厂模式
需要可切换的算法? → 策略模式
需要动态扩展功能? → 装饰者模式
需要控制对象访问? → 代理模式
总结
"设计模式是前端开发中的重要工具,但不是万能解药。选择合适的模式需要根据具体场景权衡利弊。记住:简单的代码往往是最好的代码,只有当复杂性确实存在时,才引入设计模式来管理这种复杂性。
掌握这六种设计模式,能够帮助你编写出更加模块化、可测试、可维护的前端代码。在实际项目中,这些模式往往可以组合使用,发挥更大的威力。



