设计模式

常用设计模式

  • 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  • 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。

OOP六大设计原则

单一职责原则、开闭原则、里氏替换原则、依赖倒置原则、接口隔离、迪米勒原则

单例模式

使某个方法在一次或多次被调用的情况下,其产生的实例仍只有一个,复用先前的资源来节省运算。

// 全局提示组件
const showMsg= (() => {
  let div;
  return msg => {
    // 仅在第一次调用时创建这个div容器,之后的调用都复用此div,而非每次调用都创建一个新的
    if (!div) {
      div = document.createElement('div');
      document.body.appendChild(div);
    }
    div.innerText = msg;
  }
})();

showMsg('hello'); // 创建一个div
showMsg('world'); // 复用上面的div

观察者模式

当对象间需要一对一或者一对多通信时,在接收方和发送方都引入观察者,通过观察者来传递信息,而非直接调用对方的方法,降低耦合。

class Observer {
  constructor() {
    this.dep = new Set();
  }
  emit(msg) {
    for (let callback of this.dep) {
      callback(msg);
    }
  }
  subscribe(callback) {
    this.dep.add(callback);
    return () => this.dep.delete(callback);
  }
}

class House {
  constructor() {
    this.event = new Observer();
  }
}

class Person {
  constructor(name) {
    this.name = name;
  }
  watch(observer) {
    observer.subscribe(msg => {
      console.log(this.name + ' see ' + msg);
    });
  }
}

const house = new House();
const person1 = new Person('Alice');
const person2 = new Person('Bob');

// 让Alice和Bob关注house发生的事件
person1.watch(house.event);
person2.watch(house.event);

// house发生着火事件
house.event.emit('fire!!!');

// Alice和Bob接收到着火事件
// 'Alice see fire!!!'
// 'Bob see fire!!!'

策略模式

有一系列相似的运算,需要根据条件来采取不同的运算时,可以使用策略模式,来替代大量平铺的if/else,符合开闭原则,也易于拓展。

interface Animal {
  yell(): string;
}

class Yeller {
  private animal: Animal;
  constructor() {
    this.animal = new DefaultAnimal();
  }
  setAnimal(animal: Animal) {
    this.animal = animal;
  }
  // 对外提供统一的run接口,根据不同animal显示不同叫声,只要实现了yell方法的类都可以作为animal
  run() {
    console.log(this.animal.yell());
  }
}

class DefaultAnimal implements Animal {
  yell() {
    return 'default';
  }
}

class Cat implements Animal {
  yell() {
    return 'meow';
  }
}

class Dog implements Animal {
  yell() {
    return 'bark';
  }
}

const yeller = new Yeller(); // 以默认Animal初始化,Animal可以理解为策略
yeller.run(); // 默认策略下显示default

yeller.setAnimal(new Cat()); // 设置策略为Cat
yeller.run(); // cat策略下显示meow

yeller.setAnimal(new Dog()); // 设置策略为Dog
yeller.run(); // dog策略下显示bark

工厂模式

使用统一方法来创建实例,而非直接使用new创建,不暴露创建逻辑的细节。

class Animal {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHi(): void {
    console.log(`this is ${this.name}!`);
  }
}

// 对外提供方法来创建实例,而不直接提供构造函数
function createAnimal(name: string): Animal {
  switch (name) {
    case 'cat':
      return new Animal('cat');
    case 'dog':
      return new Animal('dog');
    default:
      throw new Error('unsupported name!');
  }
}

const cat = createAnimal('cat');
cat.sayHi(); // this is cat! 

const dog = createAnimal('dog');
dog.sayHi(); // this is dog!

装饰器模式

所谓装饰,即在不改变原有逻辑代码的情况下,对其添加新的功能。

class Task {
  public id: number;
  constructor(id: number) {
    this.id = id;
  }

  run(): void {
    console.log(`task ${this.id } run!`);
  }
}

// 在不改变原有function代码的前提下,加入新的逻辑,即对其"装饰"
function logger(fn: Function): Function {
  return () => {
    console.log(`func started at ${Date.now()}`);
    fn();
    console.log(`func finished at ${Date.now()}`);
  }
}

const task = new Task(1);
const taskRunWithLog = logger(() => task.run());

taskRunWithLog();

// 执行结果:
// func started at xxx
// task 1 run!
// func finished at xxx

当然,基于ES最新的proposal-decorators提案,我们可以有更优雅的写法。

class Task {
  public id: number;
  constructor(id: number) {
    this.id = id;
  }

  @logger
  run(): void {
    console.log(`task ${this.id } run!`);
  }
}

function logger(target: Object, name: string, descriptor: PropertyDescriptor) {
  const originFn = target[name];

  descriptor.value = function(...args) {
    console.log(`func started at ${Date.now()}`);
    originFn.call(this, ...args);
    console.log(`func finished at ${Date.now()}`);
  }

  return descriptor;
}

const task = new Task(1);
task.run();

// 执行结果和上个例子一致:
// func started at xxx
// task 1 run!
// func finished at xxx

迭代器模式

对目标数据生成迭代器,只需使用迭代器的方法就可以对其进行遍历,而不需要知道目标数据的存储格式和遍历方法。

interface Iter {
  next(): any;
  done(): boolean;
}

class MyIter implements Iter {
  private arr: any[];
  private index: number;
  private hasNext: boolean;

  constructor(arr: any[]) {
    this.arr = arr;
    this.reset();
  }
  next() {
    const cur = this.index;

    this.index = cur + 1;
    this.hasNext = this.index < this.arr.length;

    return this.arr[cur];
  }
  done() {
    return !this.hasNext;
  }
  reset(): void {
    this.index = 0;
    this.hasNext = !!this.arr.length;
  }
}

const iter = new MyIter([1, 2, 3]);

// 只需要调用迭代器的next和done方法即可完成遍历
while (!iter.done()) {
  console.log(iter.next());
}

// 执行结果:
// 1
// 2
// 3

责任链模式

事件发送给责任链,再由责任链将事件分发给处理器,将事件的发送者与接收者解耦。

class Chain {
  private chain: Handler[];
  constructor(chain?: Handler[]) {
    this.chain = chain ? chain : [];
  }
  addHandler(handler: Handler) {
    this.chain.push(handler);
  }
  handle(event: MyEvent) {
    let handled = false;

    // 该事件依次传递给注册的处理函数,直到被某个处理函数处理
    for (let i = 0; i < this.chain.length; i++) {
      handled = this.chain[i](event);

      if (handled) {
        break;
      }
    }
  }
}

interface MyEvent {
  type: string;
  message: string;
}

interface Handler {
  (event: MyEvent): boolean;
}

const handlerA: Handler = (event: MyEvent): boolean => {
  if (event.type !== 'A') {
    return false;
  }

  console.log(`handle event [A]: ${event.message}`);
  
  return true;
}

const handlerB: Handler = (event: MyEvent): boolean => {
  if (event.type !== 'B') {
    return false;
  }

  console.log(`handle event [B]: ${event.message}`);
  
  return true;
}

const handlerDefault: Handler = (event: MyEvent): boolean => {
  console.log(`handle event [Default] ${event.message}`);
  
  return true;
}

// 初始化责任链,并注册处理函数
const chain = new Chain();

chain.addHandler(handlerA);
chain.addHandler(handlerB);
chain.addHandler(handlerDefault);

// 给责任链发送事件
chain.handle({
  type: 'A',
  message: 'message from A',
});

chain.handle({
  type: 'B',
  message: 'message from B',
});

chain.handle({
  type: 'C',
  message: 'message from C',
});

// 依次输出:
// handle event [A]: message from A
// handle event [B]: message from B
// handle event [Default] message from C