一、为什么需要设计模式
在日常开发中,我们经常会遇到一些似曾相识的问题。比如多个组件需要共享数据,或者需要控制某个对象的创建过程。这时候如果每次都从头开始写代码,不仅效率低下,而且容易产生bug。设计模式就像是经过千锤百炼的解决方案模板,能帮助我们快速应对这些常见场景。
举个例子,电商网站的商品详情页需要展示库存信息,这个数据会被添加到购物车组件、收藏组件等多个地方。如果每个组件都单独请求库存接口,不仅会造成资源浪费,还可能导致数据不一致。这时候,观察者模式就能派上用场了。
二、单例模式实战
单例模式确保一个类只有一个实例,这在需要全局唯一对象的场景非常有用。比如前端的状态管理、日志记录器等。
// 技术栈:JavaScript ES6
class Logger {
constructor() {
if (!Logger.instance) {
this.logs = [];
Logger.instance = this;
}
return Logger.instance;
}
log(message) {
this.logs.push(message);
console.log(`[LOG]: ${message}`);
}
printLogCount() {
console.log(`共 ${this.logs.length} 条日志`);
}
}
// 使用示例
const logger1 = new Logger();
const logger2 = new Logger();
logger1.log('第一次日志');
logger2.log('第二次日志');
console.log(logger1 === logger2); // true,说明是同一个实例
logger1.printLogCount(); // 共 2 条日志
这个实现有几个关键点:
- 通过静态属性instance保存唯一实例
- 构造函数中判断是否已存在实例
- 提供全局访问点
应用场景:
- 全局状态管理
- 日志记录器
- 浏览器中的window对象
注意事项:
- 在多线程环境下需要额外处理(前端一般不需要)
- 过度使用会导致代码耦合度高
三、观察者模式实战
观察者模式定义了对象间一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知。这在事件处理系统、数据绑定等场景非常有用。
// 技术栈:JavaScript ES6
class Subject {
constructor() {
this.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('数据更新了!');
// 输出:
// 收到通知:数据更新了!
// 收到通知:数据更新了!
subject.unsubscribe(observer1);
subject.notify('再次更新');
// 输出:
// 收到通知:再次更新
实际项目中,我们可以用它来实现:
- 自定义事件系统
- 响应式数据绑定
- 实时通知功能
优点:
- 松耦合,主题和观察者可以独立变化
- 支持广播通信
缺点:
- 如果观察者过多,通知会耗时
- 循环依赖可能导致系统崩溃
四、策略模式实战
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。这在需要根据不同条件执行不同逻辑的场景特别有用。
// 技术栈:JavaScript ES6
// 定义策略类
const strategies = {
'level1'(bonus) {
return bonus * 0.1;
},
'level2'(bonus) {
return bonus * 0.2;
},
'level3'(bonus) {
return bonus * 0.3;
}
};
// 上下文类
class BonusCalculator {
constructor() {
this.strategy = null;
this.bonus = 0;
}
setStrategy(strategy) {
this.strategy = strategies[strategy];
}
setBonus(bonus) {
this.bonus = bonus;
}
calculate() {
if (!this.strategy) {
throw new Error('请先设置策略');
}
return this.strategy(this.bonus);
}
}
// 使用示例
const calculator = new BonusCalculator();
calculator.setBonus(10000);
calculator.setStrategy('level2');
console.log(calculator.calculate()); // 2000
应用场景:
- 表单验证规则
- 促销折扣计算
- 算法选择
优点:
- 避免多重条件判断
- 易于扩展新策略
- 算法可以复用
注意事项:
- 会增加策略类数量
- 客户端需要了解所有策略
五、装饰器模式实战
装饰器模式允许向现有对象添加新功能,同时又不改变其结构。这在需要动态添加功能的场景非常有用。
// 技术栈:JavaScript ES6 + TypeScript装饰器语法
// 简单的装饰器函数
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用 ${name} 参数:${args}`);
return original.apply(this, args);
};
return descriptor;
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
// 使用示例
const calc = new Calculator();
console.log(calc.add(2, 3));
// 输出:
// 调用 add 参数:2,3
// 5
实际应用:
- 日志记录
- 权限控制
- 性能监控
优点:
- 比继承更灵活
- 可以动态添加/删除功能
- 符合开闭原则
缺点:
- 会产生许多小对象
- 过度使用会使程序变得复杂
六、总结
设计模式不是银弹,但确实是解决特定问题的有效工具。在实际项目中,我们常常需要根据具体场景灵活运用甚至组合多种模式。比如一个电商系统可能同时使用:
- 单例模式管理购物车
- 观察者模式处理库存变化
- 策略模式计算不同会员等级的折扣
- 装饰器模式添加日志和权限控制
记住,设计模式的本质是提高代码的可维护性和可扩展性,而不是为了模式而模式。当简单的代码就能解决问题时,不要过度设计。
最后,分享一个选择设计模式的小技巧:当你发现自己在写相似的代码结构第三次时,可能就是时候考虑引入适当的设计模式了。
评论