1

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

 2 years ago
source link: https://rollingstarky.github.io/2022/05/26/node-js-design-patterns-strategy-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 设计模式笔记 —— Strategy 模式

2022-05-26

| Program

| 0

|

3.1k

|

0:03

Strategy 模式的主体是一个 context 对象,再把逻辑中有变化的部分抽取到独立的可相互替换的 strategy 对象中,从而使 context 支持不同的策略。即 context 实现通用的逻辑,strategy 实现可替换的部分。context 与 不同的 strategy 相组合即产生了多种不同的实现。
Strategy

就像雨天穿胶鞋,打篮球穿运动鞋,短跑比赛穿跑鞋。这些不同的鞋子对应的就是一系列策略,它们是同一类对象的不同变种,彼此之间可以相互替换。
面对不同的使用场景,选择对应的策略即可,这带来了更多的灵活性。首先鞋子和人不能是绑定的,这样的话,换鞋子就需要同时换掉整个人了;其次也没有任何一双鞋可以同时满足所有的使用场景。让鞋子作为可替换的插件无疑是最直观和方便的。
总的来说,策略代表了一个对象中可替换的部分。不同的策略应对同一个问题的不同变种。静态与动态分离。

比如需要实现一个 Order 对象,代表在线商城中的订单。该对象有一个 pay() 方法,负责支付行为,将用户的钱转移到商户手中。为了能够支持多种不同的支付方式,可以有以下两种选项:

  • pay() 方法中使用 if...else,根据不同的支付方式,完成对应的支付动作
  • 将支付的具体逻辑移交给独立的 strategy 对象,用户选择支付方式后,将对应的 strategy 注入到 Order

对于第一种方案,当 Order 对象需要支持更多的支付方式时,就必须要修改 Order 本身的代码。这会使代码变得非常复杂,难以维护。
当使用第二种 Strategy 模式时,理论上可以支持无限多的支付方式。Order 只负责维护用户、商品条目、价格等信息,具体的支付逻辑则由另一个 Strategy 对象来实现。Order 本身不会由于支付方式的增加而发生任何变更。

实例:支持 JSON、INI 等多种格式的 config 对象

mkdir strategy && cd strategy
npm install ini
npm install object-path

package.json

{
"type": "module",
"dependencies": {
"ini": "^3.0.0",
"object-path": "^0.11.8"
}
}

config.js

import {promises as fs} from 'fs'
import objectPath from 'object-path'

export class Config {
constructor(formatStrategy) {
this.data = {}
this.formatStrategy = formatStrategy
}

get(configPath) {
return objectPath.get(this.data, configPath)
}

set(configPath, value) {
return objectPath.set(this.data, configPath, value)
}

async load(filePath) {
console.log(`Deserializing from ${filePath}`)
this.data = this.formatStrategy.deserialize(
await fs.readFile(filePath, 'utf-8')
)
}

async save(filePath) {
console.log(`Serializing to ${filePath}`)
await fs.writeFile(filePath,
this.formatStrategy.serialize(this.data))
}
}

其中构造函数 constructor 接收一个具体的策略对象 formStrategy 作为参数,之后的 loadsave 方法又使用这个 formStrategy 去执行与格式相关的序列化和反序列化操作。不同的 formStrategy 有着不同的实现,从而 Config 类可以凭借 construcotr 接收的不同参数,与不同的策略整合,灵活地应对不同的需求场景。

strategy.js

import ini from 'ini'

export const iniStrategy = {
deserialize: data => ini.parse(data),
serialize: data => ini.stringify(data)
}

export const jsonStrategy = {
deserialize: data => JSON.parse(data),
serialize: data => JSON.stringify(data, null, ' ')
}

此处的代码实现了两种不同的策略:iniStrategyjsonStrategy,分别针对不同的文件格式。它们有着一致的接口,符合策略之间可以相互替换的原则,从而都可以被前面 Config 类的 loadsave 方法调用。

index.js

import {Config} from './config.js'
import {jsonStrategy, iniStrategy} from './strategy.js'

async function main() {
const iniConfig = new Config(iniStrategy)
await iniConfig.load('samples/conf.ini')
iniConfig.set('book.nodejs', 'design patterns')
await iniConfig.save('samples/conf_mod.ini')

const jsonConfig = new Config(jsonStrategy)
await jsonConfig.load('samples/conf.json')
jsonConfig.set('book.nodejs', 'design patterns')
await jsonConfig.save('samples/conf_mod.json')
}

main()

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