Node.js 设计模式笔记 —— State 模式

State 模式是一种特殊形式的 Strategy 模式:Context 选择的具体策略根据不同的 state 发生变化。
对于 Strategy 模式,可以基于不同的变量比如传入的参数来决定选择具体哪个策略,一旦选择确定后,直到 context 剩余的整个生命周期结束,该策略都保持不变。相反在 State 模式中,策略(或者在这里的语境下,叫做状态)在 context 的生命周期里是动态变化的,从而允许对象的行为可以根据内部状态的变化自适应地更改。

State pattern

举例来说,我们需要创建一个宾馆预定系统,由一个 Reservation 类对预定房间的行为进行建模。

考虑如下一系列事件:

  • 当 reservation 对象初次创建后,其处于未确认状态。用户可以通过一个 confirm() 方法对此次预定进行确认。但不能通过 cancel() 方法取消预订,因为此次预定还并没有被确认。可以使用 delete() 方法删除这条记录
  • 一旦该 reservation 被确认,订单处于已确认状态。confirm() 方法将不能再次被调用(不能重复确认);但该 reservation 支持通过 cancel() 方法进行取消,同时该记录无法被删除(已经有人确认预定)
  • 在 reservation 日期的前一天,订单处于已生效状态。上述所有 3 个方法都不再支持,用户只能办理入住
State pattern

参考上图,可以实现 3 种 不同的策略,他们都实现了 confirm()cancel()delete() 这几个方法。每种策略的具体逻辑由不同的状态决定。Reservation 对象只需要在每次状态切换时,激活对应的策略。

实例:failsafe socket

mkdir state && cd state
npm install json-over-tcp-2

package.json

{
  "type": "module",
  "dependencies": {
    "json-over-tcp-2": "^0.3.5"
  }
}

failsafeSocket.js

import {OfflineState} from './offlineState.js'
import {OnlineState} from './onlineState.js'

export class FailsafeSocket {
  constructor(options) {
    this.options = options
    this.queue = []
    this.currentState = null
    this.socket = null
    this.states = {
      offline: new OfflineState(this),
      online: new OnlineState(this)
    }
    this.changeState('offline')
  }

  changeState(state) {
    console.log(`Activating state: ${state}`)
    this.currentState = this.states[state]
    this.currentState.activate()
  }

  send(data) {
    this.currentState.send(data)
  }
}

上述 FailsafeSocket 类主要由以下几个组件构成:

  • 构造函数 constructor 会初始化一个 queue 队列来存储 socket 离线时发送的数据,还创建了 offlineonline 两种不同的状态,分别对应离线时和在线时 socket 的不同行为
  • changeState() 方法负责不同状态的切换。它会更新当前状态 currentState 并调用该状态的 activate() 方法
  • send() 方法包含 FailsafeSocket 类的主要功能,它会基于在线或离线状态触发不同的行为。这里它将具体的操作指派给了当前激活的状态对象去实现

offlineState.js

import jsonOverTcp from 'json-over-tcp-2'

export class OfflineState {
  constructor(failsafeSocket) {
    this.failsafeSocket = failsafeSocket
  }

  send(data) {
    this.failsafeSocket.queue.push(data)
  }

  activate() {
    const retry = () => {
      setTimeout(() => this.activate(), 1000)
    }

    console.log('Trying to connect...')
    this.failsafeSocket.socket = jsonOverTcp.connect(
      this.failsafeSocket.options,
      () => {
        console.log('Connection established')
        this.failsafeSocket.socket.removeListener('error', retry)
        this.failsafeSocket.changeState('online')
      }
    )
    this.failsafeSocket.socket.once('error', retry)
  }
}

上述模块负责定义 socket 处于离线状态时的行为。

  • send() 方法只负责将接受到的数据存储到队列中,因为此时是离线状态,队列中的数据会在 socket 在线时取出并发送
  • activate() 方法会尝试建立连接,连接失败则隔一秒重试。成功建立连接后,failsafeSocket 的状态变为在线状态,触发在线状态的 activate() 方法

onlineState.js

export class OnlineState {
  constructor(failsafeSocket) {
    this.failsafeSocket = failsafeSocket
    this.hasDisconnected = false
  }

  send(data) {
    this.failsafeSocket.queue.push(data)
    this._safeWrite(data)
  }

  _safeWrite(data) {
    this.failsafeSocket.socket.write(data, (err) => {
      if (!this.hasDisconnected && !err) {
        this.failsafeSocket.queue.pop()
      }
    })
  }

  activate() {
    this.hasDisconnected = false
    for (const data of this.failsafeSocket.queue) {
      this._safeWrite(data)
    }

    this.failsafeSocket.socket.once('error', () => {
      this.hasDisconnected = true
      this.failsafeSocket.changeState('offline')
    })
  }
}

OnlineState 模块实现了当 socket 处于在线状态时的行为。

  • send() 方法会将数据放入队列,并立即尝试将其写入到 socket,因为此时是在线状态。若该数据成功写入,则将其从队列中移除
  • activate() 方法会在连接成功建立时触发,会尝试发送在 socket 离线时排队的所有数据并监听任意错误事件。若有错误发生,转换到离线状态,触发离线状态的 activate 方法,继续尝试建立连接

server.js

import jsonOverTcp from 'json-over-tcp-2'

const server = jsonOverTcp.createServer({port: 5000})
server.on('connection', socket => {
  socket.on('data', data => {
    console.log('Client data', data)
  })
})

server.listen(5000, () => console.log('Server started'))

client.js

import {FailsafeSocket} from './failsafeSocket.js'

const failsafeSocket = new FailsafeSocket({port: 5000})
setInterval(() => {
  failsafeSocket.send(process.memoryUsage())
}, 1000)

参考资料

Node.js Design Patterns: Design and implement production-grade Node.js applications using proven patterns and techniques, 3rd Edition

你可能感兴趣的:(Node.js 设计模式笔记 —— State 模式)