State 模式是一种特殊形式的 Strategy 模式:Context 选择的具体策略根据不同的 state 发生变化。
对于 Strategy 模式,可以基于不同的变量比如传入的参数来决定选择具体哪个策略,一旦选择确定后,直到 context 剩余的整个生命周期结束,该策略都保持不变。相反在 State 模式中,策略(或者在这里的语境下,叫做状态)在 context 的生命周期里是动态变化的,从而允许对象的行为可以根据内部状态的变化自适应地更改。
举例来说,我们需要创建一个宾馆预定系统,由一个 Reservation 类对预定房间的行为进行建模。
考虑如下一系列事件:
- 当 reservation 对象初次创建后,其处于未确认状态。用户可以通过一个
confirm()
方法对此次预定进行确认。但不能通过cancel()
方法取消预订,因为此次预定还并没有被确认。可以使用delete()
方法删除这条记录 - 一旦该 reservation 被确认,订单处于已确认状态。
confirm()
方法将不能再次被调用(不能重复确认);但该 reservation 支持通过cancel()
方法进行取消,同时该记录无法被删除(已经有人确认预定) - 在 reservation 日期的前一天,订单处于已生效状态。上述所有 3 个方法都不再支持,用户只能办理入住
参考上图,可以实现 3 种 不同的策略,他们都实现了 confirm()
、cancel()
、delete()
这几个方法。每种策略的具体逻辑由不同的状态决定。Reservation 对象只需要在每次状态切换时,激活对应的策略。
实例:failsafe socket
1 | mkdir state && cd state |
package.json
:1
2
3
4
5
6{
"type": "module",
"dependencies": {
"json-over-tcp-2": "^0.3.5"
}
}
failsafeSocket.js
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import {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 离线时发送的数据,还创建了offline
和online
两种不同的状态,分别对应离线时和在线时 socket 的不同行为 changeState()
方法负责不同状态的切换。它会更新当前状态currentState
并调用该状态的activate()
方法send()
方法包含FailsafeSocket
类的主要功能,它会基于在线或离线状态触发不同的行为。这里它将具体的操作指派给了当前激活的状态对象去实现
offlineState.js
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import 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
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31export 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
:1
2
3
4
5
6
7
8
9
10import 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
:1
2
3
4
5
6import {FailsafeSocket} from './failsafeSocket.js'
const failsafeSocket = new FailsafeSocket({port: 5000})
setInterval(() => {
failsafeSocket.send(process.memoryUsage())
}, 1000)