Inventário de padrões de design JavaScript comuns

prefácio

Padrões de projeto são soluções para vários problemas que comumente existem no projeto de software.

Pode ser entendido simplesmente como algumas rotinas de desenvolvimento de programas.

Quando encontramos uma cena adequada, podemos pensar em um padrão de design que corresponda a essa cena como um reflexo condicionado. Por exemplo, um componente não pode atender aos requisitos existentes e novas funções precisam ser adicionadas a ele. O negócio dentro do componente é relativamente independente e não queremos modificá-lo.

Neste momento, podemos usar o padrão decorator.

padrão de construtor

Existem os dois objetos a seguir:

const jack = {
    
    
  name'jack',
  age18
};
const jim = {
  name'jim',
  age17
};

Abstraímos as propriedades desses dois objetos, que é o padrão do construtor.

function Person(name, age{
    
    
  this.name = name;
  this.age = age;
}
const jack = new Person('jack'18);
const jim = new Person('jim'17);

padrão de protótipo

Agora queremos adicionar um método say em Person, para que tanto jack quanto jim possam usar este método say:

function Person(name, age{
    
    
  this.name = name;
  this.age = age;
  this.say = function ({
    console.log('说话');
  };
}
const jack = new Person('jack'18);
const jim = new Person('jim'17);
console.log(jack.say === jim.say); // false

Adicione o método diretamente ao construtor e cada instância armazenará uma cópia, resultando em um desperdício de memória. Como resolver este problema?

Podemos adicionar o método say ao protótipo Person:

function Person(name, age{
    
    
  this.name = name;
  this.age = age;
}
Person.prototype.say = function ({
  console.log('说话');
};
const jack = new Person('jack'18);
const jim = new Person('jim'17);
console.log(jack.say === jim.say); // true

Isso resolve o problema do desperdício de memória. Esse é o modo de protótipo.

Padrão Simples de Fábrica

Apenas um parâmetro é necessário para obter o objeto que precisamos sem conhecer os detalhes da criação.

class Dress {}
class Shirt {}

class ClothFactory {
  static create(type) {
    switch (type) {
      case 'dress':
        return new Dress();
      case 'shirt':
        return new Shirt();
      default:
        break;
    }
  }
}
const dress = ClothFactory.create('dress');
const shirt = ClothFactory.create('shirt');
console.log(dress); // dress实例
console.log(shirt); // shirt实例

Pode-se ver que não nos preocupamos em como criar Dress e Shirt e apenas precisamos passar parâmetros, e ClothFactory nos ajudará a criá-los.

padrão de fábrica abstrato

O padrão de fábrica abstrata é tornar o negócio aplicável à criação de um cluster de classe de produto por meio da abstração da classe e não é responsável pela instância de um determinado produto de classe.

class Cloth {
    
    
  constructor() {
    if (new.target === Cloth) {
      throw new Error('Cloth是抽象类, 不能实例化');
    }
  }
  consoleClothName() {
    throw new Error('consoleClothName要被重写');
  }
}
class Dress extends Cloth {
  constructor() {
    super();
    this.name = '裙子';
  }
  consoleClothName() {
    console.log('裙子');
  }
}
class Shirt extends Cloth {
  constructor() {
    super();
    this.name = '衬衫';
  }
  consoleClothName() {
    console.log('衬衫');
  }
}

class ClothFactory {
  static create(type) {
    switch (type) {
      case 'dress':
        return Dress;
      case 'shirt':
        return Shirt;
      default:
        break;
    }
  }
}

const DressClass = ClothFactory.create('dress');
new DressClass().consoleClothName(); // 裙子
new Cloth(); // Uncaught Error: Cloth是抽象类, 不能实例化

padrão singleton

O padrão singleton garante que uma classe só pode ser instanciada uma vez.

class Person {
    
    
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  static getInstance(name, age) {
    // 类函数中的 this 指向类
    if (!this.instance) {
      this.instance = new Person(name, age);
    }
    return this.instance;
  }
}
const jack = Person.getInstance('Jack'18);
const jim = Person.getInstance('Jim'18);
console.log(jack === jim); // true

A mesma instância é retornada pela segunda vez e pela primeira vez, garantindo que a classe Person seja instanciada apenas uma vez.

Você também pode colocar o processamento singleton no construtor para garantir que a instância que criamos sempre seja a mesma.

class Person {
    
    
  constructor(name, age) {
    if (!Person.instance) {
      this.name = name;
      this.age = age;
      Person.instance = this;
    }
    return Person.instance;
  }
}
const jack = new Person('Jack'18);
const jim = new Person('Jim'18);
console.log(jack === jim);

Pode-se ver que as duas novas instâncias retornam o mesmo objeto de instância.

O padrão singleton também pode ser implementado usando encerramentos:

var singleton = (function (name, age{
    
    
  function Person(name, age{
    this.name = name;
    this.age = age;
  }

  var instance = null;
  return function ({
    if (!instance) {
      instance = new Person(name, age);
    }
    return instance;
  };
})();

console.log(singleton('jack'18) === singleton('jim'16)); // true

padrão decorador

O padrão decorador envolve e estende o objeto original sem alterá-lo. Esse padrão é amplamente utilizado, por exemplo: funções de throttling e anti-vibração, os componentes de alto nível do React são aplicações práticas do padrão decorator.

class Person {
    
    
  sayChinese() {
    console.log('我会说中文');
  }
}

class PersonDecorator {
  constructor(person) {
    this.person = person;
  }
  sayChinese() {
    this.person.sayChinese();
  }
  sayEnglish() {
    console.log('我会说英文');
  }
}

const jack = new Person();
jack.sayChinese();

const newJack = new PersonDecorator(jack);
newJack.sayChinese();
newJack.sayEnglish(); // 我会说英文

Conseguimos o efeito de que o newJack pode dizer inglês por meio da extensão da embalagem.

padrão de adaptador

O padrão do adaptador pode nos ajudar a resolver problemas de incompatibilidade, transformando a interface de uma classe em outra interface que o cliente espera. Não afeta a implementação existente, compatível com o código que chama a interface antiga

class PersonA {
    
    
  say() {
    console.log('说话');
  }
}

class PersonB {
  speak() {
    console.log('说话');
  }
}

new PersonA().say();
new PersonB().say(); // Uncaught TypeError: (intermediate value).say is not a function

PersonB 通过 speak 才能说话,可是我们现在想要让 PersonB 与 PersonA 保持一致,通过 say 来说话。我们可以写一个适配器类:

class PersonBAdapter {
    
    
  constructor(personB) {
    this.personB = personB;
  }
  say() {
    this.personB.speak();
  }
}

new PersonBAdapter().say(); // 说话

代理模式

出于某种考虑,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的。这样的模式就是代理模式。 比如,老板喜欢喝咖啡,但是买咖啡这件事情交给秘书去做,秘书就是老板的代理。

class Coffee {
    
    
  drink() {
    console.log('咖啡好喝');
  }
}
class Secretary {
  buyCoffee() {
    return new Coffee();
  }
}
class Boss {
  buyCoffee() {
    return new Secretary().buyCoffee();
  }
}

const myBoss = new Boss();
myBoss.buyCoffee().drink(); // 咖啡好喝

代码中可以看出,老板没有实际去买咖啡(new Coffee()这个操作),而是交给了秘书去做。

有 4 中常见的代理应用场景:事件代理、保护代理、虚拟代理和缓存代理。

事件代理

有这样一个场景:一个页面有 100 个按钮,每个按钮点击会弹出 hello。如果给每个按钮都添加点击事件,则需要 100 个点击事件,极其消耗性能。

不妨利用事件冒泡的原理,把事件加到它们的公共父级上,触发执行效果。这样一方面能够减少事件数量,提高性能;另一方面对于新添加的元素,依然可以触发该事件

保护代理

顾名思义,保护目标对象,替他过滤掉不必要的操作。比如下面这个例子,秘书替老板过滤掉不必要的员工建议。

class Employee {
    
    
  advice(secretary, content) {
    secretary.handleAdvice(content);
  }
}
class Secretary {
  constructor(boss) {
    this.boss = boss;
  }
  handleAdvice(content) {
    if (content === '好建议') {
      this.boss.getAdvice(content);
    } else {
      console.log('老板只接受好建议');
    }
  }
}
class Boss {
  getAdvice(content) {
    console.log(content);
  }
}

const boss = new Boss();
const secretary = new Secretary(boss);
new Employee().advice(secretary, '好建议'); // 好建议
new Employee().advice(secretary, '坏建议'); // 老板只接受好建议

虚拟代理

虚拟代理是把一些开销很大的对象,延迟到真正需要它的时候才去创建执行。

缓存代理

缓存代理就是将消耗性能的工作加上缓存,同样的工作下次直接使用缓存即可。比如下面这个例子,我们通过代理对累加做缓存:

function addAll({
    
    
  console.log('计算');
  let sum = 0;
  for (let i = 0, len = arguments.length; i < len; i++) {
    sum += arguments[i];
  }
  return sum;
}

const addProxy = (function ({
  let cache = {};
  return function ({
    const type = Array.prototype.join.call(arguments);
    if (!cache[type]) {
      cache[type] = addAll.apply(thisarguments);
    }
    return cache[type];
  };
})();

console.log(addProxy(345)); // 12
console.log(addProxy(345)); // 12
console.log(addProxy(345)); // 12

最终,三次结果都返回 12,但是只计算了一次。

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。

策略模式是比较常用的模式。拿年终奖举例,S 绩效 4 倍月薪,A 绩效 3 倍月薪,B 绩效 2 倍月薪。正常一个计算奖金的函数如下

function bonus(performance, salary{
    
    
  if (performance === 'S') {
    return 4 * salary;
  }
  if (performance === 'A') {
    return 3 * salary;
  }
  if (performance === 'B') {
    return 2 * salary;
  }
}

这个函数违背了单一功能原则,一个函数里处理了 3 份逻辑。我们把这三份逻辑单独抽离出来。

function performanceS(salary{
    
    
  return 4 * salary;
}
function performanceA(salary{
  return 3 * salary;
}
function performanceB(salary{
  return 2 * salary;
}

function bonus(performance, salary{
  if (performance === 'S') {
    return performanceS(salary);
  }
  if (performance === 'A') {
    return performanceA(salary);
  }
  if (performance === 'B') {
    return performanceB(salary);
  }
}

代码看上去好像更复杂了,但是代码的逻辑更清晰了。假如每个计算逻辑都比较复杂的话,无疑这样处理更有利于理解。 平时工作中,针对该问题,可能更多是下面这种处理方式:

const performanceStrategies = {
    
    
  S: 4,
  A: 3,
  B: 2
};

function bonus(performance, salary) {
  return performanceStrategies[performance] * salary;
}

观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

class Publisher {
    
    
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    this.observers.forEach((item, index) => {
      if (item === observer) {
        this.observers.splice(index, 1);
      }
    });
  }

  notify() {
    this.observers.forEach((item) => {
      item.update();
    });
  }
}

class Observer1 {
  update() {
    console.log('observer1接收到了通知');
  }
}
class Observer2 {
  update() {
    console.log('observer2接收到了通知');
  }
}

const publisher = new Publisher();
const observer1 = new Observer1();
const observer2 = new Observer2();
publisher.addObserver(observer1);
publisher.addObserver(observer2);
publisher.notify(); // observer1接收到了通知 observer2接收到了通知

发布订阅模式

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在

同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在

class PubSub {
    
    
  constructor() {
    this.messageObj = {};
    this.listenerObj = {};
  }

  addPublish(type, content) {
    if (!this.messageObj[type]) {
      this.messageObj[type] = [];
    }
    this.messageObj[type].push(content);
  }

  addSubscribe(type, callback) {
    if (!this.listenerObj[type]) {
      this.listenerObj[type] = [];
    }
    this.listenerObj[type].push(callback);
  }

  notify(type) {
    const messageList = this.messageObj[type];
    (this.listenerObj[type] || []).forEach((callback) => callback(messageList));
  }
}

class Publisher {
  constructor(name, pubsub) {
    this.name = name;
    this.pubsub = pubsub;
  }
  publish(type, content) {
    this.pubsub.addPublish(type, content);
  }
}

class Subscriber {
  constructor(name, pubsub) {
    this.name = name;
    this.pubsub = pubsub;
  }
  subscribe(type, callback) {
    this.pubsub.addSubscribe(type, callback);
  }
}

const pubsub = new PubSub();

const publishA = new Publisher('publishA', pubsub);
publishA.publish('A''this is a');
publishA.publish('A''this is another a');
publishA.publish('B''this is b');
const publishB = new Publisher('publishB', pubsub);

const subscribeA = new Subscriber('subscribeA', pubsub);
subscribeA.subscribe('A', (res) => console.log(res));
subscribeA.subscribe('B', (res) => console.log(res));

pubsub.notify('A'); // ['this is a', 'this is another a']
pubsub.notify('B'); // ['this is b']

代码中我们创建了两个发布者, publishA 和 publishB : publishA 发布了两个 A 类型信息,publishB 发布了一个 B 类型信息

一个订阅者, subscribeA : subscribeA 订阅了 A 类型和 B 类型的信息

享元模式

享元模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

一个简单的例子:假如有个商店,电里有 50 种衣服,我们需要让塑料模特穿上衣服拍照。正常情况下,需要 50 个塑料模特。

class Model {
    
    
  constructor(cloth) {
    this.cloth = cloth;
  }
  takePhoto() {
    console.log(`cloth:${ this.cloth}`);
  }
}

for (let i = 0; i < 50; i++) {
  const model = new Model(i);
  model.takePhoto();
}

但是仔细考虑下,我们并不需要这么多塑料模特,塑料模特可以反复使用,一个就够了。

const model = new Model();
for (let i = 0; i < 50; i++) {
  model.cloth = i;
  model.takePhoto();
}

通过复用,只需要一个模型实例就完成了同样的功能。 实际开发中的情况可能更复杂一些,但是核心是一样的。通过共享来优化性能。常见的应用有线程池、对象池等。

参考文档:

https://juejin.cn/post/7072175210874535967#heading-69

本文由 mdnice 多平台发布

Acho que você gosta

Origin blog.csdn.net/ppppppppppsd/article/details/129282590
Recomendado
Clasificación