阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 设计模式的优缺点分析

设计模式的优缺点分析

作者:陈川 阅读数:1753人阅读 分类: JavaScript

设计模式是软件开发中解决常见问题的可复用方案,在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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌