第三部分:行为型模式
行为型模式关注对象之间的通信,帮助你实现更灵活的交互逻辑
目录
观察者模式
💡 模式定义
观察者模式(Observer Pattern)定义对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
🤔 为什么需要观察者模式?
问题场景:多个组件需要响应同一个数据变化
假设你的电商应用有购物车功能,当购物车商品数量变化时,需要更新多个地方:
❌ 不使用观察者模式的痛点
// CartManager.js - 购物车管理器
class CartManager {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
// 耦合:直接调用各个组件的更新方法
updateCartBadge(this.items.length);
updateCartSidebar(this.items);
updateCheckoutButton(this.items.length > 0);
updateTotalPrice(this.calculateTotal());
// 每次添加新功能都要修改这里!
// updateRecommendations(this.items);
// updateCouponHints(this.items);
}
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId);
// 又要重复调用所有更新方法
updateCartBadge(this.items.length);
updateCartSidebar(this.items);
updateCheckoutButton(this.items.length > 0);
updateTotalPrice(this.calculateTotal());
}
calculateTotal() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
}
// 各个独立的更新函数
function updateCartBadge(count) {
document.querySelector('.cart-badge').textContent = count;
}
function updateCartSidebar(items) {
// 更新侧边栏
}
function updateCheckoutButton(enabled) {
document.querySelector('.checkout-btn').disabled = !enabled;
}
function updateTotalPrice(total) {
document.querySelector('.total-price').textContent = `¥${total}`;
}
问题:
- 紧耦合:CartManager 必须知道所有需要更新的组件
- 难以扩展:新增功能需要修改 CartManager
- 难以维护:一个地方改动可能影响多个功能
- 违反单一职责:CartManager 既管理数据又负责通知
✅ 使用观察者模式解决
// EventEmitter.js - 事件发射器(观察者模式实现)
class EventEmitter {
constructor() {
this.events = new Map();
}
// 订阅事件(注册观察者)
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(listener);
// 返回取消订阅函数
return () => this.off(event, listener);
}
// 取消订阅
off(event, listener) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
// 触发事件(通知所有观察者)
emit(event, ...args) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
listeners.forEach(listener => {
try {
listener(...args);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
});
}
// 单次订阅
once(event, listener) {
const onceWrapper = (...args) => {
listener(...args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
}
export default EventEmitter;
// CartManager.js - 购物车管理器(发布者)
import EventEmitter from './EventEmitter';
class CartManager extends EventEmitter {
constructor() {
super();
this.items = [];
}
addItem(item) {
this.items.push(item);
// 只负责发布事件,不关心谁在监听
this.emit('itemAdded', item);
this.emit('cartChanged', this.items);
}
removeItem(itemId) {
const removedItem = this.items.find(item => item.id === itemId);
this.items = this.items.filter(item => item.id !== itemId);
this.emit('itemRemoved', removedItem);
this.emit('cartChanged', this.items);
}
updateQuantity(itemId, quantity) {
const item = this.items.find(item => item.id === itemId);
if (item) {
item.quantity = quantity;
this.emit('quantityChanged', item);
this.emit('cartChanged', this.items);
}
}
clear() {
this.items = [];
this.emit('cartCleared');
this.emit('cartChanged', this.items);
}
getItems() {
return this.items;
}
calculateTotal() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
}
export default new CartManager();
// CartBadge.jsx - 购物车徽章组件(观察者)
import React from 'react';
import cartManager from './CartManager';
function CartBadge() {
const [count, setCount] = React.useState(cartManager.getItems().length);
React.useEffect(() => {
// 订阅购物车变化
const unsubscribe = cartManager.on('cartChanged', (items) => {
setCount(items.length);
});
// 清理订阅
return unsubscribe;
}, []);
return (
<div className="cart-badge">
<span className="icon">🛒</span>
{count > 0 && <span className="count">{count}</span>}
</div>
);
}
export default CartBadge;
// CartSidebar.jsx - 购物车侧边栏组件(观察者)
import React from 'react';
import cartManager from './CartManager';
function CartSidebar() {
const [items, setItems] = React.useState(cartManager.getItems());
const [total, setTotal] = React.useState(cartManager.calculateTotal());
React.useEffect(() => {
const unsubscribe = cartManager.on('cartChanged', (newItems) => {
setItems(newItems);
setTotal(cartManager.calculateTotal());
});
return unsubscribe;
}, []);
return (
<div className="cart-sidebar">
<h3>购物车</h3>
{items.length === 0 ? (
<p>购物车是空的</p>
) : (
<>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} x {item.quantity} - ¥{item.price * item.quantity}
<button onClick={() => cartManager.removeItem(item.id)}>删除</button>
</li>
))}
</ul>
<div className="total">总计: ¥{total}</div>
<button className="checkout-btn">去结算</button>
</>
)}
</div>
);
}
export default CartSidebar;
// ProductCard.jsx - 商品卡片组件
import React from 'react';
import cartManager from './CartManager';
function ProductCard({ product }) {
const handleAddToCart = () => {
cartManager.addItem({
id: product.id,
name: product.name,
price: product.price,
quantity: 1,
});
};
return (
<div className="product-card">
<h4>{product.name}</h4>
<p>¥{product.price}</p>
<button onClick={handleAddToCart}>加入购物车</button>
</div>
);
}
export default ProductCard;
// Analytics.js - 数据分析(观察者)
import cartManager from './CartManager';
class Analytics {
constructor() {
// 订阅购物车事件
cartManager.on('itemAdded', (item) => {
this.trackEvent('cart_item_added', {
item_id: item.id,
item_name: item.name,
price: item.price,
});
});
cartManager.on('itemRemoved', (item) => {
this.trackEvent('cart_item_removed', {
item_id: item.id,
});
});
}
trackEvent(eventName, data) {
console.log('Analytics:', eventName, data);
// 发送到分析服务器
}
}
export default new Analytics();
效果:
- ✅ 解耦:CartManager 不知道谁在监听
- ✅ 易于扩展:新增功能只需订阅事件
- ✅ 单一职责:每个组件只关心自己的逻辑
- ✅ 动态订阅:可以随时添加/移除观察者
🎯 React 中的观察者模式
使用 Context + useReducer 实现
// CartContext.jsx - 使用 Context 实现观察者模式
import React from 'react';
const CartContext = React.createContext();
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload],
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload),
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
),
};
case 'CLEAR':
return {
...state,
items: [],
};
default:
return state;
}
}
export function CartProvider({ children }) {
const [state, dispatch] = React.useReducer(cartReducer, { items: [] });
const addItem = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
const removeItem = (itemId) => {
dispatch({ type: 'REMOVE_ITEM', payload: itemId });
};
const updateQuantity = (itemId, quantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id: itemId, quantity } });
};
const clear = () => {
dispatch({ type: 'CLEAR' });
};
const calculateTotal = () => {
return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
};
return (
<CartContext.Provider value={{
items: state.items,
addItem,
removeItem,
updateQuantity,
clear,
calculateTotal,
}}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => {
const context = React.useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within CartProvider');
}
return context;
};
// 使用 Context
function App() {
return (
<CartProvider>
<Header />
<ProductList />
<CartSidebar />
</CartProvider>
);
}
function CartBadge() {
const { items } = useCart(); // 自动订阅 items 变化
return (
<div className="cart-badge">
<span className="icon">🛒</span>
{items.length > 0 && <span className="count">{items.length}</span>}
</div>
);
}
function ProductCard({ product }) {
const { addItem } = useCart();
return (
<div className="product-card">
<h4>{product.name}</h4>
<p>¥{product.price}</p>
<button onClick={() => addItem({ ...product, quantity: 1 })}>
加入购物车
</button>
</div>
);
}
🏗️ 真实业务场景
场景1:实时通知系统
// NotificationManager.js - 通知管理器
import EventEmitter from './EventEmitter';
class NotificationManager extends EventEmitter {
constructor() {
super();
this.notifications = [];
}
// 显示通知
show(notification) {
const id = Date.now();
const notif = {
id,
...notification,
timestamp: new Date(),
};
this.notifications.push(notif);
this.emit('notificationAdded', notif);
// 自动关闭
if (notification.duration) {
setTimeout(() => {
this.dismiss(id);
}, notification.duration);
}
return id;
}
// 关闭通知
dismiss(id) {
const notification = this.notifications.find(n => n.id === id);
if (notification) {
this.notifications = this.notifications.filter(n => n.id !== id);
this.emit('notificationDismissed', notification);
}
}
// 清空所有通知
clearAll() {
this.notifications = [];
this.emit('notificationsCleared');
}
// 便捷方法
success(message, duration = 3000) {
return this.show({ type: 'success', message, duration });
}
error(message, duration = 5000) {
return this.show({ type: 'error', message, duration });
}
warning(message, duration = 4000) {
return this.show({ type: 'warning', message, duration });
}
info(message, duration = 3000) {
return this.show({ type: 'info', message, duration });
}
}
export default new NotificationManager();
// NotificationContainer.jsx - 通知容器组件
import React from 'react';
import notificationManager from './NotificationManager';
function NotificationContainer() {
const [notifications, setNotifications] = React.useState([]);
React.useEffect(() => {
const handleAdded = (notification) => {
setNotifications(prev => [...prev, notification]);
};
const handleDismissed = (notification) => {
setNotifications(prev => prev.filter(n => n.id !== notification.id));
};
const handleCleared = () => {
setNotifications([]);
};
notificationManager.on('notificationAdded', handleAdded);
notificationManager.on('notificationDismissed', handleDismissed);
notificationManager.on('notificationsCleared', handleCleared);
return () => {
notificationManager.off('notificationAdded', handleAdded);
notificationManager.off('notificationDismissed', handleDismissed);
notificationManager.off('notificationsCleared', handleCleared);
};
}, []);
return (
<div className="notification-container">
{notifications.map(notification => (
<Notification
key={notification.id}
notification={notification}
onClose={() => notificationManager.dismiss(notification.id)}
/>
))}
</div>
);
}
function Notification({ notification, onClose }) {
const typeStyles = {
success: { background: '#52c41a', color: '#fff' },
error: { background: '#ff4d4f', color: '#fff' },
warning: { background: '#faad14', color: '#fff' },
info: { background: '#1890ff', color: '#fff' },
};
return (
<div className="notification" style={typeStyles[notification.type]}>
<span>{notification.message}</span>
<button onClick={onClose}>×</button>
</div>
);
}
export default NotificationContainer;
// 在应用中使用
import notificationManager from './NotificationManager';
function LoginForm() {
const handleLogin = async (credentials) => {
try {
await loginAPI(credentials);
notificationManager.success('登录成功!');
} catch (error) {
notificationManager.error('登录失败:' + error.message);
}
};
// ...
}
场景2:WebSocket 实时数据
// WebSocketManager.js - WebSocket 管理器
import EventEmitter from './EventEmitter';
class WebSocketManager extends EventEmitter {
constructor() {
super();
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect(url) {
this.ws = new WebSocket(url);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.emit('connected');
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// 根据消息类型触发不同事件
switch (data.type) {
case 'user_online':
this.emit('userOnline', data.payload);
break;
case 'user_offline':
this.emit('userOffline', data.payload);
break;
case 'new_message':
this.emit('newMessage', data.payload);
break;
case 'order_status_changed':
this.emit('orderStatusChanged', data.payload);
break;
default:
this.emit('message', data);
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.emit('disconnected');
this.reconnect(url);
};
}
reconnect(url) {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`Reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
setTimeout(() => {
this.connect(url);
}, 1000 * this.reconnectAttempts);
} else {
this.emit('reconnectFailed');
}
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.error('WebSocket is not connected');
}
}
disconnect() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
}
export default new WebSocketManager();
// OnlineUsers.jsx - 在线用户列表
import React from 'react';
import wsManager from './WebSocketManager';
function OnlineUsers() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
const handleUserOnline = (user) => {
setUsers(prev => [...prev, user]);
};
const handleUserOffline = (user) => {
setUsers(prev => prev.filter(u => u.id !== user.id));
};
wsManager.on('userOnline', handleUserOnline);
wsManager.on('userOffline', handleUserOffline);
return () => {
wsManager.off('userOnline', handleUserOnline);
wsManager.off('userOffline', handleUserOffline);
};
}, []);
return (
<div className="online-users">
<h3>在线用户 ({users.length})</h3>
<ul>
{users.map(user => (
<li key={user.id}>
<span className="online-indicator">🟢</span>
{user.name}
</li>
))}
</ul>
</div>
);
}
// ChatMessages.jsx - 聊天消息
import React from 'react';
import wsManager from './WebSocketManager';
function ChatMessages() {
const [messages, setMessages] = React.useState([]);
React.useEffect(() => {
const handleNewMessage = (message) => {
setMessages(prev => [...prev, message]);
};
wsManager.on('newMessage', handleNewMessage);
return () => {
wsManager.off('newMessage', handleNewMessage);
};
}, []);
return (
<div className="chat-messages">
{messages.map((msg, index) => (
<div key={index} className="message">
<strong>{msg.username}:</strong> {msg.text}
</div>
))}
</div>
);
}
🎨 在主流框架中的应用
Node.js EventEmitter
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
console.log(a, b);
});
myEmitter.emit('event', 'a', 'b');
Vue 响应式系统
// Vue 2/3 使用观察者模式实现响应式
export default {
data() {
return {
count: 0
};
},
watch: {
// 观察 count 变化
count(newValue, oldValue) {
console.log('count changed:', newValue);
}
}
}
RxJS - 强大的观察者模式库
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
// 监听输入框变化
const input = document.querySelector('#search');
const search$ = fromEvent(input, 'input').pipe(
debounceTime(300),
map(event => event.target.value)
);
search$.subscribe(value => {
console.log('搜索:', value);
});
⚖️ 优缺点分析
✅ 优点
- 解耦:发布者和订阅者解耦
- 动态关系:可以在运行时建立/解除订阅
- 广播通信:一对多通知
- 符合开闭原则:添加新观察者不需要修改发布者
❌ 缺点
- 性能问题:大量观察者会影响性能
- 内存泄漏:忘记取消订阅会导致内存泄漏
- 调试困难:事件流追踪困难
- 顺序问题:观察者执行顺序不确定
📋 何时使用观察者模式
✅ 适合使用的场景
- 状态变化需要通知多个对象
- 事件系统、消息总线
- 实时数据同步(WebSocket)
- 跨组件通信
- 日志、分析、监控系统
❌ 不适合使用的场景
- 只有一个订阅者
- 同步顺序很重要
- 性能敏感的场景
- 简单的父子组件通信(用 props 即可)
策略模式
💡 模式定义
策略模式(Strategy Pattern)定义一系列算法,把它们封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户端而变化。
🤔 为什么需要策略模式?
问题场景:表单验证、支付方式、排序算法等需要切换的逻辑
❌ 不使用策略模式的痛点
// FormValidator.jsx - 充满 if-else 的验证逻辑
function validateForm(formData, rules) {
const errors = {};
Object.keys(rules).forEach(field => {
const value = formData[field];
const rule = rules[field];
// 大量 if-else 判断
if (rule.required) {
if (!value || value.trim() === '') {
errors[field] = `${field} is required`;
return;
}
}
if (rule.minLength) {
if (value.length < rule.minLength) {
errors[field] = `${field} must be at least ${rule.minLength} characters`;
return;
}
}
if (rule.maxLength) {
if (value.length > rule.maxLength) {
errors[field] = `${field} must be at most ${rule.maxLength} characters`;
return;
}
}
if (rule.email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
errors[field] = `${field} must be a valid email`;
return;
}
}
if (rule.phone) {
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(value)) {
errors[field] = `${field} must be a valid phone number`;
return;
}
}
if (rule.url) {
try {
new URL(value);
} catch {
errors[field] = `${field} must be a valid URL`;
return;
}
}
if (rule.number) {
if (isNaN(value)) {
errors[field] = `${field} must be a number`;
return;
}
}
if (rule.min) {
if (Number(value) < rule.min) {
errors[field] = `${field} must be at least ${rule.min}`;
return;
}
}
if (rule.max) {
if (Number(value) > rule.max) {
errors[field] = `${field} must be at most ${rule.max}`;
return;
}
}
// 每次添加新规则都要加 if-else...
});
return errors;
}
问题:
- 难以扩展:添加新规则需要修改核心代码
- 代码臃肿:大量 if-else
- 难以测试:每个规则都耦合在一起
- 难以复用:规则无法独立使用
✅ 使用策略模式解决
// validators/strategies.js - 验证策略
const validationStrategies = {
// 必填策略
required: (value, errorMsg = '此字段为必填') => {
if (!value || String(value).trim() === '') {
return errorMsg;
}
return null;
},
// 最小长度策略
minLength: (length, errorMsg) => (value) => {
if (value && value.length < length) {
return errorMsg || `最少需要 ${length} 个字符`;
}
return null;
},
// 最大长度策略
maxLength: (length, errorMsg) => (value) => {
if (value && value.length > length) {
return errorMsg || `最多允许 ${length} 个字符`;
}
return null;
},
// 邮箱策略
email: (errorMsg = '请输入有效的邮箱地址') => (value) => {
if (!value) return null;
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(value) ? null : errorMsg;
},
// 手机号策略
phone: (errorMsg = '请输入有效的手机号') => (value) => {
if (!value) return null;
const regex = /^1[3-9]\d{9}$/;
return regex.test(value) ? null : errorMsg;
},
// 网址策略
url: (errorMsg = '请输入有效的网址') => (value) => {
if (!value) return null;
try {
new URL(value);
return null;
} catch {
return errorMsg;
}
},
// 数字策略
number: (errorMsg = '请输入数字') => (value) => {
if (!value) return null;
return isNaN(value) ? errorMsg : null;
},
// 最小值策略
min: (minValue, errorMsg) => (value) => {
if (!value) return null;
const num = Number(value);
return num < minValue ? (errorMsg || `最小值为 ${minValue}`) : null;
},
// 最大值策略
max: (maxValue, errorMsg) => (value) => {
if (!value) return null;
const num = Number(value);
return num > maxValue ? (errorMsg || `最大值为 ${maxValue}`) : null;
},
// 正则表达式策略
pattern: (regex, errorMsg = '格式不正确') => (value) => {
if (!value) return null;
return regex.test(value) ? null : errorMsg;
},
// 自定义策略
custom: (validatorFn, errorMsg) => (value) => {
const isValid = validatorFn(value);
return isValid ? null : errorMsg;
},
};
export default validationStrategies;
// FormValidator.js - 表单验证器
import validationStrategies from './validators/strategies';
class FormValidator {
constructor(rules) {
this.rules = rules;
this.compiledRules = this.compileRules(rules);
}
// 编译验证规则
compileRules(rules) {
const compiled = {};
Object.keys(rules).forEach(field => {
const fieldRules = rules[field];
compiled[field] = [];
fieldRules.forEach(rule => {
if (typeof rule === 'function') {
// 直接使用函数
compiled[field].push(rule);
} else if (typeof rule === 'object') {
// 使用策略配置
const { type, ...options } = rule;
const strategy = validationStrategies[type];
if (!strategy) {
console.warn(`Unknown validation strategy: ${type}`);
return;
}
// 根据策略类型创建验证器
if (type === 'required') {
compiled[field].push((value) => strategy(value, options.message));
} else {
compiled[field].push(strategy(options.value, options.message));
}
}
});
});
return compiled;
}
// 验证单个字段
validateField(field, value) {
const fieldRules = this.compiledRules[field];
if (!fieldRules) return null;
for (const rule of fieldRules) {
const error = rule(value);
if (error) return error;
}
return null;
}
// 验证整个表单
validate(formData) {
const errors = {};
Object.keys(this.compiledRules).forEach(field => {
const error = this.validateField(field, formData[field]);
if (error) {
errors[field] = error;
}
});
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
}
export default FormValidator;
// LoginForm.jsx - 使用策略模式的表单
import React from 'react';
import FormValidator from './FormValidator';
function LoginForm() {
const [formData, setFormData] = React.useState({
email: '',
password: '',
});
const [errors, setErrors] = React.useState({});
// 定义验证规则(使用策略)
const validator = React.useMemo(() => {
return new FormValidator({
email: [
{ type: 'required', message: '邮箱不能为空' },
{ type: 'email' },
],
password: [
{ type: 'required', message: '密码不能为空' },
{ type: 'minLength', value: 6, message: '密码至少6个字符' },
{
type: 'custom',
value: (value) => /[A-Z]/.test(value) && /[0-9]/.test(value),
message: '密码必须包含大写字母和数字',
},
],
});
}, []);
const handleChange = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
// 实时验证
const error = validator.validateField(field, value);
setErrors(prev => ({ ...prev, [field]: error }));
};
const handleSubmit = (e) => {
e.preventDefault();
const { isValid, errors: validationErrors } = validator.validate(formData);
if (isValid) {
console.log('提交表单:', formData);
} else {
setErrors(validationErrors);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
placeholder="邮箱"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input
type="password"
placeholder="密码"
value={formData.password}
onChange={(e) => handleChange('password', e.target.value)}
/>
{errors.password && <span className="error">{errors.password}</span>}
</div>
<button type="submit">登录</button>
</form>
);
}
export default LoginForm;
效果:
- ✅ 易于扩展:添加新策略不修改核心代码
- ✅ 代码清晰:每个策略独立
- ✅ 易于测试:可以独立测试每个策略
- ✅ 高度复用:策略可以在多个表单中复用
🏗️ 真实业务场景
场景1:支付方式策略
// payment/strategies.js - 支付策略
class PaymentStrategy {
pay(amount) {
throw new Error('Subclass must implement pay method');
}
refund(transactionId, amount) {
throw new Error('Subclass must implement refund method');
}
}
// 支付宝策略
class AlipayStrategy extends PaymentStrategy {
async pay(amount) {
console.log(`使用支付宝支付 ¥${amount}`);
// 调用支付宝 SDK
return {
success: true,
transactionId: 'ALIPAY-' + Date.now(),
method: 'alipay',
};
}
async refund(transactionId, amount) {
console.log(`支付宝退款 ¥${amount}`);
return { success: true };
}
}
// 微信支付策略
class WechatStrategy extends PaymentStrategy {
async pay(amount) {
console.log(`使用微信支付 ¥${amount}`);
// 调用微信支付 SDK
return {
success: true,
transactionId: 'WECHAT-' + Date.now(),
method: 'wechat',
};
}
async refund(transactionId, amount) {
console.log(`微信退款 ¥${amount}`);
return { success: true };
}
}
// 信用卡策略
class CreditCardStrategy extends PaymentStrategy {
constructor(cardInfo) {
super();
this.cardInfo = cardInfo;
}
async pay(amount) {
console.log(`使用信用卡支付 ¥${amount}`);
console.log('卡号:', this.cardInfo.number);
// 调用支付网关
return {
success: true,
transactionId: 'CARD-' + Date.now(),
method: 'credit_card',
};
}
async refund(transactionId, amount) {
console.log(`信用卡退款 ¥${amount}`);
return { success: true };
}
}
// 余额支付策略
class BalanceStrategy extends PaymentStrategy {
constructor(userId) {
super();
this.userId = userId;
}
async pay(amount) {
// 检查余额
const balance = await this.getBalance();
if (balance < amount) {
throw new Error('余额不足');
}
console.log(`使用余额支付 ¥${amount}`);
await this.deductBalance(amount);
return {
success: true,
transactionId: 'BALANCE-' + Date.now(),
method: 'balance',
};
}
async refund(transactionId, amount) {
console.log(`余额退款 ¥${amount}`);
await this.addBalance(amount);
return { success: true };
}
async getBalance() {
// 获取用户余额
return 1000;
}
async deductBalance(amount) {
// 扣除余额
}
async addBalance(amount) {
// 增加余额
}
}
export {
PaymentStrategy,
AlipayStrategy,
WechatStrategy,
CreditCardStrategy,
BalanceStrategy,
};
// PaymentManager.js - 支付管理器
class PaymentManager {
constructor() {
this.strategy = null;
}
setStrategy(strategy) {
this.strategy = strategy;
}
async executePayment(amount) {
if (!this.strategy) {
throw new Error('Payment strategy not set');
}
try {
const result = await this.strategy.pay(amount);
console.log('支付成功:', result);
return result;
} catch (error) {
console.error('支付失败:', error);
throw error;
}
}
async executeRefund(transactionId, amount) {
if (!this.strategy) {
throw new Error('Payment strategy not set');
}
try {
const result = await this.strategy.refund(transactionId, amount);
console.log('退款成功:', result);
return result;
} catch (error) {
console.error('退款失败:', error);
throw error;
}
}
}
export default PaymentManager;
// CheckoutPage.jsx - 结账页面
import React from 'react';
import PaymentManager from './PaymentManager';
import {
AlipayStrategy,
WechatStrategy,
CreditCardStrategy,
BalanceStrategy,
} from './payment/strategies';
function CheckoutPage({ totalAmount }) {
const [paymentMethod, setPaymentMethod] = React.useState('alipay');
const [paying, setPaying] = React.useState(false);
const paymentManager = React.useMemo(() => new PaymentManager(), []);
const handlePay = async () => {
setPaying(true);
try {
// 根据选择的支付方式设置策略
switch (paymentMethod) {
case 'alipay':
paymentManager.setStrategy(new AlipayStrategy());
break;
case 'wechat':
paymentManager.setStrategy(new WechatStrategy());
break;
case 'credit_card':
paymentManager.setStrategy(new CreditCardStrategy({
number: '4111111111111111',
cvv: '123',
expiry: '12/25',
}));
break;
case 'balance':
paymentManager.setStrategy(new BalanceStrategy('user123'));
break;
default:
throw new Error('Invalid payment method');
}
// 执行支付(不关心具体实现)
const result = await paymentManager.executePayment(totalAmount);
alert('支付成功!');
} catch (error) {
alert('支付失败:' + error.message);
} finally {
setPaying(false);
}
};
return (
<div className="checkout-page">
<h2>支付 ¥{totalAmount}</h2>
<div className="payment-methods">
<label>
<input
type="radio"
value="alipay"
checked={paymentMethod === 'alipay'}
onChange={(e) => setPaymentMethod(e.target.value)}
/>
支付宝
</label>
<label>
<input
type="radio"
value="wechat"
checked={paymentMethod === 'wechat'}
onChange={(e) => setPaymentMethod(e.target.value)}
/>
微信支付
</label>
<label>
<input
type="radio"
value="credit_card"
checked={paymentMethod === 'credit_card'}
onChange={(e) => setPaymentMethod(e.target.value)}
/>
信用卡
</label>
<label>
<input
type="radio"
value="balance"
checked={paymentMethod === 'balance'}
onChange={(e) => setPaymentMethod(e.target.value)}
/>
余额支付
</label>
</div>
<button onClick={handlePay} disabled={paying}>
{paying ? '支付中...' : '确认支付'}
</button>
</div>
);
}
export default CheckoutPage;
场景2:排序策略
// sorting/strategies.js - 排序策略
class SortStrategy {
sort(items) {
throw new Error('Subclass must implement sort method');
}
}
// 价格升序
class PriceAscStrategy extends SortStrategy {
sort(items) {
return [...items].sort((a, b) => a.price - b.price);
}
}
// 价格降序
class PriceDescStrategy extends SortStrategy {
sort(items) {
return [...items].sort((a, b) => b.price - a.price);
}
}
// 按评分排序
class RatingStrategy extends SortStrategy {
sort(items) {
return [...items].sort((a, b) => b.rating - a.rating);
}
}
// 按销量排序
class SalesStrategy extends SortStrategy {
sort(items) {
return [...items].sort((a, b) => b.sales - a.sales);
}
}
// 按最新排序
class NewestStrategy extends SortStrategy {
sort(items) {
return [...items].sort((a, b) =>
new Date(b.createdAt) - new Date(a.createdAt)
);
}
}
export {
PriceAscStrategy,
PriceDescStrategy,
RatingStrategy,
SalesStrategy,
NewestStrategy,
};
// ProductList.jsx - 商品列表
import React from 'react';
import {
PriceAscStrategy,
PriceDescStrategy,
RatingStrategy,
SalesStrategy,
NewestStrategy,
} from './sorting/strategies';
function ProductList({ products }) {
const [sortBy, setSortBy] = React.useState('price_asc');
const sortStrategies = {
price_asc: new PriceAscStrategy(),
price_desc: new PriceDescStrategy(),
rating: new RatingStrategy(),
sales: new SalesStrategy(),
newest: new NewestStrategy(),
};
const sortedProducts = React.useMemo(() => {
const strategy = sortStrategies[sortBy];
return strategy.sort(products);
}, [products, sortBy]);
return (
<div className="product-list">
<div className="sort-options">
<label>排序方式:</label>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="price_asc">价格从低到高</option>
<option value="price_desc">价格从高到低</option>
<option value="rating">按评分</option>
<option value="sales">按销量</option>
<option value="newest">最新上架</option>
</select>
</div>
<div className="products">
{sortedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
);
}
function ProductCard({ product }) {
return (
<div className="product-card">
<h4>{product.name}</h4>
<p>价格: ¥{product.price}</p>
<p>评分: {product.rating}⭐</p>
<p>销量: {product.sales}</p>
</div>
);
}
export default ProductList;
🎨 在主流框架中的应用
React Hooks - 策略模式的应用
// useAuth hook 使用不同的认证策略
function useAuth(strategy = 'jwt') {
const strategies = {
jwt: useJWTAuth,
oauth: useOAuthAuth,
session: useSessionAuth,
};
return strategies[strategy]();
}
function useJWTAuth() {
// JWT 认证逻辑
}
function useOAuthAuth() {
// OAuth 认证逻辑
}
function useSessionAuth() {
// Session 认证逻辑
}
Passport.js - 认证策略
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const GoogleStrategy = require('passport-google-oauth20').Strategy;
// 使用本地策略
passport.use(new LocalStrategy(
function(username, password, done) {
// 验证逻辑
}
));
// 使用 Google 策略
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, cb) {
// 验证逻辑
}
));
⚖️ 优缺点分析
✅ 优点
- 开闭原则:添加新策略不需要修改现有代码
- 消除条件语句:避免大量 if-else
- 算法独立:每个策略独立,易于测试
- 运行时切换:可以在运行时切换策略
❌ 缺点
- 类数量增加:每个策略都是一个类
- 客户端必须了解策略:需要知道有哪些策略可选
- 策略间通信:策略之间很难共享数据
📋 何时使用策略模式
✅ 适合使用的场景
- 表单验证规则
- 支付方式、物流方式
- 排序算法、搜索算法
- 数据格式化、数据转换
- 认证方式、加密算法
❌ 不适合使用的场景
- 只有一两种算法
- 算法很少变化
- 策略之间需要共享大量数据
命令模式
💡 模式定义
命令模式(Command Pattern)将请求封装成对象,从而可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
🤔 为什么需要命令模式?
问题场景:撤销重做、操作历史、宏命令
假设你在开发一个富文本编辑器,需要支持撤销/重做功能:
❌ 不使用命令模式的痛点
// TextEditor.jsx - 难以实现撤销重做
function TextEditor() {
const [content, setContent] = React.useState('');
const [history, setHistory] = React.useState([]);
const [historyIndex, setHistoryIndex] = React.useState(-1);
const handleInsertText = (text, position) => {
const newContent =
content.slice(0, position) + text + content.slice(position);
setContent(newContent);
// 如何保存足够的信息来撤销?
// 需要记录:操作类型、位置、文本、旧内容...
setHistory([...history.slice(0, historyIndex + 1), {
type: 'insert',
text,
position,
oldContent: content,
}]);
setHistoryIndex(historyIndex + 1);
};
const handleDeleteText = (start, end) => {
const deletedText = content.slice(start, end);
const newContent = content.slice(0, start) + content.slice(end);
setContent(newContent);
setHistory([...history.slice(0, historyIndex + 1), {
type: 'delete',
start,
end,
deletedText,
oldContent: content,
}]);
setHistoryIndex(historyIndex + 1);
};
const handleBold = (start, end) => {
// 加粗操作...
// 又要记录不同的历史信息
};
const handleUndo = () => {
if (historyIndex < 0) return;
const action = history[historyIndex];
// 根据不同操作类型,执行不同的撤销逻辑
if (action.type === 'insert') {
setContent(action.oldContent);
} else if (action.type === 'delete') {
const newContent =
content.slice(0, action.start) +
action.deletedText +
content.slice(action.start);
setContent(newContent);
} else if (action.type === 'bold') {
// 撤销加粗...
}
// 每种操作都要写撤销逻辑!
setHistoryIndex(historyIndex - 1);
};
const handleRedo = () => {
// 重做也要写一遍逻辑...
};
// ...
}
问题:
- 撤销逻辑复杂:每种操作都要写撤销/重做逻辑
- 难以扩展:新增操作需要修改撤销/重做代码
- 代码重复:相似的历史记录逻辑
- 难以测试:操作和撤销耦合在一起
✅ 使用命令模式解决
// commands/Command.js - 命令基类
class Command {
execute() {
throw new Error('Subclass must implement execute method');
}
undo() {
throw new Error('Subclass must implement undo method');
}
redo() {
return this.execute();
}
}
export default Command;
// commands/InsertTextCommand.js - 插入文本命令
import Command from './Command';
class InsertTextCommand extends Command {
constructor(editor, text, position) {
super();
this.editor = editor;
this.text = text;
this.position = position;
this.oldContent = null;
}
execute() {
// 保存旧内容
this.oldContent = this.editor.getContent();
// 执行插入
const content = this.editor.getContent();
const newContent =
content.slice(0, this.position) +
this.text +
content.slice(this.position);
this.editor.setContent(newContent);
return true;
}
undo() {
// 恢复旧内容
this.editor.setContent(this.oldContent);
}
}
export default InsertTextCommand;
// commands/DeleteTextCommand.js - 删除文本命令
import Command from './Command';
class DeleteTextCommand extends Command {
constructor(editor, start, end) {
super();
this.editor = editor;
this.start = start;
this.end = end;
this.deletedText = null;
}
execute() {
const content = this.editor.getContent();
// 保存被删除的文本
this.deletedText = content.slice(this.start, this.end);
// 执行删除
const newContent = content.slice(0, this.start) + content.slice(this.end);
this.editor.setContent(newContent);
return true;
}
undo() {
// 恢复被删除的文本
const content = this.editor.getContent();
const newContent =
content.slice(0, this.start) +
this.deletedText +
content.slice(this.start);
this.editor.setContent(newContent);
}
}
export default DeleteTextCommand;
// commands/BoldTextCommand.js - 加粗文本命令
import Command from './Command';
class BoldTextCommand extends Command {
constructor(editor, start, end) {
super();
this.editor = editor;
this.start = start;
this.end = end;
this.oldFormat = null;
}
execute() {
// 保存旧格式
this.oldFormat = this.editor.getFormat(this.start, this.end);
// 应用加粗
this.editor.applyFormat(this.start, this.end, { bold: true });
return true;
}
undo() {
// 恢复旧格式
this.editor.applyFormat(this.start, this.end, this.oldFormat);
}
}
export default BoldTextCommand;
// CommandManager.js - 命令管理器
class CommandManager {
constructor() {
this.history = [];
this.currentIndex = -1;
}
// 执行命令
execute(command) {
// 清除当前位置之后的历史
this.history = this.history.slice(0, this.currentIndex + 1);
// 执行命令
const success = command.execute();
if (success) {
// 添加到历史
this.history.push(command);
this.currentIndex++;
}
return success;
}
// 撤销
undo() {
if (!this.canUndo()) return false;
const command = this.history[this.currentIndex];
command.undo();
this.currentIndex--;
return true;
}
// 重做
redo() {
if (!this.canRedo()) return false;
this.currentIndex++;
const command = this.history[this.currentIndex];
command.redo();
return true;
}
canUndo() {
return this.currentIndex >= 0;
}
canRedo() {
return this.currentIndex < this.history.length - 1;
}
getHistory() {
return this.history.map((cmd, index) => ({
name: cmd.constructor.name,
active: index <= this.currentIndex,
}));
}
clear() {
this.history = [];
this.currentIndex = -1;
}
}
export default CommandManager;
// TextEditor.jsx - 使用命令模式的编辑器
import React from 'react';
import CommandManager from './CommandManager';
import InsertTextCommand from './commands/InsertTextCommand';
import DeleteTextCommand from './commands/DeleteTextCommand';
import BoldTextCommand from './commands/BoldTextCommand';
function TextEditor() {
const [content, setContent] = React.useState('');
const [selection, setSelection] = React.useState({ start: 0, end: 0 });
const [formats, setFormats] = React.useState({});
const commandManager = React.useRef(new CommandManager());
// 编辑器接口
const editor = React.useMemo(() => ({
getContent: () => content,
setContent,
getFormat: (start, end) => formats[`${start}-${end}`] || {},
applyFormat: (start, end, format) => {
setFormats(prev => ({
...prev,
[`${start}-${end}`]: format,
}));
},
}), [content, formats]);
const handleInsert = (text) => {
const command = new InsertTextCommand(editor, text, selection.start);
commandManager.current.execute(command);
};
const handleDelete = () => {
if (selection.start === selection.end) return;
const command = new DeleteTextCommand(editor, selection.start, selection.end);
commandManager.current.execute(command);
};
const handleBold = () => {
if (selection.start === selection.end) return;
const command = new BoldTextCommand(editor, selection.start, selection.end);
commandManager.current.execute(command);
};
const handleUndo = () => {
commandManager.current.undo();
};
const handleRedo = () => {
commandManager.current.redo();
};
return (
<div className="text-editor">
<div className="toolbar">
<button onClick={() => handleInsert('Hello')}>插入 Hello</button>
<button onClick={handleDelete}>删除选中</button>
<button onClick={handleBold}>加粗</button>
<button
onClick={handleUndo}
disabled={!commandManager.current.canUndo()}
>
撤销
</button>
<button
onClick={handleRedo}
disabled={!commandManager.current.canRedo()}
>
重做
</button>
</div>
<textarea
value={content}
onChange={(e) => {
const command = new InsertTextCommand(
editor,
e.target.value.slice(content.length),
content.length
);
commandManager.current.execute(command);
}}
onSelect={(e) => {
setSelection({
start: e.target.selectionStart,
end: e.target.selectionEnd,
});
}}
/>
<HistoryPanel commandManager={commandManager.current} />
</div>
);
}
function HistoryPanel({ commandManager }) {
const [history, setHistory] = React.useState([]);
React.useEffect(() => {
const interval = setInterval(() => {
setHistory(commandManager.getHistory());
}, 100);
return () => clearInterval(interval);
}, [commandManager]);
return (
<div className="history-panel">
<h4>操作历史</h4>
<ul>
{history.map((item, index) => (
<li key={index} className={item.active ? 'active' : 'inactive'}>
{item.name}
</li>
))}
</ul>
</div>
);
}
export default TextEditor;
效果:
- ✅ 统一接口:所有命令都有 execute/undo/redo 方法
- ✅ 易于扩展:新增命令不影响现有代码
- ✅ 操作历史:自动记录所有操作
- ✅ 易于测试:可以独立测试每个命令
🏗️ 真实业务场景
场景1:图形编辑器(移动、缩放、旋转)
// commands/MoveCommand.js
class MoveCommand extends Command {
constructor(shape, deltaX, deltaY) {
super();
this.shape = shape;
this.deltaX = deltaX;
this.deltaY = deltaY;
}
execute() {
this.shape.x += this.deltaX;
this.shape.y += this.deltaY;
}
undo() {
this.shape.x -= this.deltaX;
this.shape.y -= this.deltaY;
}
}
// commands/ResizeCommand.js
class ResizeCommand extends Command {
constructor(shape, newWidth, newHeight) {
super();
this.shape = shape;
this.newWidth = newWidth;
this.newHeight = newHeight;
this.oldWidth = shape.width;
this.oldHeight = shape.height;
}
execute() {
this.shape.width = this.newWidth;
this.shape.height = this.newHeight;
}
undo() {
this.shape.width = this.oldWidth;
this.shape.height = this.oldHeight;
}
}
// commands/MacroCommand.js - 宏命令(组合多个命令)
class MacroCommand extends Command {
constructor(commands = []) {
super();
this.commands = commands;
}
add(command) {
this.commands.push(command);
}
execute() {
this.commands.forEach(command => command.execute());
}
undo() {
// 逆序撤销
for (let i = this.commands.length - 1; i >= 0; i--) {
this.commands[i].undo();
}
}
}
场景2:任务队列
// TaskQueue.js - 任务队列
class TaskQueue {
constructor() {
this.queue = [];
this.history = [];
this.isProcessing = false;
}
// 添加任务
enqueue(command) {
this.queue.push(command);
this.processNext();
}
// 处理下一个任务
async processNext() {
if (this.isProcessing || this.queue.length === 0) return;
this.isProcessing = true;
const command = this.queue.shift();
try {
await command.execute();
this.history.push(command);
console.log(`任务完成: ${command.constructor.name}`);
} catch (error) {
console.error(`任务失败: ${command.constructor.name}`, error);
// 可以选择重试或跳过
} finally {
this.isProcessing = false;
this.processNext();
}
}
// 取消所有任务
cancelAll() {
this.queue = [];
}
// 重试失败的任务
retryLast() {
if (this.history.length === 0) return;
const lastCommand = this.history[this.history.length - 1];
this.enqueue(lastCommand);
}
}
// 使用示例
const taskQueue = new TaskQueue();
class UploadFileCommand extends Command {
constructor(file) {
super();
this.file = file;
}
async execute() {
console.log(`上传文件: ${this.file.name}`);
// 模拟上传
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// 批量上传
files.forEach(file => {
taskQueue.enqueue(new UploadFileCommand(file));
});
🎨 在主流框架中的应用
Redux Action - 命令模式
// Redux 的 Action 就是命令模式
const incrementAction = { type: 'INCREMENT' };
const decrementAction = { type: 'DECREMENT' };
// Dispatch 就是执行命令
dispatch(incrementAction);
// Redux DevTools 支持时间旅行(撤销/重做)
Git 命令 - 命令模式的典范
git add . # 添加到暂存区(可撤销)
git commit -m "..." # 提交(可撤销)
git revert HEAD # 撤销最后一次提交
git reset --hard # 重置到某个状态
⚖️ 优缺点分析
✅ 优点
- 解耦:调用者和接收者解耦
- 可撤销:轻松实现撤销/重做
- 可组合:可以组合多个命令(宏命令)
- 可记录:可以记录操作历史
- 可排队:可以实现任务队列
❌ 缺点
- 类数量增加:每个操作都是一个命令类
- 内存开销:需要保存历史状态
- 复杂度:简单操作也要封装成命令
📋 何时使用命令模式
✅ 适合使用的场景
- 撤销/重做功能
- 操作历史记录
- 任务队列、批处理
- 事务管理
- 宏命令(组合多个操作)
❌ 不适合使用的场景
- 简单的 CRUD 操作
- 不需要撤销的操作
- 内存敏感的场景
状态模式
💡 模式定义
状态模式(State Pattern)允许对象在内部状态改变时改变其行为,对象看起来好像修改了它的类。
🤔 为什么需要状态模式?
问题场景:订单状态、播放器状态、流程控制
假设你在开发一个音乐播放器:
❌ 不使用状态模式的痛点
// MusicPlayer.jsx - 充满状态判断
function MusicPlayer({ track }) {
const [state, setState] = React.useState('stopped'); // stopped, playing, paused
const audioRef = React.useRef(new Audio(track.url));
const handlePlay = () => {
// 根据当前状态判断能否播放
if (state === 'stopped') {
audioRef.current.play();
setState('playing');
} else if (state === 'paused') {
audioRef.current.play();
setState('playing');
} else if (state === 'playing') {
// 已经在播放,什么都不做
console.log('Already playing');
}
};
const handlePause = () => {
if (state === 'playing') {
audioRef.current.pause();
setState('paused');
} else if (state === 'stopped') {
// 停止状态不能暂停
console.log('Cannot pause when stopped');
} else if (state === 'paused') {
// 已经暂停
console.log('Already paused');
}
};
const handleStop = () => {
if (state === 'playing' || state === 'paused') {
audioRef.current.pause();
audioRef.current.currentTime = 0;
setState('stopped');
} else if (state === 'stopped') {
console.log('Already stopped');
}
};
const handleNext = () => {
if (state === 'playing') {
// 播放中可以切换
audioRef.current.pause();
// 加载下一首
setState('playing');
} else if (state === 'paused') {
// 暂停时也可以切换
// 加载下一首
setState('paused');
} else if (state === 'stopped') {
// 停止时切换但不播放
// 加载下一首
setState('stopped');
}
};
// 每个操作都要判断所有可能的状态!
// 新增状态(如 buffering, error)会导致大量修改
return (
<div className="music-player">
<p>状态: {state}</p>
<button onClick={handlePlay}>播放</button>
<button onClick={handlePause}>暂停</button>
<button onClick={handleStop}>停止</button>
<button onClick={handleNext}>下一首</button>
</div>
);
}
问题:
- 状态判断分散:每个方法都要判断所有状态
- 难以扩展:新增状态需要修改所有方法
- 代码重复:相似的状态判断逻辑
- 状态转换不清晰:不知道哪些状态可以互相转换
✅ 使用状态模式解决
// states/PlayerState.js - 状态基类
class PlayerState {
constructor(player) {
this.player = player;
}
play() {
console.log('Cannot play in this state');
}
pause() {
console.log('Cannot pause in this state');
}
stop() {
console.log('Cannot stop in this state');
}
next() {
console.log('Cannot switch track in this state');
}
getName() {
return this.constructor.name;
}
}
export default PlayerState;
// states/StoppedState.js
import PlayerState from './PlayerState';
class StoppedState extends PlayerState {
play() {
console.log('Starting playback...');
this.player.audio.play();
this.player.setState(this.player.states.playing);
}
pause() {
console.log('Cannot pause when stopped');
}
stop() {
console.log('Already stopped');
}
next() {
console.log('Switching to next track (stopped)');
this.player.loadNextTrack();
// 保持停止状态
}
}
export default StoppedState;
// states/PlayingState.js
import PlayerState from './PlayerState';
class PlayingState extends PlayerState {
play() {
console.log('Already playing');
}
pause() {
console.log('Pausing playback...');
this.player.audio.pause();
this.player.setState(this.player.states.paused);
}
stop() {
console.log('Stopping playback...');
this.player.audio.pause();
this.player.audio.currentTime = 0;
this.player.setState(this.player.states.stopped);
}
next() {
console.log('Switching to next track (playing)');
this.player.loadNextTrack();
this.player.audio.play();
// 保持播放状态
}
}
export default PlayingState;
// states/PausedState.js
import PlayerState from './PlayerState';
class PausedState extends PlayerState {
play() {
console.log('Resuming playback...');
this.player.audio.play();
this.player.setState(this.player.states.playing);
}
pause() {
console.log('Already paused');
}
stop() {
console.log('Stopping playback...');
this.player.audio.currentTime = 0;
this.player.setState(this.player.states.stopped);
}
next() {
console.log('Switching to next track (paused)');
this.player.loadNextTrack();
// 保持暂停状态
}
}
export default PausedState;
// MusicPlayer.js - 播放器类
import StoppedState from './states/StoppedState';
import PlayingState from './states/PlayingState';
import PausedState from './states/PausedState';
class MusicPlayer {
constructor(track) {
this.audio = new Audio(track.url);
this.currentTrack = track;
// 创建所有状态实例
this.states = {
stopped: new StoppedState(this),
playing: new PlayingState(this),
paused: new PausedState(this),
};
// 初始状态
this.currentState = this.states.stopped;
this.listeners = [];
}
// 设置状态
setState(state) {
console.log(`State: ${this.currentState.getName()} -> ${state.getName()}`);
this.currentState = state;
this.notifyListeners();
}
// 操作方法(委托给当前状态)
play() {
this.currentState.play();
}
pause() {
this.currentState.pause();
}
stop() {
this.currentState.stop();
}
next() {
this.currentState.next();
}
loadNextTrack() {
// 加载下一首歌
console.log('Loading next track...');
}
getState() {
return this.currentState.getName();
}
// 观察者模式:通知状态变化
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
notifyListeners() {
this.listeners.forEach(listener => listener(this.getState()));
}
}
export default MusicPlayer;
// MusicPlayerUI.jsx - React 组件
import React from 'react';
import MusicPlayer from './MusicPlayer';
function MusicPlayerUI({ track }) {
const [state, setState] = React.useState('StoppedState');
const playerRef = React.useRef(null);
React.useEffect(() => {
const player = new MusicPlayer(track);
playerRef.current = player;
// 订阅状态变化
const unsubscribe = player.subscribe(setState);
return () => {
unsubscribe();
};
}, [track]);
const handlePlay = () => playerRef.current?.play();
const handlePause = () => playerRef.current?.pause();
const handleStop = () => playerRef.current?.stop();
const handleNext = () => playerRef.current?.next();
return (
<div className="music-player">
<p>当前状态: {state}</p>
<div className="controls">
<button onClick={handlePlay}>▶️ 播放</button>
<button onClick={handlePause}>⏸️ 暂停</button>
<button onClick={handleStop}>⏹️ 停止</button>
<button onClick={handleNext}>⏭️ 下一首</button>
</div>
</div>
);
}
export default MusicPlayerUI;
效果:
- ✅ 状态逻辑集中:每个状态类只处理自己的逻辑
- ✅ 易于扩展:新增状态只需添加新类
- ✅ 状态转换清晰:一目了然
- ✅ 消除条件语句:没有 if-else
🏗️ 真实业务场景
场景1:订单状态机
// states/OrderState.js
class OrderState {
constructor(order) {
this.order = order;
}
pay() {
throw new Error('Cannot pay in this state');
}
ship() {
throw new Error('Cannot ship in this state');
}
deliver() {
throw new Error('Cannot deliver in this state');
}
cancel() {
throw new Error('Cannot cancel in this state');
}
getName() {
return this.constructor.name;
}
}
// 待支付状态
class PendingPaymentState extends OrderState {
pay() {
console.log('Processing payment...');
// 支付逻辑
this.order.setState(this.order.states.paid);
}
cancel() {
console.log('Cancelling order...');
this.order.setState(this.order.states.cancelled);
}
}
// 已支付状态
class PaidState extends OrderState {
ship() {
console.log('Shipping order...');
this.order.setState(this.order.states.shipped);
}
cancel() {
console.log('Cancelling and refunding...');
// 退款逻辑
this.order.setState(this.order.states.cancelled);
}
}
// 已发货状态
class ShippedState extends OrderState {
deliver() {
console.log('Order delivered!');
this.order.setState(this.order.states.delivered);
}
}
// 已完成状态
class DeliveredState extends OrderState {
// 已完成,不能再操作
}
// 已取消状态
class CancelledState extends OrderState {
// 已取消,不能再操作
}
// Order.js
class Order {
constructor(orderId) {
this.orderId = orderId;
this.states = {
pendingPayment: new PendingPaymentState(this),
paid: new PaidState(this),
shipped: new ShippedState(this),
delivered: new DeliveredState(this),
cancelled: new CancelledState(this),
};
this.currentState = this.states.pendingPayment;
}
setState(state) {
console.log(`Order ${this.orderId}: ${this.currentState.getName()} -> ${state.getName()}`);
this.currentState = state;
}
pay() {
this.currentState.pay();
}
ship() {
this.currentState.ship();
}
deliver() {
this.currentState.deliver();
}
cancel() {
this.currentState.cancel();
}
getStatus() {
return this.currentState.getName();
}
}
export default Order;
// 使用订单状态机
const order = new Order('ORDER-123');
console.log(order.getStatus()); // PendingPaymentState
order.pay(); // 支付成功 -> PaidState
order.ship(); // 发货 -> ShippedState
order.deliver(); // 送达 -> DeliveredState
// order.cancel(); // 报错:Cannot cancel in this state
场景2:TCP 连接状态
// Connection.js - TCP 连接状态机
class ConnectionState {
constructor(connection) {
this.connection = connection;
}
open() {}
close() {}
send(data) {}
receive(data) {}
}
class ClosedState extends ConnectionState {
open() {
console.log('Opening connection...');
this.connection.socket.connect();
this.connection.setState(this.connection.states.connecting);
}
close() {
console.log('Already closed');
}
send(data) {
throw new Error('Cannot send data when connection is closed');
}
}
class ConnectingState extends ConnectionState {
open() {
console.log('Already connecting');
}
close() {
console.log('Aborting connection...');
this.connection.socket.abort();
this.connection.setState(this.connection.states.closed);
}
send(data) {
throw new Error('Cannot send data while connecting');
}
}
class ConnectedState extends ConnectionState {
open() {
console.log('Already connected');
}
close() {
console.log('Closing connection...');
this.connection.socket.close();
this.connection.setState(this.connection.states.closed);
}
send(data) {
console.log('Sending data:', data);
this.connection.socket.send(data);
}
receive(data) {
console.log('Received data:', data);
this.connection.onData(data);
}
}
class Connection {
constructor() {
this.socket = null;
this.states = {
closed: new ClosedState(this),
connecting: new ConnectingState(this),
connected: new ConnectedState(this),
};
this.currentState = this.states.closed;
}
setState(state) {
this.currentState = state;
}
open() {
this.currentState.open();
}
close() {
this.currentState.close();
}
send(data) {
this.currentState.send(data);
}
receive(data) {
this.currentState.receive(data);
}
onData(data) {
// 处理接收到的数据
}
}
🎨 在主流框架中的应用
Promise 状态
// Promise 有三个状态:pending, fulfilled, rejected
const promise = new Promise((resolve, reject) => {
// pending 状态
setTimeout(() => {
resolve('success'); // 转换到 fulfilled 状态
}, 1000);
});
promise.then(
value => console.log(value), // fulfilled 状态
error => console.error(error) // rejected 状态
);
React 组件生命周期
class MyComponent extends React.Component {
// Mounting 状态
componentDidMount() {}
// Updating 状态
componentDidUpdate() {}
// Unmounting 状态
componentWillUnmount() {}
}
⚖️ 优缺点分析
✅ 优点
- 消除条件语句:避免大量 if-else
- 单一职责:每个状态类只处理一个状态
- 状态转换清晰:一目了然
- 易于扩展:添加新状态不影响现有状态
❌ 缺点
- 类数量增加:每个状态都是一个类
- 状态过多时复杂:状态很多时难以管理
- 状态共享困难:状态之间共享数据需要通过上下文对象
📋 何时使用状态模式
✅ 适合使用的场景
- 对象行为随状态改变而改变
- 大量条件语句(if-else, switch)
- 状态转换规则明确
- 状态机、工作流
❌ 不适合使用的场景
- 只有 2-3 个状态
- 状态转换简单
- 状态之间有大量共享逻辑
📝 总结
行为型模式对比
| 模式 | 核心目的 | 使用场景 | 优先级 |
|---|---|---|---|
| 观察者模式 | 对象间一对多依赖 | 事件系统、状态订阅 | ⭐⭐⭐⭐⭐ |
| 策略模式 | 算法可替换 | 表单验证、支付方式 | ⭐⭐⭐⭐⭐ |
| 命令模式 | 请求封装成对象 | 撤销重做、任务队列 | ⭐⭐⭐⭐ |
| 状态模式 | 状态改变行为 | 状态机、流程控制 | ⭐⭐⭐⭐ |
学习建议
- 观察者模式:前端最常用,掌握 EventEmitter 和 React Context
- 策略模式:理解如何消除 if-else,提升代码质量
- 命令模式:理解撤销/重做的实现原理
- 状态模式:学会用状态机处理复杂流程
模式组合使用
在实际开发中,这些模式经常组合使用:
- 观察者 + 命令:命令执行后通知观察者
- 策略 + 工厂:使用工厂创建策略对象
- 状态 + 命令:状态转换通过命令实现