JS设计模式快速参考指南

揭开 JavaScript 设计模式秘密的旅程,探索它们的意义、实现和实际应用。

创建模式是一种设计模式类别,用于解决与对象创建情况相关的常见问题。

1、单例模式
将特定对象的实例数量限制为一个。单例减少了对全局变量的需求,从而避免了名称冲突的风险。

class Animal {
  constructor() {
    if (typeof Animal.instance === 'object') {
      return Animal.instance;
    }

    Animal.instance = this;

    return this;
  }
}

export default Animal;


2、Prototype
原型模式在现有对象的基础上创建新对象,并设置默认属性值。

在本例中,我们可以使用 clone() 方法创建一个新对象 Fruit,其名称和权重与父对象相同。

class Fruit {
  constructor(name, weight) {
    this.name = name;
    this.weight = weight;
  }

  clone() {
    return new Fruit(this.name, this.weight);
  }
}

export default Fruit;

3、Factory
工厂模式创建新对象,委托子类实例化哪个类。

在本例中,MovieFactory 决定创建哪种类型的影片。

class MovieFactory {
  create(genre) {
    if (genre === 'Adventure') return new Movie(genre, 10000);
    if (genre === 'Action') return new Movie(genre, 11000);
  }
}

class Movie {
  constructor(type, price) {
    this.type = type;
    this.price = price;
  }
}

export default MovieFactory;

4、Abstract Factory
抽象工厂创建按主题分组的新对象,而不指定其具体类别。

function foodProducer(kind) {
  if (kind === 'protein') return proteinPattern;
  if (kind === 'fat') return fatPattern;
  return carbohydratesPattern;
}

function proteinPattern() {
  return new Protein();
}

function fatPattern() {
  return new Fat();
}

function carbohydratesPattern() {
  return new Carbohydrates();
}

class Protein {
  info() {
    return 'I am Protein.';
  }
}

class Fat {
  info() {
    return 'I am Fat.';
  }
}

class Carbohydrates {
  info() {
    return 'I am carbohydrates.';
  }
}

export default foodProducer;


结构模式是设计模式的一个类别,用于解决与对象和类的组成情况有关的常见问题。

5、Adapter
适配器模式允许类协同工作,将一个类接口创建为另一个接口。

在本例中,我们使用 SoldierAdapter 来使用当前系统中的传统方法 attack(),并支持新版本的士兵 SuperSoldiers。

class Soldier {
  constructor(level) {
    this.level = level;
  }

  attack() {
    return this.level * 1;
  }
}

class SuperSoldier {
  constructor(level) {
    this.level = level;
  }

  attackWithShield() {
    return this.level * 10;
  }
}

class SoldierAdapter {
  constructor(superSoldier) {
    this.superSoldier = superSoldier;
  }

  attack() {
    return this.superSoldier.attackWithShield();
  }
}

export { Soldier, SuperSoldier, SoldierAdapter };

6、Bridge
桥接模式允许在我们的类中使用一个接口,并根据我们接收的实例和需要返回的实例构建不同的实现。

在本例中,我们在士兵类型和武器类型之间创建了一座桥,这样我们就可以将武器实例正确地传递给我们的士兵。


class Soldier {
  constructor(weapon) {
    this.weapon = weapon;
  }
}

class SuperSoldier extends Soldier {
  constructor(weapon) {
    super(weapon);
  }

  attack() {
    return 'SuperSoldier, Weapon: ' + this.weapon.get();
  }
}

class IronMan extends Soldier {
  constructor(weapon) {
    super(weapon);
  }

  attack() {
    return 'Ironman, Weapon: ' + this.ink.get();
  }
}

class Weapon {
  constructor(type) {
    this.type = type;
  }

  get() {
    return this.type;
  }
}

class Shield extends Weapon {
  constructor() {
    super('shield');
  }
}

class Rocket extends Weapon {
  constructor() {
    super('rocket');
  }
}

export { SuperSoldier, IronMan, Shield, Rocket };

当您需要在运行时使用抽象中的特定实现时,请使用此模式。

7、Composite
复合模式允许创建具有原始项或对象集合属性的对象。集合中的每个项都可以包含其他集合,从而创建深嵌套结构。

在本例中,我们创建的计算设备子系统存储在一个内阁中,每个元素都可以是不同的实例。

//Equipment
class Equipment {
  getPrice() {
    return this.price || 0;
  }

  getName() {
    return this.name;
  }

  setName(name) {
    this.name = name;
  }
}

class Pattern extends Equipment {
  constructor() {
    super();
    this.equipments = [];
  }

  add(equipment) {
    this.equipments.push(equipment);
  }

  getPrice() {
    return this.equipments
      .map(equipment => {
        return equipment.getPrice();
      })
      .reduce((a, b) => {
        return a + b;
      });
  }
}

class Cabbinet extends Pattern {
  constructor() {
    super();
    this.setName('cabbinet');
  }
}

// --- leafs ---
class FloppyDisk extends Equipment {
  constructor() {
    super();
    this.setName('Floppy Disk');
    this.price = 70;
  }
}

class HardDrive extends Equipment {
  constructor() {
    super();
    this.setName('Hard Drive');
    this.price = 250;
  }
}

class Memory extends Equipment {
  constructor() {
    super();
    this.setName('Memory');
    this.price = 280;
  }
}

export { Cabbinet, FloppyDisk, HardDrive, Memory };

当您想表示对象的层次结构时,请使用这种模式。

8、Decorator
装饰器模式允许在运行时动态扩展对象的行为。

在本示例中,我们使用装饰器来扩展 Facebook 通知中的行为。


class Notification {
  constructor(kind) {
    this.kind = kind || "Generic";
  }
  
  getInfo() {
    return `I'm a ${this.kind} Notification`;
  }
}

class FacebookNotification extends Notification {
  constructor() {
    super(
"Facebook");
  }
  
  setNotification(msg) {
    this.message = msg;
  }
  
  getInfo() {
    return `${super.getInfo()} with the message: ${this.message}`;
  }
}

class SMSNotification extends Notification {
  constructor() {
    super(
"SMS");
  }
  
  getInfo() {
    return super.getInfo();
  }
}

export { FacebookNotification, SMSNotification };

当您想在运行时为对象添加扩展而不影响其他对象时,请使用此模式。

9、Facade
Facade 模式为子系统中的一组接口提供了简化接口。门面定义了更高级别的接口,使子系统更易于使用

在本例中,我们创建了一个简单的界面 Cart,它抽象了折扣、发货和费用等几个子系统的所有复杂功能。

class Cart {
  constructor() {
    this.discount = new Discount();
    this.shipping = new Shipping();
    this.fees = new Fees();
  }

  calc(price) {
    price = this.discount.calc(price);
    price = this.fees.calc(price);
    price += this.shipping.calc();

    return price;
  }
}

class Discount {
  calc(value) {
    return value * 0.85;
  }
}

class Shipping {
  calc() {
    return 500;
  }
}

class Fees {
  calc(value) {
    return value * 1.1;
  }
}

export default Cart;

当您想为复杂的子系统提供一个简单的接口时,请使用这种模式。

10、Flyweight
Flyweight 模式通过高效共享大量细粒度对象来节省内存。共享的 Flyweight 对象是不可变的,也就是说,它们无法更改,因为它们代表了与其他对象共享的特征。

在这个示例中,我们正在管理和创建某种食谱或烹饪应用程序的配料。

class Ingredient {
  constructor(name) {
    this.name = name;
  }
  
  getInfo() {
    return `I'm a ${this.name}`
  }
}

class Ingredients {
  constructor() {
    this.ingredients = {};
  }

  create(name) {
    let ingredient = this.ingredients[name];
    if (ingredient) return ingredient;

    this.ingredients[name] = new Ingredient(name);

    return this.ingredients[name];
  }
}

export { Ingredients };

当应用程序使用大量小对象,而这些对象的存储成本又很高,或者它们的身份并不重要时,就可以使用这种模式。

11、Proxy
代理模式为另一个对象提供一个代理或占位符对象,并控制对该另一个对象的访问。

在本例中,我们使用这种模式来限制飞行员的年龄。

class Plane {
  fly() {
    return 'flying';
  }
}

class PilotProxy {
  constructor(pilot) {
    this.pilot = pilot;
  }

  fly() {
    return this.pilot.age < 18 ? `too young to fly` : new Plane().fly();
  }
}

class Pilot {
  constructor(age) {
    this.age = age;
  }
}

export { Plane, PilotProxy, Pilot };

当一个对象受到严重限制,无法履行其职责时,可以使用这种模式。


行为模式是设计模式的一个类别,用于解决与通信和对象间分配相关的常见问题。

12、Chain of Responsibility
责任链模式允许将请求沿着有机会处理请求的对象链传递。收到请求后,每个处理程序都会决定是处理该请求还是将其传递给链中的下一个处理程序。

在本例中,我们将 Discount 类作为责任链对象来处理购物车中折扣多少的请求。


class ShoppingCart {
  constructor() {
    this.products = [];
  }

  addProduct(p) {
    this.products.push(p);
  }
}

class Discount {
  calc(products) {
    let ndiscount = new NumberDiscount();
    let pdiscount = new PriceDiscount();
    let none = new NoneDiscount();
    ndiscount.setNext(pdiscount);
    pdiscount.setNext(none);

    return ndiscount.exec(products);
  }
}

class NumberDiscount {
  constructor() {
    this.next = null;
  }

  setNext(fn) {
    this.next = fn;
  }

  exec(products) {
    let result = 0;
    if (products.length > 3) result = 0.05;

    return result + this.next.exec(products);
  }
}

class PriceDiscount {
  constructor() {
    this.next = null;
  }

  setNext(fn) {
    this.next = fn;
  }

  exec(products) {
    let result = 0;
    let total = products.reduce((a, b) => (a + b), 0);

    if (total >= 500) result = 0.1;

    return result + this.next.exec(products);
  }
}

class NoneDiscount {
  exec() {
    return 0;
  }
}

export { ShoppingCart, Discount };

当不止一个对象可以处理一个请求,且运行时已知道该信息时,可使用此模式。

13、Command
命令模式允许将请求封装为对象。通过这种转换,您可以将请求作为方法参数传递,延迟或排队执行请求,并支持可撤销操作。

在本例中,我们将 on/off 指令封装为对象,并将其作为参数传递给汽车构造函数。

class Car {
  constructor(instruction) {
    this.instruction = instruction;
  }

  execute() {
    this.instruction.execute();
  }
}

class Engine {
  constructor() {
    this.state = false;
  }

  on() {
    this.state = true;
  }

  off() {
    this.state = false;
  }
}

class OnInstruction {
  constructor(engine) {
    this.engine = engine;
  }

  execute() {
    this.engine.on();
  }
}

class OffInstruction {
  constructor(engine) {
    this.engine = engine;
  }

  execute() {
    this.engine.off();
  }
}

export { Car, Engine, OnInstruction, OffInstruction };

当需要处理大量请求队列或需要批撤销操作时,请使用此模式。

14、Interpreter
当问题经常出现时,解释器模式可以为简单语言创建一个语法;可以考虑用简单语言的句子来表示问题,这样解释器就可以通过解释句子来解决问题。

在本例中,我们将创建一个简单的数字,用于对几个数字进行乘幂运算。


class Mul {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  interpreter() {
    return this.left.interpreter() * this.right.interpreter();
  }
}

class Pow {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  interpreter() {
    return this.left.interpreter() - this.right.interpreter();
  }
}

class Num {
  constructor(val) {
    this.val = val;
  }

  interpreter() {
    return this.val;
  }
}

export { Num, Mul, Pow };

当您要解释给定的语言时,可以用这种模式将语句表示为抽象语法树。

15、Iterator
迭代器模式允许访问集合中的元素,而不暴露其底层表示。

在这个示例中,我们创建了一个简单的迭代器,它将包含一个数组中的元素,使用 next() 和 hasNext() 方法,我们可以遍历所有元素。

class Iterator {
  constructor(el) {
    this.index = 0;
    this.elements = el;
  }

  next() {
    return this.elements[this.index++];
  }

  hasNext() {
    return this.index < this.elements.length;
  }
}

export default Iterator;

当您想访问对象的内容集合而又不知道其内部表示方式时,请使用这种模式。

16、Mediator
调解器模式可以通过定义一个对象来封装一组对象的交互方式,从而减少对象之间的混乱依赖关系。

在本例中,我们创建了一个类 Mediator TrafficTower,它将允许我们从飞机实例中获取所有位置信息。

class TrafficTower {
  constructor() {
    this.airplanes = [];
  }

  getPositions() {
    return this.airplanes.map(airplane => {
      return airplane.position.showPosition();
    });
  }
}

class Airplane {
  constructor(position, trafficTower) {
    this.position = position;
    this.trafficTower = trafficTower;
    this.trafficTower.airplanes.push(this);
  }

  getPositions() {
    return this.trafficTower.getPositions();
  }
}

class Position {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
  
  showPosition() {
    return `My Position is ${x} and ${y}`;
  }
}

export { TrafficTower, Airplane, Position };

当一组对象以复杂的方式进行通信时,可使用这种模式。

17、Memento
记忆模式允许捕捉对象的内部状态并将其外部化,以便以后可以将对象恢复到这种状态。

在本例中,我们将创建一种简单的方法来存储值,并在需要时恢复快照。


class Memento {
  constructor(value) {
    this.value = value;
  }
}

const originator = {
  store: function(val) {
    return new Memento(val);
  },
  restore: function(memento) {
    return memento.value;
  }
};

class Keeper {
  constructor() {
    this.values = [];
  }

  addMemento(memento) {
    this.values.push(memento);
  }

  getMemento(index) {
    return this.values[index];
  }
}

export { originator, Keeper };


当您想制作对象状态的快照以恢复对象以前的状态时,请使用此模式。

18、Observer
观察者模式允许在对象之间定义一对多的依赖关系,这样当一个对象改变状态时,它的所有依赖对象都会收到通知并自动更新。

在这个示例中,我们创建了一个简单的类产品,其他类可以通过 register() 方法观察注册的变化,而当某个对象更新时,notifyAll() 方法将与所有观察者交流这些变化。

class ObservedProduct {
 constructor() {
    this.price = 0;
    this.actions = [];
  }

  setBasePrice(val) {
    this.price = val;
    this.notifyAll();
  }

  register(observer) {
    this.actions.push(observer);
  }

  unregister(observer) {
    this.actions.remove.filter(function(el) {
      return el !== observer;
    });
  }

  notifyAll() {
    return this.actions.forEach(
      function(el) {
        el.update(this);
      }.bind(this)
    );
  }
}

class fees {
  update(product) {
    product.price = product.price * 1.2;
  }
}

class profit {
  update(product) {
    product.price = product.price * 2;
  }
}

export { ObservedProduct, fees, profit };

当改变一个对象的状态可能需要改变其他对象,而实际对象集事先未知或动态变化时,使用这种模式。

19、State
状态模式允许对象在其内部状态发生变化时改变其行为。

在本示例中,我们将创建一个简单的状态模式,其中的 Order 类将使用 next() 方法更新状态。

class OrderStatus {
  constructor(name, nextStatus) {
    this.name = name;
    this.nextStatus = nextStatus;
  }

  next() {
    return new this.nextStatus();
  }
}

class WaitingForPayment extends OrderStatus {
  constructor() {
    super('waitingForPayment', Shipping);
  }
}

class Shipping extends OrderStatus {
  constructor() {
    super('shipping', Delivered);
  }
}

class Delivered extends OrderStatus {
  constructor() {
    super('delivered', Delivered);
  }
}

class Order {
  constructor() {
    this.state = new WaitingForPayment();
  }

  nextPattern() {
    this.state = this.state.next();
  }
}

export default Order;


当对象的行为取决于其状态,而其行为在运行时的变化又取决于该状态时,使用这种模式。

20、Strategy
策略模式允许定义一系列算法,对每种算法进行封装,并使它们可以互换。

在这个例子中,我们有一组折扣,可以应用到 ShoppingCart 中,这里的诀窍在于我们可以将要应用的函数传递给构造函数,从而改变折扣的金额。

class ShoppingCart {
  constructor(discount) {
    this.discount = discount;
    this.amount = 0;
  }

  checkout() {
    return this.discount(this.amount);
  }

  setAmount(amount) {
    this.amount = amount;
  }
}

function guest(amount) {
  return amount;
}

function regular(amount) {
  return amount * 0.9;
}

function premium(amount) {
  return amount * 0.8;
}

export { ShoppingCart, guest, regular, premium };

当你有很多相似的类,只是执行某些行为的方式不同时,就可以使用这种模式。

21、Template
模板模式允许在超类中定义算法的骨架,但允许子类在不改变算法结构的情况下覆盖算法的特定步骤。

在本例中,我们创建了一个简单的模板方法来计算税金,并在增值税和消费税(税金类型)中扩展了该模板,这样我们就可以在多个税金类中重复使用相同的结构。

class Tax {
  calc(value) {
    if (value >= 1000) value = this.overThousand(value);

    return this.complementaryFee(value);
  }

  complementaryFee(value) {
    return value + 10;
  }
}

class VAT extends Tax {
  constructor() {
    super();
  }

  overThousand(value) {
    return value * 1.1;
  }
}

class GST extends Tax {
  constructor() {
    super();
  }

  overThousand(value) {
    return value * 1.2;
  }
}

export { VAT, GST };

如果只想让客户扩展算法的特定步骤,而不想扩展整个算法或其结构,请使用这种模式。

22、Visitor
访问者模式允许将算法与操作对象分开。

在本例中,我们创建了一个结构来计算两类员工的奖金,通过这种方法,我们可以将奖金方法扩展到更多类型的员工,如 CEO 奖金、VP 奖金等。

function bonusPattern(employee) {
  if (employee instanceof Manager) employee.bonus = employee.salary * 2;
  if (employee instanceof Developer) employee.bonus = employee.salary;
}

class Employee {
  constructor(salary) {
    this.bonus = 0;
    this.salary = salary;
  }

  accept(item) {
    item(this);
  }
}

class Manager extends Employee {
  constructor(salary) {
    super(salary);
  }
}

class Developer extends Employee {
  constructor(salary) {
    super(salary);
  }
}

export { Developer, Manager, bonusPattern };


当一个对象结构包含许多类,而您想对该结构的元素执行依赖于其类的操作时,请使用此模式。