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 生态系统细节的语言。

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

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

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax