您现在的位置是:网站首页 > 如何选择合适的设计模式文章详情
如何选择合适的设计模式
陈川
【
JavaScript
】
50213人已围观
9592字
理解设计模式的核心概念
设计模式不是银弹,而是针对特定问题的解决方案模板。在JavaScript中,设计模式的应用需要考虑语言特性——原型继承、函数一等公民、动态类型等。每个模式都有其适用场景,错误的选择可能导致代码过度复杂化。
观察者模式适合处理对象间一对多的依赖关系。比如实现一个事件系统:
class EventEmitter {
constructor() {
this.listeners = {};
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
emit(event, ...args) {
if (this.listeners[event]) {
this.listeners[event].forEach(cb => cb(...args));
}
}
}
分析问题领域
选择模式前必须明确待解决的问题。创建型?结构型?还是行为型问题?例如表单验证场景:
- 如果验证规则频繁变化,策略模式更合适
- 需要组合多个验证器时,组合模式可能更好
- 验证过程涉及多个步骤,可以考虑责任链模式
// 策略模式实现验证器
const validators = {
required: value => !!value.trim(),
minLength: (value, length) => value.length >= length,
email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
};
function validate(formData, rules) {
return Object.keys(rules).every(field => {
return rules[field].every(rule => {
const [validator, ...args] = rule.split(':');
return validators[validator](formData[field], ...args);
});
});
}
评估代码变化方向
好的模式选择应该面向可能的变化。考虑电商系统中的折扣计算:
- 如果折扣类型固定,简单条件判断足够
- 预期会新增折扣类型,策略模式更合适
- 需要组合多种折扣时,装饰器模式可能更好
// 策略模式实现折扣计算
const discountStrategies = {
fixed: (amount, discount) => amount - discount,
percentage: (amount, percent) => amount * (1 - percent/100),
seasonal: (amount, season) => {
const factors = { summer: 0.8, winter: 0.7 };
return amount * (factors[season] || 1);
}
};
function calculatePrice(amount, strategy, ...args) {
return discountStrategies[strategy](amount, ...args);
}
考虑性能影响
某些模式会引入额外开销。例如:
- 享元模式通过共享减少内存使用,但增加查找复杂度
- 代理模式增强控制但可能影响性能
- 观察者模式在事件密集时可能导致性能问题
虚拟滚动场景适合享元模式:
class FlyweightList {
constructor(itemHeight, renderItem) {
this.itemHeight = itemHeight;
this.renderItem = renderItem;
this.visibleItems = new Map();
}
update(container, scrollTop, data) {
const startIdx = Math.floor(scrollTop / this.itemHeight);
const endIdx = Math.min(
startIdx + Math.ceil(container.clientHeight / this.itemHeight),
data.length - 1
);
// 回收不可见项
this.visibleItems.forEach((node, idx) => {
if (idx < startIdx || idx > endIdx) {
container.removeChild(node);
this.visibleItems.delete(idx);
}
});
// 添加新可见项
for (let i = startIdx; i <= endIdx; i++) {
if (!this.visibleItems.has(i)) {
const node = this.renderItem(data[i]);
node.style.position = 'absolute';
node.style.top = `${i * this.itemHeight}px`;
container.appendChild(node);
this.visibleItems.set(i, node);
}
}
}
}
团队熟悉度因素
选择模式时需要考虑团队的技术储备:
- 熟悉度高的模式实现和维护成本更低
- 新颖模式可能带来长期收益,但短期学习成本高
- 文档和示例的完整性影响模式采用
例如,React生态中常见的高阶组件模式:
function withLoading(WrappedComponent) {
return function EnhancedComponent({ isLoading, ...props }) {
return isLoading ? <div className="loader" /> : <WrappedComponent {...props} />;
};
}
// 使用
const UserProfileWithLoading = withLoading(UserProfile);
模式组合的可能性
复杂场景往往需要组合多个模式:
- MVC架构结合观察者、策略、组合模式
- Redux实现中可见单例、观察者、装饰器模式的组合
- 现代前端框架常混合工厂、代理、装饰器等模式
状态管理库的典型实现:
// 结合单例和观察者模式
class Store {
static instance;
constructor(reducer) {
if (Store.instance) {
return Store.instance;
}
this.state = reducer(undefined, {});
this.listeners = [];
this.reducer = reducer;
Store.instance = this;
}
dispatch(action) {
this.state = this.reducer(this.state, action);
this.listeners.forEach(listener => listener());
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
getState() {
return this.state;
}
}
JavaScript特定模式选择
某些模式在JavaScript中有特殊实现方式:
- 模块模式替代传统单例
- 原型继承实现享元模式
- 闭包实现私有变量
模块模式示例:
const counterModule = (() => {
let count = 0;
const increment = () => ++count;
const reset = () => count = 0;
const getCount = () => count;
return {
increment,
reset,
getCount
};
})();
// 使用
counterModule.increment();
console.log(counterModule.getCount());
避免模式滥用
模式滥用常见表现:
- 简单问题使用复杂模式
- 强行套用模式导致代码晦涩
- 忽视语言特性而机械应用模式
例如,不需要工厂模式的简单对象创建:
// 过度设计
class UserFactory {
static createUser(type) {
switch(type) {
case 'admin': return new Admin();
case 'customer': return new Customer();
default: throw new Error('Invalid user type');
}
}
}
// 更简单的实现
function createUser(type, props) {
const userTypes = {
admin: Admin,
customer: Customer
};
const UserClass = userTypes[type];
if (!UserClass) throw new Error('Invalid user type');
return new UserClass(props);
}
重构现有代码的模式引入
逐步引入模式的方法:
- 识别代码中的痛点
- 选择针对性解决这些痛点的模式
- 小范围重构验证效果
- 逐步扩大应用范围
例如将回调地狱改为命令模式:
// 重构前
function processOrder(orderId, callback) {
fetchOrder(orderId, (order) => {
validateOrder(order, (isValid) => {
if (isValid) {
updateInventory(order, () => {
sendConfirmation(order, callback);
});
}
});
});
}
// 重构后
class OrderProcessor {
constructor() {
this.commands = [];
}
addCommand(command) {
this.commands.push(command);
}
execute(order, callback) {
let current = 0;
const next = () => {
if (current < this.commands.length) {
this.commands[current++](order, next);
} else {
callback();
}
};
next();
}
}
// 使用
const processor = new OrderProcessor();
processor.addCommand(fetchOrder);
processor.addCommand(validateOrder);
processor.addCommand(updateInventory);
processor.addCommand(sendConfirmation);
processor.execute(orderId, () => console.log('Done'));
设计模式与架构模式的关系
区分设计模式与架构模式:
- 设计模式解决微观层面的代码组织问题
- 架构模式关注宏观应用结构
- 两者可以协同工作
前端常见的架构模式组合:
// MV*架构示例
class Model {
constructor(data) {
this.data = data;
this.listeners = [];
}
set(key, value) {
this.data[key] = value;
this.notify();
}
subscribe(listener) {
this.listeners.push(listener);
}
notify() {
this.listeners.forEach(l => l(this.data));
}
}
class View {
constructor(template) {
this.template = template;
this.element = document.createElement('div');
}
render(data) {
this.element.innerHTML = this.template(data);
return this.element;
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.subscribe(data => this.updateView(data));
}
updateView(data) {
document.body.appendChild(this.view.render(data));
}
handleInput(key, value) {
this.model.set(key, value);
}
}
现代JavaScript特性对模式的影响
ES6+特性改变了某些模式的实现方式:
- Class语法替代构造函数模式
- 模块系统替代命名空间模式
- Proxy替代部分装饰器模式实现
- Symbol实现更好的私有成员
使用Proxy实现数据绑定:
function createReactiveObject(initialState, onChange) {
return new Proxy(initialState, {
set(target, property, value) {
const oldValue = target[property];
target[property] = value;
if (oldValue !== value) {
onChange(property, value, oldValue);
}
return true;
}
});
}
const state = createReactiveObject(
{ count: 0 },
(key, newVal, oldVal) => console.log(`${key} changed from ${oldVal} to ${newVal}`)
);
state.count = 1; // 触发日志输出
测试驱动下的模式选择
测试需求影响模式选择:
- 依赖注入模式提高可测试性
- 策略模式便于单元测试替换
- 工厂模式简化测试替身创建
可测试的服务层实现:
// 数据服务抽象
class DataService {
constructor(fetcher = fetch) {
this.fetcher = fetcher;
}
async getUsers() {
const response = await this.fetcher('/api/users');
return response.json();
}
}
// 测试时
const mockFetcher = () => Promise.resolve({
json: () => Promise.resolve([{ id: 1, name: 'Test' }])
});
const testService = new DataService(mockFetcher);
testService.getUsers().then(users => console.log(users));
性能分析与模式调整
通过性能分析优化模式选择:
- 内存分析发现对象创建开销 → 考虑享元模式
- 函数调用频繁 → 考虑策略模式缓存
- 复杂条件判断 → 状态模式可能更优
使用memoization优化计算:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用
const expensiveCalculation = memoize((a, b) => {
console.log('Calculating...');
return a * b + a / b;
});
console.log(expensiveCalculation(2, 3)); // 计算
console.log(expensiveCalculation(2, 3)); // 从缓存读取
浏览器环境下的特殊考量
前端特有因素影响模式选择:
- DOM操作成本高 → 适合命令模式封装
- 事件处理复杂 → 观察者/中介者模式
- 异步操作多 → Promise/async模式组合
批量DOM更新优化:
class DOMBatchUpdater {
constructor() {
this.updates = [];
this.batchTimer = null;
}
queueUpdate(element, property, value) {
this.updates.push({ element, property, value });
if (!this.batchTimer) {
this.batchTimer = requestAnimationFrame(() => this.flushUpdates());
}
}
flushUpdates() {
const updatesByElement = new Map();
this.updates.forEach(({ element, property, value }) => {
if (!updatesByElement.has(element)) {
updatesByElement.set(element, {});
}
updatesByElement.get(element)[property] = value;
});
updatesByElement.forEach((props, element) => {
Object.assign(element.style, props);
});
this.updates = [];
this.batchTimer = null;
}
}
// 使用
const updater = new DOMBatchUpdater();
updater.queueUpdate(divEl, 'color', 'red');
updater.queueUpdate(divEl, 'backgroundColor', 'white');
上一篇: 常见设计模式误用与反模式
下一篇: 设计模式的优缺点分析