JS 设计模式速成指南(下)

大家好,这里是大家的林语冰。欢迎关注"前端俱乐部"~

地球人都知道,JS 是一门混合范型语言,所以 JS 也支持面向对象编程,其中设计模式则号称后 ES6 时代 JS 或 TS 面向对象编程的最佳实践。

"设计模式 "源于 GOF(四人帮)合著出版的一部经典作品《设计模式:可复用的面向对象软件元素》,该书第一次完整科普了软件开发中设计模式的概念。

设计模式一般分为三大类:

  • 创建型模式
  • 结构型模式
  • 行为型模式

《JS 设计模式速成指南(上)》 中,我们已经科普了前面两大类的设计模式。本期下篇我们将继续补充设计模式最后的一大类别 ------ 行为型模式。

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。

英文原味版请传送 Quick Reference Guide to Design Patterns in JS

职责链模式

职责链模式(chain of responsibility)允许沿着有机会处理该请求的对象链传递请求。

收到请求后,每个处理程序决定就地处理该请求,或者将请求传递给职责链中的下一个处理程序。

举个栗子,我们链接 Discount 折扣类,来处理购物车折扣金额的请求。

js 复制代码
// 购物车类
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 }

当存在多个对象可以处理同一请求,且该信息在运行时已知时,请使用职责链模式。

命令模式

命令模式(command)允许将请求封装为对象。

通过此转换,我们可以将请求作为方法参数传递、延迟或排队请求的执行,并支持可撤消的操作。

举个栗子,我们将开/关指令封装为对象,并将它们作为参数传递到 Car 构造函数中。

js 复制代码
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 }

当我们有一个需要处理的请求队列,或者我们想要撤消时,请使用命令模式。

解释器模式

解释器模式(interpreter)允许在问题频发时,为简单语言创建语法;可以考虑用简单语言将其表示为一个句子,这样解释器通过解释该句子来解决问题。

举个栗子,我们创建一个简单的数字来对几个数字进行数学运算。

js 复制代码
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 }

当我们想要解释给定语言,且可以将语句表示为抽象语法树时,请使用解释器模式。

迭代器模式

迭代器模式(iterator)允许读写集合中的元素,而不暴露其底层表示。

举个栗子,我们创建一个简单的迭代器,它将有一个填充了元素的数组,并使用 next()hasNext() 方法,我们可以迭代所有元素。

js 复制代码
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

当我们想要读写对象的内容集合,而不知道其内部表示方式时,请使用迭代器偶模式。

中介者模式

中介者模式(mediator)通过定义封装一组对象如何交互的对象,减少对象间的混乱依赖。

举个栗子,我们创建一个 TrafficTower 中介者类,它允许我们使用 Airplane 实例的所有位置。

js 复制代码
// 交通信号塔类
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 }

当一组对象之间以复杂的方式通信时,请使用中介者模式。

备忘录模式

备忘录模式(memento)允许捕获并具体化对象的内部状态,这样稍后可以将对象恢复到该状态。

举个栗子,我们创建一种简单方法来存值,并按需恢复快照。

js 复制代码
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 }

当我们想要生成对象状态的快照,以便能够恢复对象的先前状态时,请使用备忘录模式。

观察者模式

观察者模式(observer)允许定义对象间的一对多的依赖关系,这样当一个对象更改状态时,其所有依赖都会得到通知,并自动更新。

举个栗子,我们正在创建一个简单的产品类,其他类可以观察 register() 方法中的注册相关的变更,并且当某些东东更新时,notifyAll() 方法会向所有观察者通信这些更改。

js 复制代码
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 }

当更改一个对象的状态可能需要更改其他对象,并且实际的一组对象事先未知或动态更改时,请使用观察者模式。

状态模式

状态模式(state)允许对象在其内部状态发生变化时,改变其行为。

举个栗子,我们使用 Order 类创建一个简单的状态模式,该类会使用 next() 方法更新其状态。

js 复制代码
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

当对象的行为取决于其状态,并且其在运行时的行为变化取决于该状态时,请使用状态模式。

策略模式

策略模式(strategy)允许定义一系列算法,封装每个算法,并使它们可以互换。

举个栗子,我们有一组可用于 ShoppingCart 购物车的折扣。

这里的技巧是,我们可以传递要用于构造函数的函数,并以这种方式更改折扣金额。

js 复制代码
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 }

当我们有一大坨类似的类,它们只是在执行某些行为的方式上有所不同时,请使用策略模式。

模板模式

模板模式(template)允许在超类中定义算法的骨架,但允许子类覆盖算法的特定步骤,而不改变其结构。

举个栗子,我们创建一个简单的模板方法来计算税收,并在增值税和商品及服务税(税收类型)中扩展此模板。这样我们就可以在若干税收类别中,复用相同的结构。

js 复制代码
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 }

当我们想让客户端只扩展算法的特定步骤,而不是整个算法或其结构时,请使用模板模式。

访问者模式

访问者模式(visitor)允许将算法与其操作的对象分开。

举个栗子,我们创建一个结构体,来计算两种员工的奖金,这样我们就可以将奖金方法扩展到越来越多类型的员工,比如 CEO 奖金、VP奖金等。

js 复制代码
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 }

当对象结构包含一大坨类,并且我们想要操作该结构中依赖于其类的元素时,请使用访问者模式。

高潮总结

行为型模式(behavioral)是一种设计模式类别,适用于通信和对象情况间的分配的相关常见问题。

我们已经亲眼见证设计模式如何为常见问题提供优雅的解决方案,使得代码不仅更易维护,而且更易理解和扩展。

设计模式的优雅之处在于其通用性。无论是构建简单的 Web App,还是复杂的企业系统,设计模式都提供了一种超越 JS 生态系统细节的语言。

本期话题是 ------ 你在实际开发中最常使用的是哪种设计模式?欢迎在本文下方自由言论,文明共享。

长期关注"前端俱乐部",坚持阅读,自律打卡,每天一次,进步一点。谢谢大家的点赞,掰掰~

相关推荐
小马哥编程1 小时前
Function.prototype和Object.prototype 的区别
javascript
小白学前端6661 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203981 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
王小王和他的小伙伴1 小时前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
学前端的小朱1 小时前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿1 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
zh路西法1 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式
好名字08212 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
夏旭泽2 小时前
设计模式-备忘录模式
设计模式·备忘录模式
摇光932 小时前
js高阶-async与事件循环
开发语言·javascript·事件循环·宏任务·微任务