返回介绍

策略模式( Strategy )

发布于 2025-01-25 22:50:14 字数 5774 浏览 0 评论 0 收藏 0

策略模式通过将可变部分提取为单独的,可交换的对象 Strategy 来使对象 Context 支持其逻辑中的变化。 Context 实现通用逻辑,而策略实现了可变部分,允许上下文根据不同因素(如输入值,系统配置或用户偏好)调整其行为。这些策略通常是解决方案的一部分,他们都实现了相同的接口,这是 Context 对象所期望的接口。下图显示了我们刚刚描述的情况:

上图显示了 Context 对象如何将不同的策略插入到其结构中,就好像它们是一个机器的可替换部分一样。想象一下汽车,其轮胎可以视为适应不同路况的策略。我们可以安装冬季轮胎在雪路上行驶,这要归功于他们的螺栓,而我们可以决定为高速公路行驶的高性能轮胎做长途旅行。一方面,我们不想把整个车改变,另一方面,我们不想要一辆八轮车,这样就可以在任何一条路上行驶。

我们很快就明白这种模式有多强大,不仅有助于分离算法中的关注点,而且还使其具有更好的灵活性并适应同一问题的不同变化。

策略模式在支持算法变化需要复杂的条件逻辑(大量的 if...elseswitch 语句)或混合同一族不同算法的所有情况下特别有用。设想一个名为 Order 的对象,表示一个电子商务网站的在线订单。该对象有一个名为 pay() 的方法,就像它说的那样,完成订单并将资金从用户转移到商城用户。

为了支持不同的支付系统,我们有几个选项,如下所示:

  • pay() 方法中使用 if...else 语句来完成基于操作的操作。
  • 在选择的付款选项上将支付的逻辑委托给实现用户选择的特定支付网关逻辑的策略对象。

在第一种解决方案中,我们的订单对象不能支持其他支付方式,除非其代码被修改。而且,当支付选项的数量增加时,这可能变得相当复杂。相反,使用策略模式使得 Order 对象支持几乎无限数量的支付方法,并且保持其范围仅限于管理用户的细节,购买的项目和相对价格,同时将完成支付的工作委派给另一个对象。

现在让我们用一个简单实际的例子来展示这个模式。

多格式配置对象

让我们考虑一个名为 Config 的对象,该对象包含应用程序使用的一组配置参数,例如数据库 URL,服务器的侦听端口等。 Config 对象应该能够提供一个简单的接口来访问这些参数,而且还可以使用持久性存储(如文件)导入和导出配置。我们希望能够支持不同的格式来存储配置,例如 JSONINIYAML

通过应用我们了解的策略模式,我们可以立即识别 Config 对象的变量部分,这是允许我们序列化和反序列化配置的功能。

让我们创建一个名为 config.js 的新模块,让我们定义配置管理器的通用部分:

const fs = require('fs');
const objectPath = require('object-path');
class Config {
  constructor(strategy) {
    this.data = {};
    this.strategy = strategy;
  }
  get(path) {
    return objectPath.get(this.data, path);
  }
  // ...
}

在前面的代码中,我们将配置数据封装到一个实例变量( this.data )中,然后我们提供了 set()get() 方法,允许我们使用 object-path 访问配置属性(例如, property.subProperty ),通过利用 object-path 。在构造函数中,我们也采取了一种策略作为输入,它代表解析和序列化数据的算法。

现在让我们看看我们将如何使用策略,开始编写 Config 类的剩余部分:

const fs = require('fs');
const objectPath = require('object-path');

class Config {
  constructor(strategy) {
    this.data = {};
    this.strategy = strategy;
  }

  get(path) {
    return objectPath.get(this.data, path);
  }

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

  read(file) {
    console.log(`Deserializing from ${file}`);
    this.data = this.strategy.deserialize(fs.readFileSync(file, 'utf-8'));
  }

  save(file) {
    console.log(`Serializing to ${file}`);
    fs.writeFileSync(file, this.strategy.serialize(this.data));
  }
}

module.exports = Config;

在前面的代码中,当从文件中读取配置时,我们将反序列化任务委托给策略;那么当我们想把配置保存到文件中时,我们使用策略来序列化配置。这个简单的设计允许 Config 对象在加载和保存数据时支持不同的文件格式。

为了演示这一点,我们在一个名为 strategies.js 的文件中创建一些策略。 让我们从解析和序列化 JSON 数据的策略开始:

module.exports.json = {
  deserialize: data => JSON.parse(data),
  serialize: data => JSON.stringify(data, null, '  ')
}

没有什么复杂的!我们的策略简单地实现了接口,以便它可以被 Config 对象使用。

同样,我们要创建的下一个策略允许我们支持 INI 文件格式:

const ini = require('ini'); // https://npmjs.org/package/ini
module.exports.ini = {
  deserialize: data => ini.parse(data),
  serialize: data => ini.stringify(data)
}

现在,为了向您展示如何结合在一起,我们创建一个名为 configTest.js 的文件,让我们尝试使用不同的格式文件加载和保存示例配置:

const Config = require('./config');
const strategies = require('./strategies');
const jsonConfig = new Config(strategies.json);
jsonConfig.read('samples/conf.json');
jsonConfig.set('book.nodejs', 'design patterns');
jsonConfig.save('samples/conf_mod.json');
const iniConfig = new Config(strategies.ini);
iniConfig.read('samples/conf.ini');
iniConfig.set('book.nodejs', 'design patterns');
iniConfig.save('samples/conf_mod.ini');

我们的测试模块揭示了策略模式的属性。我们只定义了一个 Config 类,它实现了我们的配置管理器的公共部分,同时改变了用于序列化和反序列化的策略,允许我们创建支持不同文件格式的不同 Config 实例。

前面的例子只显示了使用策略模式实现多格式配置对象的方法之一。其他有效的方法可能如下:

  • 创建两个不同的策略系列:一个用于反序列化,另一个用于序列化。这将允许从格式读取并保存到另一个格式。
  • 根据所提供文件的扩展名,动态选择策略; Config 对象可以保持一个 map extension -> strategy ,并用它来为给定的扩展名选择正确的算法。

正如我们所看到的,有几种选择使用策略的选择,正确的选择取决于我们的要求,以及我们希望获得的特性/简单性的折衷。

而且,模式本身的实现可能会有很大的不同,例如,以其最简单的形式, contextstrategy 都可以是简单的函数:

function context(strategy) {...}

尽管前面的情况看起来可能微不足道,但在 JavaScript 等编程语言中,函数是一等公民,并且可以用作完全成熟的对象。

在所有这些变化之间,不变的是模式背后的思想;模式的实现可以稍微改变,但驱动模式实现的核心概念永远是一样的。

实际应用场景

Passport.jsNode.js 的认证框架,它允许在 Web 服务器上支持不同的认证方案。通过 Passport ,我们可以轻松使用 Facebook 登录或使用 Twitter 登录功能到我们的 Web 应用程序。 Passport 使用策略模式将认证过程中所需的公共逻辑与可以更改的部分(即实际的认证步骤)分开。例如,我们可能想要使用 OAuth 来获取访问令牌来访问 FacebookTwitter 个人资料,或者只需使用本地数据库来验证用户名/密码。对于 Passport ,这些都是完成身份验证过程的不同策略,正如我们所能想象的,这使得这个库可以支持几乎无限的身份验证服务。客户以看看 http://passportjs.org/guide/providers 上支持的不同身份验证,以了解策略模式可以执行的操作。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文