设计模式的优缺点分析
设计模式是软件开发中解决常见问题的可复用方案,在JavaScript中合理运用设计模式能提升代码的可维护性、可扩展性和复用性。不同模式适用于不同场景,各有其优势和局限性,需要根据具体需求权衡选择。
单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。在JavaScript中常用于管理全局状态或共享资源。
优点:
- 减少内存开销,避免重复创建相同对象
- 提供对唯一实例的受控访问
- 适合全局状态管理,如Vuex/Redux的store
缺点:
- 违反单一职责原则,可能承担过多功能
- 难以进行单元测试,因为全局状态会被共享
- 可能导致代码耦合度过高
class Logger {
constructor() {
if (!Logger.instance) {
this.logs = [];
Logger.instance = this;
}
return Logger.instance;
}
log(message) {
this.logs.push(message);
console.log(message);
}
}
const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true
工厂模式
工厂模式定义一个创建对象的接口,但让子类决定实例化哪个类。在JavaScript中常用于创建复杂对象或需要根据不同条件返回不同实例的场景。
优点:
- 将对象创建与使用分离,降低耦合度
- 易于扩展,添加新产品只需新增工厂类
- 隐藏具体产品类的实现细节
缺点:
- 增加了系统复杂度,需要引入更多类和接口
- 客户端可能需要知道具体工厂才能创建所需对象
class Car {
constructor(options) {
this.doors = options.doors || 4;
this.color = options.color || 'white';
}
}
class Truck {
constructor(options) {
this.doors = options.doors || 2;
this.color = options.color || 'black';
this.payload = options.payload || '1ton';
}
}
class VehicleFactory {
createVehicle(type, options) {
switch(type) {
case 'car':
return new Car(options);
case 'truck':
return new Truck(options);
default:
throw new Error('Unknown vehicle type');
}
}
}
const factory = new VehicleFactory();
const myCar = factory.createVehicle('car', { color: 'red' });
观察者模式
观察者模式定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。
优点:
- 支持简单的广播通信,自动通知所有订阅者
- 抽象耦合,主题和观察者之间松耦合
- 支持动态添加和移除观察者
缺点:
- 通知顺序不可控,可能导致循环调用
- 观察者过多时通知效率可能成为瓶颈
- 观察者不知道彼此存在,可能导致更新冲突
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(`Received data: ${data}`);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello World!');
装饰器模式
装饰器模式允许向现有对象动态添加新功能而不改变其结构,是继承的替代方案。
优点:
- 比继承更灵活,可以动态添加或删除功能
- 避免子类爆炸问题
- 符合开放-封闭原则
缺点:
- 会产生许多小对象,增加系统复杂度
- 装饰器顺序可能影响行为
- 难以从对象中移除特定装饰器
class Coffee {
cost() {
return 5;
}
}
class MilkDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost() + 2;
}
}
class SugarDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost() + 1;
}
}
let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);
console.log(myCoffee.cost()); // 8
策略模式
策略模式定义一系列算法,将每个算法封装起来并使它们可以互相替换,让算法独立于使用它的客户端变化。
优点:
- 避免多重条件判断语句
- 算法可以自由切换
- 易于扩展新的算法
- 符合开闭原则
缺点:
- 客户端必须了解所有策略类
- 会增加策略类的数量
- 各策略类需要对外暴露
class Shipping {
constructor(strategy) {
this.strategy = strategy;
}
calculate(package) {
return this.strategy.calculate(package);
}
}
class UPS {
calculate(package) {
return package.weight * 1.5;
}
}
class Fedex {
calculate(package) {
return package.weight * 2.5 + package.distance * 0.5;
}
}
const package = { weight: 10, distance: 100 };
const upsShipping = new Shipping(new UPS());
console.log(upsShipping.calculate(package)); // 15
const fedexShipping = new Shipping(new Fedex());
console.log(fedexShipping.calculate(package)); // 75
代理模式
代理模式为其他对象提供一种代理以控制对这个对象的访问,在访问对象时引入一定程度的间接性。
优点:
- 保护目标对象,控制访问权限
- 扩展目标对象的功能
- 降低系统耦合度
缺点:
- 增加系统复杂度
- 请求处理速度可能变慢
- 需要额外的工作来维护代理
class RealImage {
constructor(filename) {
this.filename = filename;
this.loadFromDisk();
}
display() {
console.log(`Displaying ${this.filename}`);
}
loadFromDisk() {
console.log(`Loading ${this.filename}`);
}
}
class ProxyImage {
constructor(filename) {
this.filename = filename;
this.realImage = null;
}
display() {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
const image = new ProxyImage('test.jpg');
image.display(); // 第一次会加载
image.display(); // 第二次直接显示
模块模式
模块模式使用闭包封装"私有"状态和组织结构,提供公共API的同时隐藏实现细节。
优点:
- 封装私有变量和方法
- 减少全局变量污染
- 代码组织更清晰
- 支持信息隐藏
缺点:
- 私有成员难以扩展
- 难以进行单元测试
- 修改可见性需要重构代码
const counterModule = (function() {
let count = 0;
function increment() {
count++;
}
function reset() {
count = 0;
}
function getCount() {
return count;
}
return {
increment,
reset,
getCount
};
})();
counterModule.increment();
console.log(counterModule.getCount()); // 1
适配器模式
适配器模式将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而不能一起工作的类可以一起工作。
优点:
- 让任何两个没有关联的类可以一起运行
- 提高类的复用性
- 增加类的透明度
- 灵活性好
缺点:
- 过多使用适配器会使系统非常混乱
- 某些语言限制可能导致实现困难
- 不是设计阶段考虑的模式,而是解决已有问题
class OldCalculator {
operations(t1, t2, operation) {
switch(operation) {
case 'add':
return t1 + t2;
case 'sub':
return t1 - t2;
default:
return NaN;
}
}
}
class NewCalculator {
add(t1, t2) {
return t1 + t2;
}
sub(t1, t2) {
return t1 - t2;
}
}
class CalculatorAdapter {
constructor() {
this.calculator = new NewCalculator();
}
operations(t1, t2, operation) {
switch(operation) {
case 'add':
return this.calculator.add(t1, t2);
case 'sub':
return this.calculator.sub(t1, t2);
default:
return NaN;
}
}
}
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15
const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15
const adapter = new CalculatorAdapter();
console.log(adapter.operations(10, 5, 'add')); // 15
组合模式
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
优点:
- 高层模块调用简单
- 节点自由增加
- 忽略组合对象和单个对象的不同
缺点:
- 设计较复杂,需要识别出树枝和树叶
- 不容易限制组合中的组件类型
- 使设计变得更加抽象
class Employee {
constructor(name, position) {
this.name = name;
this.position = position;
this.subordinates = [];
}
add(employee) {
this.subordinates.push(employee);
}
remove(employee) {
const index = this.subordinates.indexOf(employee);
if (index > -1) {
this.subordinates.splice(index, 1);
}
}
display(indent = '') {
console.log(`${indent}${this.name} - ${this.position}`);
this.subordinates.forEach(employee => {
employee.display(indent + ' ');
});
}
}
const ceo = new Employee('John', 'CEO');
const headSales = new Employee('Robert', 'Head Sales');
const headMarketing = new Employee('Michel', 'Head Marketing');
const clerk1 = new Employee('Laura', 'Marketing Clerk');
const clerk2 = new Employee('Bob', 'Sales Clerk');
ceo.add(headSales);
ceo.add(headMarketing);
headSales.add(clerk2);
headMarketing.add(clerk1);
ceo.display();
状态模式
状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
优点:
- 将与特定状态相关的行为局部化
- 减少条件分支语句
- 方便添加新的状态和转换
缺点:
- 增加系统中类和对象的个数
- 结构与实现都较为复杂
- 不符合"开闭原则",添加新状态可能修改原有代码
class TrafficLight {
constructor() {
this.states = [new GreenLight(), new YellowLight(), new RedLight()];
this.current = this.states[0];
}
change() {
const totalStates = this.states.length;
let currentIndex = this.states.findIndex(light => light === this.current);
this.current = this.states[(currentIndex + 1) % totalStates];
}
sign() {
return this.current.sign();
}
}
class Light {
constructor(light) {
this.light = light;
}
}
class GreenLight extends Light {
constructor() {
super('green');
}
sign() {
return 'GO';
}
}
class YellowLight extends Light {
constructor() {
super('yellow');
}
sign() {
return 'ATTENTION';
}
}
class RedLight extends Light {
constructor() {
super('red');
}
sign() {
return 'STOP';
}
}
const trafficLight = new TrafficLight();
console.log(trafficLight.sign()); // GO
trafficLight.change();
console.log(trafficLight.sign()); // ATTENTION
trafficLight.change();
console.log(trafficLight.sign()); // STOP
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:如何选择合适的设计模式