使用JavaScript学习设计模式(3)| 小册免费学

289 阅读4分钟

系列文章

行为型

迭代器模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露对象的对象的内部表示。

迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,及时不关心对象的内部构造,也可以按照顺序访问其中的每个元素。

简单类说,它的目的就是去遍历一个可遍历的对象。

像 JS 中原生的 forEach、map 等方法都属于是迭代器模式的一种实现,一般来说不用自己去实现迭代器。

在 JS 中有一种类数组的存在,他们没有迭代方法,比如 nodeList、arguments 并不能直接使用迭代方法,需要使用 jQuery 的 each 方法或者将类数组装换为真正的数组在进行迭代。

而在最新的 ES6 中,对有只要有 Iterator 接口的数据类型都可以使用 for..of..进行遍历,而他的底层则是对 next 方法的反复调用,具体参考阮一峰-Iterator 和 for...of 循环

例子

我们可以借助 Iterator 接口自己实现一个迭代器。

class Creater {
  constructor(list) {
    this.list = list;
  }
  // 创建一个迭代器,也叫遍历器
  createIterator() {
    return new Iterator(this);
  }
}
class Iterator {
  constructor(creater) {
    this.list = creater.list;
    this.index = 0;
  }
  // 判断是否遍历完数据
  isDone() {
    if (this.index >= this.list.length) {
      return true;
    }
    return false;
  }
  next() {
    return this.list[this.index++];
  }
}

var arr = [1, 2, 3, 4];
var creater = new Creater(arr);
var iterator = creater.createIterator();
console.log(iterator.list); // [1, 2, 3, 4]
while (!iterator.isDone()) {
  console.log(iterator.next());
  // 1
  // 2
  // 3
  // 4
}

小结

  1. JavaScript 中的有序数据集合有 Array,Map,Set,String,typeArray,arguments,NodeList,不包括 Object
  2. 任何部署了[Symbol.iterator]接口的数据都可以使用 for...of 循环遍历
  3. 迭代器模式使目标对象和迭代器对象分离,符合开放封闭原则

订阅/发布模式(观察者)

发布/订阅模式又叫观察者模式,她定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖他的对象都将得到通知。在 JavaScrtipt 中,我们一般使用时间模型来替代传统的发布/订阅模式。

比如:Vue 中的双向绑定和事件机制。

发布/订阅模式和观察者模式的区别

  • 发布者可以直接处接到订阅的操作,叫观察者模式

  • 发布者不直接触及到订阅者,而是由统一的第三方完成通信操作,叫发布/订阅模式

    发布订阅模式和观察者模式.png

例子

可以自己实现一个事件总线,模拟$emit$on

class EventBus {
  constructor() {
    this.callbacks = {};
  }
  $on(name, fn) {
    (this.callbacks[name] || (this.callbacks[name] = [])).push(fn);
  }
  $emit(name, args) {
    let cbs = this.callbacks[name];
    if (cbs) {
      cbs.forEach((c) => {
        c.call(this, args);
      });
    }
  }
  $off(name) {
    this.callbacks[name] = null;
  }
}
let event = new EventBus();
event.$on("event1", (arg) => {
  console.log("event1", arg);
});

event.$on("event2", (arg) => {
  console.log("event2", arg);
});

event.$emit("event1", 1); // event1 1
event.$emit("event2", 2); // event2 2

策略模式

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

策略模式的目的就是将算法的使用和算法的实现分离开来。

一个策略模式通常由两部分组成:

  • 一组可变的策略类:封装了具体的算法,负责具体的计算过程
  • 一组不变的环境类:接收到请求后,随后将请求委托到某个策略类

说明环境类要维持对某个策略对象的引用。

例子

通过绩效等级计算奖金,可以轻易的写出如下的代码:

var calculateBonus = function(performanceLevel, salary) {
  if (performanceLevel === "S") {
    return salary * 4;
  }
  if (performanceLevel === "A") {
    return salary * 3;
  }
  if (performanceLevel === "B") {
    return salary * 2;
  }
};

calculateBonus("B", 20000); // 输出:40000
calculateBonus("S", 6000); // 输出:24000

使用策略模式修改代码:

var strategies = {
  S: (salary) => {
    return salary * 4;
  },
  A: (salary) => {
    return salary * 3;
  },
  B: (salary) => {
    return salary * 2;
  },
};
var calculateBonus = function(level, salary) {
  return strategies[level](salary);
};
console.log(calculateBonus("S", 200)); // 输出:800
console.log(calculateBonus("A", 200)); // 输出:600

状态模式

状态模式允许一个对象在其内部状态改变的时候改变

状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

例子

实现一个交通灯的切换。

点击查看Demo:交通信号灯-在线例子

这时候如果在加一个蓝光的话,可以直接添加一个蓝光的类,然后添加 parssBtn 方法,其他状态都不需要变化。

小结

  • 通过定义不同的状态类,根据状态的改变而改变状态的行为,不必把大量的逻辑都写在被操作对象的类中,而且容易增加新的状态。
  • 符合开放封闭原则

解释器模式

**解释器模式(Interpreter):**给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

用到的比较少,可以参考两篇文章来理解。

小结

  • 描述语言语法如何定义,如何解释和编译
  • 用于专业场景

中介者模式

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。

这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护

通过一个中介者对象,其他所有相关对象都通过该对象来通信,而不是相互引用,但其中一个对象发生改变时,只需要通知中介者对象即可。

通过中介者模式可以解除对象与对象之前的耦合关系。

例如:Vuex

middle-parttern.png

参考链接:JavaScript 中介者模式

小结

  • 将各关联对象通过中介者隔离
  • 符合开放封闭原则
  • 减少耦合

访问者模式

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。

通过这种方式,元素的执行算法可以随着访问者改变而改变。

例子

通过访问者调用元素类的方法。

// 访问者
function Visitor() {
  this.visit = function(concreteElement) {
    concreteElement.doSomething(); // 谁访问,就使用谁的doSomething()
  };
}
// 元素类
function ConceteElement() {
  this.doSomething = function() {
    console.log("这是一个具体元素");
  };
  this.accept = function(visitor) {
    visitor.visit(this);
  };
}
// Client
var ele = new ConceteElement();
var v = new Visitor();
ele.accept(v); // 这是一个具体元素

小结

  • 假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
  • 假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。

备忘录模式

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象

例子

实现一个带有保存记录功能的”编辑器“,功能包括

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销功能)
// 状态备忘
class Memento {
  constructor(content) {
    this.content = content;
  }
  getContent() {
    return this.content;
  }
}

// 备忘列表
class CareTaker {
  constructor() {
    this.list = [];
  }
  add(memento) {
    this.list.push(memento);
  }
  get(index) {
    return this.list[index];
  }
}

// 编辑器
class Editor {
  constructor() {
    this.content = null;
  }
  setContent(content) {
    this.content = content;
  }
  getContent() {
    return this.content;
  }
  saveContentToMemento() {
    return new Memento(this.content);
  }
  getContentFromMemento(memento) {
    this.content = memento.getContent();
  }
}

// 测试代码
let editor = new Editor();
let careTaker = new CareTaker();

editor.setContent("111");
editor.setContent("222");
careTaker.add(editor.saveContentToMemento()); // 存储备忘录
editor.setContent("333");
careTaker.add(editor.saveContentToMemento()); // 存储备忘录
editor.setContent("444");

console.log(editor.getContent()); // 444
editor.getContentFromMemento(careTaker.get(1)); // 撤销
console.log(editor.getContent()); // 333
editor.getContentFromMemento(careTaker.get(0)); // 撤销
console.log(editor.getContent()); // 222

小结

  • 状态对象与使用者分开(解耦)
  • 符合开放封闭原则

模板方法模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。

它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

感觉用到的不是很多,想了解的可以点击下面的参考链接。

参考:JavaScript 设计模式之模板方法模式

职责链模式

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。

这种模式给予请求的类型,对请求的发送者和接收者进行解耦。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。

如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

例子

公司的报销审批流程:组长=》项目经理=》财务总监

// 请假审批,需要组长审批、经理审批、最后总监审批
class Action {
  constructor(name) {
    this.name = name;
    this.nextAction = null;
  }
  setNextAction(action) {
    this.nextAction = action;
  }
  handle() {
    console.log(`${this.name} 审批`);
    if (this.nextAction != null) {
      this.nextAction.handle();
    }
  }
}

let a1 = new Action("组长");
let a2 = new Action("项目经理");
let a3 = new Action("财务总监");
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();
// 组长 审批
// 项目经理 审批
// 财务总监 审批

// 将一步操作分为多个职责来完成,一个接一个的执行,最终完成操作。

小结

  • 可以联想到 jQuery、Promise 这种链式操作
  • 发起者和处理者进行隔离
  • 符合开发封闭原则

命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。

请求以命令的形式包裹在对象中,并传给调用对象。

调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

例子

实现一个编辑器,有很多命令,比如:写入、读取等等。

class Editor {
  constructor() {
    this.content = "";
    this.operator = [];
  }
  write(content) {
    this.content += content;
  }
  read() {
    console.log(this.content);
  }
  space() {
    this.content += " ";
  }
  readOperator() {
    console.log(this.operator);
  }
  run(...args) {
    this.operator.push(args[0]);
    this[args[0]].apply(this, args.slice(1));
    return this;
  }
}

const editor = new Editor();

editor
  .run("write", "hello")
  .run("space")
  .run("write", "zkk!")
  .run("read"); // => 'hello zkk!'

// 输出操作队列
editor.readOperator(); // ["write", "space", "write", "read"]

小结

  • 降低耦合
  • 新的命令可以很容易的添加到系统中

来自九旬的原创:博客原文链接

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情