关于怎样把对象组合起来,并规定出这些对象之间的交流方式,让组合而成的这种结构,变得易于扩展、易于形成模块、易于复用且易于适配。

  • Strategy(策略)模式:修改组件的某一部分,让该组件能够适应特定的需求
  • State(状态)模式:根据组件的行为修改它的状态
  • Template(模板)模式:复用某组件的结构,以定义新的组件
  • Iterator(迭代器)模式:定义一套通用的接口,使得开发者可以通过这套接口迭代某个集合
  • Middleware(中间件)模式:将一系列处理步骤定义到一个模块里面
  • Command(命令)模式:把执行某例程所需的信息表示出来,让我们能够轻松地传输、存储并处理这种信息

    Strategy(策略)模式

    Strategy 模式把某对象(context)的逻辑里面可能发生变化的(variable)部分单独抽出来,让这一部分可以用各种策略(strategy)来实现,从而令该对象表现出不同的形式。 context 会实现这一系列算法所共用的逻辑,并把这些算法有可能出现分歧的地方预留出来,根据某个可变的因素(例如某个输入值、某个系统配置或用户选项)来运用相应的 strategy(策略),以调整自己的行为。

image.png
这个模式中的这些 strategy 指的通常是一系列解决方案里面的某几种方案,这些方案都实现同一套接口,也就是 context 所要求的那套接口。

处理各种格式的配置信息

  1. import { promises as fs } from 'fs'
  2. import objectPath from 'object-path'
  3. export class Config {
  4. constructor (formatStrategy) {
  5. this.data = {}
  6. this.formatStrategy = formatStrategy
  7. }
  8. get (configPath) {
  9. return objectPath.get(this.data, configPath)
  10. }
  11. set (configPath, value) {
  12. return objectPath.set(this.data, configPath, value)
  13. }
  14. async load (filePath) {
  15. console.log(`Deserializing from ${filePath}`)
  16. this.data = this.formatStrategy.deserialize(
  17. await fs.readFile(filePath, 'utf-8')
  18. )
  19. }
  20. async save (filePath) {
  21. console.log(`Serializing to ${filePath}`)
  22. await fs.writeFile(filePath,
  23. this.formatStrategy.serialize(this.data))
  24. }
  25. }
  1. import ini from 'ini'
  2. export const iniStrategy = {
  3. deserialize: data => ini.parse(data),
  4. serialize: data => ini.stringify(data)
  5. }
  6. export const jsonStrategy = {
  7. deserialize: data => JSON.parse(data),
  8. serialize: data => JSON.stringify(data, null, ' ')
  9. }
  1. import { Config } from './config.js'
  2. import { jsonStrategy, iniStrategy } from './strategies.js'
  3. async function main () {
  4. const iniConfig = new Config(iniStrategy)
  5. await iniConfig.load('samples/conf.ini')
  6. iniConfig.set('book.nodejs', 'design patterns')
  7. await iniConfig.save('samples/conf_mod.ini')
  8. const jsonConfig = new Config(jsonStrategy)
  9. await jsonConfig.load('samples/conf.json')
  10. jsonConfig.set('book.nodejs', 'design patterns')
  11. await jsonConfig.save('samples/conf_mod.json')
  12. }
  13. main()

以上程序是让用户在构造 Config 实例时自行选择相应的策略对象。
还可以使用以下手段来实现策略对象的切换功能:

  • 创建两套策略体系,以分别表示整个流程之中可以变化的那两个地方:其中一套针对反序列化操作,另一套针对序列化操作。
  • 动态地选择策略:让 Config 对象根据文件的扩展名来选择合适的策略。可以构建一张映射表(map 结构),把每一种扩展名(extension)所对应的策略(strategy)保存进去,让 Config 对象从这张表中查出自己应该使用哪种策略

    State(状态)模式

    State 模式是 Strategy 模式的特例或特化(specialization),它会根据 Context 的状态(state)来改变自己的策略(strategy)

    状态迁移(state transition)可以由 context 对象发起并控制,也可以由用户所写的代码或者由 state 对象自己来做。后一种方法最灵活,而且耦合度低,因为 context 不需要知道总共有多少种状态,也不用担心什么时候应该从其中一种状态切换到另外一种。

实现一种能够处理常见错误的 socket

  1. import { OfflineState } from './offlineState.js'
  2. import { OnlineState } from './onlineState.js'
  3. export class FailsafeSocket {
  4. constructor (options) {
  5. this.options = options
  6. this.queue = []
  7. this.currentState = null
  8. this.socket = null
  9. this.states = {
  10. offline: new OfflineState(this), // socket 在服务器下线时的行为
  11. online: new OnlineState(this) // socket 在服务器上线时的行为
  12. }
  13. this.changeState('offline')
  14. }
  15. // 负责让 socket 从一种状态迁移到另一种状态
  16. changeState (state) {
  17. console.log(`Activating state: ${state}`)
  18. this.currentState = this.states[state]
  19. this.currentState.activate()
  20. }
  21. send (data) {
  22. this.currentState.send(data)
  23. }
  24. }
  1. import jsonOverTcp from 'json-over-tcp-2' // 解析流经 socket 的所有数据并调整为 json 格式
  2. export class OfflineState {
  3. constructor (failsafeSocket) {
  4. this.failsafeSocket = failsafeSocket
  5. }
  6. send (data) {
  7. this.failsafeSocket.queue.push(data)
  8. }
  9. activate () {
  10. const retry = () => {
  11. setTimeout(() => this.activate(), 1000)
  12. }
  13. console.log('Trying to connect...')
  14. this.failsafeSocket.socket = jsonOverTcp.connect(
  15. this.failsafeSocket.options,
  16. () => {
  17. console.log('Connection established')
  18. this.failsafeSocket.socket.removeListener('error', retry)
  19. // 与服务器建立连接后状态迁移到 online 状态
  20. this.failsafeSocket.changeState('online')
  21. }
  22. )
  23. this.failsafeSocket.socket.once('error', retry)
  24. }
  25. }
  1. export class OnlineState {
  2. constructor (failsafeSocket) {
  3. this.failsafeSocket = failsafeSocket
  4. this.hasDisconnected = false
  5. }
  6. send (data) {
  7. this.failsafeSocket.queue.push(data)
  8. this._safeWrite(data)
  9. }
  10. _safeWrite (data) {
  11. this.failsafeSocket.socket.write(data, (err) => {
  12. if (!this.hasDisconnected && !err) {
  13. this.failsafeSocket.queue.shift()
  14. }
  15. })
  16. }
  17. activate () {
  18. this.hasDisconnected = false
  19. for (const data of this.failsafeSocket.queue) {
  20. this._safeWrite(data)
  21. }
  22. // 发生这样的事件说明服务器下线了
  23. this.failsafeSocket.socket.once('error', () => {
  24. this.hasDisconnected = true
  25. this.failsafeSocket.changeState('offline')
  26. })
  27. }
  28. }
  1. import jsonOverTcp from 'json-over-tcp-2'
  2. const server = jsonOverTcp.createServer({ port: 5000 })
  3. server.on('connection', socket => {
  4. socket.on('data', data => {
  5. console.log('Client data', data)
  6. })
  7. })
  8. server.listen(5000, () => console.log('Server started'))
  1. import { FailsafeSocket } from './failsafeSocket.js'
  2. const failsafeSocket = new FailsafeSocket({ port: 5000 })
  3. setInterval(() => {
  4. // send current memory usage
  5. failsafeSocket.send(process.memoryUsage())
  6. }, 1000)

Template(模板)模式

Template 模式定义一个抽象的类,用来实现某个组件的骨架(skeleton,即通用的部分),并在其中留下一些还没有实现的步骤,让子类去实现。这样的步骤叫做 template method(模板方法),子类通过实现模板方法,来填补抽象类在逻辑上的缺失,让整个组件的功能完整。这个模式想帮助我们针对某组件定义一系列的类,以展示该组件的各种形式。
image.png

用 template 模式重新实现配置管理器

  1. import { promises as fsPromises } from 'fs'
  2. import objectPath from 'object-path'
  3. export class ConfigTemplate {
  4. async load (file) {
  5. console.log(`Deserializing from ${file}`)
  6. this.data = this._deserialize(
  7. await fsPromises.readFile(file, 'utf-8'))
  8. }
  9. async save (file) {
  10. console.log(`Serializing to ${file}`)
  11. await fsPromises.writeFile(file, this._serialize(this.data))
  12. }
  13. get (path) {
  14. return objectPath.get(this.data, path)
  15. }
  16. set (path, value) {
  17. return objectPath.set(this.data, path, value)
  18. }
  19. // 通过抛出错误提醒子类必须覆写这个方法
  20. _serialize () {
  21. throw new Error('_serialize() must be implemented')
  22. }
  23. _deserialize () {
  24. throw new Error('_deserialize() must be implemented')
  25. }
  26. }
  1. import { ConfigTemplate } from './configTemplate.js'
  2. import ini from 'ini'
  3. export class IniConfig extends ConfigTemplate {
  4. _deserialize (data) {
  5. return ini.parse(data)
  6. }
  7. _serialize (data) {
  8. return ini.stringify(data)
  9. }
  10. }
  1. import { ConfigTemplate } from './configTemplate.js'
  2. export class JsonConfig extends ConfigTemplate {
  3. _deserialize (data) {
  4. return JSON.parse(data)
  5. }
  6. _serialize (data) {
  7. return JSON.stringify(data, null, ' ')
  8. }
  9. }

Iterator(迭代器)模式

只要某结构能按顺序产生元素,或者其中的元素能按顺序获取,那么我们就可以利用 Iterator 模式定义一套接口,让用户通过该接口访问此结构之中的元素。

iterator 协议

在 JS 语言里面实现 Iterator 模式,需要从 iterator protocol(迭代器协议)入手,这个协议约定了一套接口,用来产生一系列的值。
迭代器协议定义了一种叫做 iterator 的对象,这种对象必须实现 next() 方法,且 next() 方法每次受到调用时,都通过 iterator result 对象,把下一个元素返回给调用者,这个 iterator result 对象具备 done 与 value 这两项属性:

  • 如果迭代结束,done 属性应该是 true,否则应该是 undefined 或 false ```typescript const A_CHAR_CODE = 65 const Z_CHAR_CODE = 90

function createAlphabetIterator () { let currCode = A_CHAR_CODE

return { next () {

  1. // String.fromCodePoint() 静态方法返回使用指定的代码点序列创建的字符串
  2. const currChar = String.fromCodePoint(currCode)
  3. if (currCode > Z_CHAR_CODE) {
  4. return { done: true }
  5. }
  6. currCode++
  7. return { value: currChar, done: false }
  8. }

} }

const iterator = createAlphabetIterator() let iterationResult = iterator.next() while (!iterationResult.done) { console.log(iterationResult.value) iterationResult = iterator.next() }

  1. <a name="p9uQh"></a>
  2. ## iterable 协议
  3. iterable protocol(可迭代协议)规定了一种标准的方式,让某对象能够依照这种方式返回迭代器,以表示该对象里面的元素能够通过迭代器来迭代。这样的对象称为 iterable。<br />在 JS 中,可以让某种对象实现 `@@iterator `方法来表明这是个可迭代的(iterable)对象,这个方法可以通过内置的 `Symbol.iterator` 符号来表示。
  4. ```typescript
  5. class MyIterable {
  6. // 其他方法
  7. [Symbol.iterator]() {
  8. // 返回迭代器
  9. }
  10. }
  1. export class Matrix {
  2. constructor (inMatrix) {
  3. this.data = inMatrix
  4. }
  5. get (row, column) {
  6. if (row >= this.data.length || column >= this.data[row].length) {
  7. throw new RangeError('Out of bounds')
  8. }
  9. return this.data[row][column]
  10. }
  11. set (row, column, value) {
  12. if (row >= this.data.length || column >= this.data[row].length) {
  13. throw new RangeError('Out of bounds')
  14. }
  15. this.data[row][column] = value
  16. }
  17. [Symbol.iterator] () {
  18. let nextRow = 0
  19. let nextCol = 0
  20. return {
  21. next: () => {
  22. if (nextRow === this.data.length) {
  23. return { done: true }
  24. }
  25. const currVal = this.data[nextRow][nextCol]
  26. if (nextCol === this.data[nextRow].length - 1) {
  27. nextRow++
  28. nextCol = 0
  29. } else {
  30. nextCol++
  31. }
  32. return { value: currVal }
  33. }
  34. }
  35. }
  36. }
  1. import { Matrix } from './matrix.js'
  2. const matrix2x2 = new Matrix([
  3. ['11', '12'],
  4. ['21', '22']
  5. ])
  6. const iterator = matrix2x2[Symbol.iterator]()
  7. let iterationResult = iterator.next()
  8. while (!iterationResult.done) {
  9. console.log(iterationResult.value)
  10. iterationResult = iterator.next()
  11. }

Generator(生成器)

Generator 也叫做半协程(semicoroutine),是一种比普通函数更宽广的函数结构,可以有多个入口点(entry point)
生成器函数(generator function)所返回的生成器对象(generator object),本身即是 iterator,也是 iterable

  1. function * myGenerator() {
  2. // 生成器的主体部分
  3. }

简单的生成器函数

  1. function * fruitGenerator () {
  2. yield 'peach'
  3. yield 'watermelon'
  4. return 'summer'
  5. }
  6. const fruitGeneratorObj = fruitGenerator()
  7. console.log(fruitGeneratorObj.next()) // {value: 'peach', done: false}
  8. console.log(fruitGeneratorObj.next()) // {value: 'watermelon', done: false}
  9. console.log(fruitGeneratorObj.next()) // {value: 'summer', done: true}
  10. console.log('Using for...of:')
  11. for (const fruit of fruitGenerator()) {
  12. console.log(fruit)
  13. }
  14. // peach
  15. // watermelon

for … of 结构不会打印出 summer ,因为这个值不是由生成器函数的代码通过 yield 命名给出的,所以不会把他当成生成器对象所生成的元素

控制生成器对象的执行逻辑

  1. function * twoWayGenerator () {
  2. try {
  3. const what = yield null
  4. yield 'Hello ' + what
  5. } catch (err) {
  6. yield 'Hello error: ' + err.message
  7. }
  8. }
  9. console.log('Passing a value back to the generator:')
  10. const twoWay = twoWayGenerator()
  11. twoWay.next()
  12. // 传入的参数会被赋值给上一条 yield 语句左侧的变量
  13. console.log(twoWay.next('world')) // Hello world
  14. console.log('Using throw():')
  15. const twoWayException = twoWayGenerator()
  16. twoWayException.next()
  17. // {value: "Hello error: Boom!", done: false}
  18. console.log(twoWayException.throw(new Error('Boom!')))
  19. console.log('Using return():')
  20. const twoWayReturn = twoWayGenerator()
  21. // {value: "myReturnValue", done: true}
  22. console.log(twoWayReturn.return('myReturnValue'))
  • throw 方法返回的是一个表示迭代结果的对象,包含 done 与 value 属性
  • return 方法会迫使生成器终止,并返回 { done: true, value: returnArgument }形式的对象

    怎么用生成器来替换普通的迭代器

    1. export class Matrix {
    2. // ...
    3. * [Symbol.iterator] () {
    4. let nextRow = 0
    5. let nextCol = 0
    6. while (nextRow !== this.data.length) {
    7. yield this.data[nextRow][nextCol]
    8. if (nextCol === this.data[nextRow].length - 1) {
    9. nextRow++
    10. nextCol = 0
    11. } else {
    12. nextCol++
    13. }
    14. }
    15. }
    16. }

    ```typescript import { Matrix } from ‘./matrix.js’

const matrix2x2 = new Matrix([ [‘11’, ‘12’], [‘21’, ‘22’] ])

const iterator = matrix2x2Symbol.iterator let iterationResult = iterator.next() while (!iterationResult.done) { console.log(iterationResult.value) iterationResult = iterator.next() }

  1. 这次把维护迭代状态所用的两个变量,设计成 *[Symbol.iterator]() 函数本省的局部变量,而不是将其放在闭包中,因为这个函数所返回的生成器对象从暂停状态恢复执行的时候,是可以访问到生成器函数里面那些局部变量的,而且那些变量会延续它们暂停时的取值。
  2. <a name="imtfc"></a>
  3. ## async iterator(异步迭代器)
  4. async iterator 必须返回 Promise,其他的和普通的 iterator 一样<br />可以把这种 iterator next() 方法实现为异步函数,或通过 [Symbol.asyncIterator] 标识的方法同步地返回一个 async iterator<br />async iterator 可以用 for await...of 结构来迭代,和下面这段代码实现的功能相同:
  5. ```typescript
  6. const asyncIterator = iterator[Symbol.asyncIterator]();
  7. let iterationResult = await asyncIterator.next();
  8. while (!iterationResult.done) {
  9. console.log(iterationResult.value);
  10. iterationResult = await asyncIterator.next();
  11. }
  1. import superagent from 'superagent'
  2. export class CheckUrls {
  3. constructor (urls) {
  4. this.urls = urls
  5. }
  6. [Symbol.asyncIterator] () {
  7. // 可迭代对象通过 [Symbol.iterator]() 来触发它的 @@iterator 方法即可获得迭代器
  8. const urlsIterator = this.urls[Symbol.iterator]()
  9. // 返回 Promise 对象
  10. return {
  11. async next () {
  12. const iteratorResult = urlsIterator.next()
  13. if (iteratorResult.done) {
  14. return { done: true }
  15. }
  16. const url = iteratorResult.value
  17. try {
  18. const checkResult = await superagent
  19. .head(url)
  20. .redirects(2)
  21. return {
  22. done: false,
  23. value: `${url} is up, status: ${checkResult.status}`
  24. }
  25. } catch (err) {
  26. return {
  27. done: false,
  28. value: `${url} is down, error: ${err.message}`
  29. }
  30. }
  31. }
  32. }
  33. }
  34. }
  1. import { CheckUrls } from './checkUrls.js'
  2. async function main () {
  3. const checkUrls = new CheckUrls([
  4. 'https://nodejsdesignpatterns.com',
  5. 'https://example.com',
  6. 'https://mustbedownforsurehopefully.com'
  7. ])
  8. for await (const status of checkUrls) {
  9. console.log(status)
  10. }
  11. }
  12. main()

在使用 for await…of 结构来迭代时,如果这个结构因为遇到 break 语句、return 语句或异常而提前跳出,那么系统会在迭代器上面调用可选的 return() 方法,所以如果你的迭代器在那几种情况下,需要像正常执行完毕时那样做一些清理,那么可以把这些清理操作写在 return() 里

async generator(异步生成器)

  1. async function * generatorFunction() {
  2. // 生成器函数的主体部分
  3. }

函数的主体部分会出现 await 指令,它所返回的 async generator 对象,在执行 next() 方法的时候,会返回一个 Promise 对象,这个 Promise 对象得到解析后,会形成一个表示结果的对象,这个对象跟普通的 generator 在执行 next() 方法时所返回的对象一样,都具备 done 与 value 属性。

  1. import superagent from 'superagent'
  2. export class CheckUrls {
  3. constructor (urls) {
  4. this.urls = urls
  5. }
  6. async * [Symbol.asyncIterator] () {
  7. for (const url of this.urls) {
  8. try {
  9. const checkResult = await superagent
  10. .head(url)
  11. .redirects(2)
  12. yield `${url} is up, status: ${checkResult.status}`
  13. } catch (err) {
  14. yield `${url} is down, error: ${err.message}`
  15. }
  16. }
  17. }
  18. }

async iterator 与 Node.js 平台的 stream

stream.Readable 的设计者想让它成为一种 async iterable,它实现了 @@asyncIterator 方法,所以也能够通过 for await…of 结构读取

  1. import split from 'split2'
  2. async function main () {
  3. const stream = process.stdin.pipe(split())
  4. for await (const line of stream) {
  5. console.log(`You wrote: ${line}`)
  6. }
  7. }
  8. main()

Middleware(中间件)模式

在 Node.js 平台中,凡是能够在底层服务与应用程序间起到粘合作用的软件层,都可以叫做中间件

从模式的角度谈中间件

中间件模式其实是一种管道,只要有一组以函数形式来表示的处理单元、过滤器或处理程序,它们之间需要连接成一条异步的序列,用来给某种类型的数据做前置处理(preprocessiong)或后置处理(postprocessing),那么我们就可以用中间件模式来实现该管道。
image.png
这个模型最关键的部分是 Middleware Manager(中间件管理器),它负责安排并执行中间件函数(middleware function)。

  • 用户可以通过 use() 函数注册新的中间件
  • 管理中的每个单元(unit)都把上一个单元的执行结果,当作自己的输入信息
  • 管道中的每个中间件,都能够决定这份数据是否不再需要继续处理。当它认为这份数据不需要继续处理的时候,可以调用某个特殊的函数(如果下一个中间件是通过回调触发的),也可以不去触发回调。另外,还可以抛出错误。当某个中间件出错时,这个模式通常会触发另一系列的中间件,那个系列是专门用来处理错误的

下面两种方法都可以把修改后的数据沿着管道传递下去:

  • 让每个中间件把自己想要添加的属性或函数,补充到它所接收的这份数据上面,并把补充后的数据继续往下传
  • 将数据设计成不可变的对象,让每个中间件都把处理结果另外表示成一个对象,然后继续往下传递

    创建针对 ZeroMQ 的中间件框架

    编写 Middleware Manager(中间件管理器)

    1. export class ZmqMiddlewareManager {
    2. constructor (socket) {
    3. this.socket = socket
    4. this.inboundMiddleware = [] // 处理入站信息
    5. this.outboundMiddleware = [] // 出站信息
    6. this.handleIncomingMessages()
    7. .catch(err => console.error(err))
    8. }
    9. async handleIncomingMessages () {
    10. for await (const [message] of this.socket) {
    11. await this
    12. .executeMiddleware(this.inboundMiddleware, message)
    13. .catch(err => {
    14. console.error('Error while processing the message', err)
    15. })
    16. }
    17. }
    18. async send (message) {
    19. const finalMessage = await this
    20. .executeMiddleware(this.outboundMiddleware, message)
    21. return this.socket.send(finalMessage)
    22. }
    23. use (middleware) {
    24. if (middleware.inbound) {
    25. this.inboundMiddleware.push(middleware.inbound)
    26. }
    27. if (middleware.outbound) {
    28. // 和入站的函数相反顺序
    29. this.outboundMiddleware.unshift(middleware.outbound)
    30. }
    31. }
    32. // 执行某条管道中的那些中间件函数
    33. async executeMiddleware (middlewares, initialMessage) {
    34. let message = initialMessage
    35. for await (const middlewareFunc of middlewares) {
    36. // 把每个函数的执行结果传给管道中的下一个函数
    37. message = await middlewareFunc.call(this, message)
    38. }
    39. return message
    40. }
    41. }

    实现处理消息所用的中间件函数

    1. export const jsonMiddleware = function () {
    2. return {
    3. inbound (message) {
    4. return JSON.parse(message.toString())
    5. },
    6. outbound (message) {
    7. return Buffer.from(JSON.stringify(message))
    8. }
    9. }
    10. }

    ```typescript import { inflateRaw, deflateRaw } from ‘zlib’ import { promisify } from ‘util’

const inflateRawAsync = promisify(inflateRaw) const deflateRawAsync = promisify(deflateRaw)

export const zlibMiddleware = function () { return { inbound (message) { return inflateRawAsync(Buffer.from(message)) }, outbound (message) { return deflateRawAsync(message) } } }

  1. <a name="OiI76"></a>
  2. ### 使用这个针对 ZeroMQ 的中间件系统
  3. 服务器端
  4. ```typescript
  5. import zeromq from 'zeromq'
  6. import { ZmqMiddlewareManager } from './zmqMiddlewareManager.js'
  7. import { jsonMiddleware } from './jsonMiddleware.js'
  8. import { zlibMiddleware } from './zlibMiddleware.js'
  9. async function main () {
  10. const socket = new zeromq.Reply()
  11. await socket.bind('tcp://127.0.0.1:5000')
  12. const zmqm = new ZmqMiddlewareManager(socket)
  13. zmqm.use(zlibMiddleware())
  14. zmqm.use(jsonMiddleware())
  15. zmqm.use({
  16. async inbound (message) {
  17. console.log('Received', message)
  18. if (message.action === 'ping') {
  19. await this.send({ action: 'pong', echo: message.echo })
  20. }
  21. return message
  22. }
  23. })
  24. console.log('Server started')
  25. }
  26. main()

客户端

  1. import zeromq from 'zeromq'
  2. import { ZmqMiddlewareManager } from './zmqMiddlewareManager.js'
  3. import { jsonMiddleware } from './jsonMiddleware.js'
  4. import { zlibMiddleware } from './zlibMiddleware.js'
  5. async function main () {
  6. const socket = new zeromq.Request()
  7. await socket.connect('tcp://127.0.0.1:5000')
  8. const zmqm = new ZmqMiddlewareManager(socket)
  9. zmqm.use(zlibMiddleware())
  10. zmqm.use(jsonMiddleware())
  11. zmqm.use({
  12. inbound (message) {
  13. console.log('Echoed back', message)
  14. return message
  15. }
  16. })
  17. setInterval(() => {
  18. zmqm.send({ action: 'ping', echo: Date.now() })
  19. .catch(err => console.error(err))
  20. }, 1000)
  21. console.log('Client connected')
  22. }
  23. main()

Command(命令)模式

凡是把执行某动作所需的信息封装起来以便稍后加以执行的对象,都可以叫做 command 对象

设计这种对象是因为我们不打算直接调用某个方法或函数,而是打算创建这么一个 command 对象,用来表达我们需要执行调用操作的这一想法。这个想法稍后会由另一个组件负责实现,那个组件会把它落实成真正的操作。
image.png

  • Command:这个组件表示一种对象,用来封装调用某方法或某函数时所必备的信息
  • Client(用户/客户):这个组件用来创建 command,并把它提供给 Invoker
  • Invoker(调用者/调用器):负责在 Target 上面执行 command
  • Target(目标,也叫做 receiver 接收者):表示需要执行的东西,可能是某个单独的函数,或对象中的某个方法

    Task 模式

    在 JS 中创建一个表示调用操作的对象,可以通过创建一个函数,并在其中定义一个闭包,把需要调用的那项操作(target)裹在闭包中,然后返回该闭包,这种闭包叫做 target 的绑定函数(bound function)
    1. function createTask(target, ...args) {
    2. return () => {
    3. target(...args)
    4. }
    5. }
    1. const task = target.bind(null, ...args)
    这样就能通过一个单独的组件(task)来控制并安排某项有待执行的任务(target),这个组件就是 Command 模式的 Invoker

    用复杂些的方法实现 Command

    ```typescript const statusUpdates = new Map()

// The Target export const statusUpdateService = { postUpdate (status) { const id = Math.floor(Math.random() * 1000000) statusUpdates.set(id, status) console.log(Status posted: ${status}) return id },

destroyUpdate (id) { statusUpdates.delete(id) console.log(Status removed: ${id}) } }

  1. ```typescript
  2. export function createPostStatusCmd (service, status) {
  3. let postId = null
  4. // The Command
  5. return {
  6. run () {
  7. postId = service.postUpdate(status)
  8. },
  9. undo () {
  10. if (postId) {
  11. service.destroyUpdate(postId)
  12. postId = null
  13. }
  14. },
  15. serialize () {
  16. return { type: 'status', action: 'post', status: status }
  17. }
  18. }
  19. }
  1. import superagent from 'superagent'
  2. // The Invoker
  3. export class Invoker {
  4. constructor () {
  5. this.history = []
  6. }
  7. run (cmd) {
  8. this.history.push(cmd)
  9. cmd.run()
  10. console.log('Command executed', cmd.serialize())
  11. }
  12. delay (cmd, delay) {
  13. setTimeout(() => {
  14. console.log('Executing delayed command', cmd.serialize())
  15. this.run(cmd)
  16. }, delay)
  17. }
  18. undo () {
  19. const cmd = this.history.pop()
  20. cmd.undo()
  21. console.log('Command undone', cmd.serialize())
  22. }
  23. async runRemotely (cmd) {
  24. await superagent
  25. .post('http://localhost:3000/cmd')
  26. .send({ json: cmd.serialize() })
  27. console.log('Command executed remotely', cmd.serialize())
  28. }
  29. }
  1. import { createPostStatusCmd } from './createPostStatusCmd.js'
  2. import { statusUpdateService } from './statusUpdateService.js'
  3. import { Invoker } from './invoker.js'
  4. // The Client code
  5. const invoker = new Invoker()
  6. const command = createPostStatusCmd(statusUpdateService, 'HI!')
  7. invoker.run(command)
  8. invoker.undo()
  9. invoker.delay(command, 1000 * 3)
  10. invoker.runRemotely(command)

参考资料

  1. github