1

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

 2 years ago
source link: https://rollingstarky.github.io/2022/07/18/node-js-design-patterns-state-pattern/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2022-07-18

| Program

| 0

|

4.6k

|

0:05

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK