概念

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装成单独的对象,使它们可以相互替换。策略模式允许算法的变化独立于使用算法的客户端


策略模式通常涉及三个核心角色:上下文(Context)、策略接口(Strategy Interface)和具体策略类(Concrete Strategies)

  • 上下文(Context)

    负责维护对策略对象的引用,并在需要时调用策略对象的算法

    上下文通常会将请求委派给策略对象来执行特定的算法。

  • 策略接口(Strategy Interface)

    定义了所有具体策略类必须实现的算法接口

    这个接口通常只有一个方法,用于执行具体的算法。

  • 具体策略类(Concrete Strategies)

    实现了策略接口,包含了具体的算法实现

    每个具体策略类代表了一个具体的算法,可以根据需求增加或修改


策略模式的核心思想是将算法封装成独立的对象,并使这些对象可以相互替换,从而使得算法的变化不会影响到使用算法的客户端

这种模式的优点在于,提高了代码的灵活性和可维护性,使得算法可以在不修改客户端代码的情况下进行替换或者扩展

策略模式适用于以下情况:

  • 当一个系统需要支持多种算法,并且这些算法可以相互替换时,可以使用策略模式
  • 当一个类的行为取决于一些动态变化的条件时,可以考虑使用策略模式


举个简单的例子,考虑一个电商系统中的支付功能

系统可以支持多种支付方式,如支付宝、微信支付、信用卡支付等

策略模式可以将每种支付方式封装成一个具体的策略类,然后根据用户选择的支付方式来动态地选择并使用相应的支付策略,从而实现支付功能的灵活性和可扩展性


实现条件

  1. 存在一组相关的算法

    策略模式适用于存在一组相关的算法,并且客户端需要在运行时选择其中一个算法来使用的情况

  2. 算法之间可以相互替换

    策略模式的核心思想是将算法封装成策略对象,并且允许客户端在不修改客户端代码的情况下替换算法

  3. 需要避免使用条件语句来选择算法

    如果存在多个条件语句来选择不同的算法,可能会导致代码的可读性和可维护性下降

    策略模式可以将条件语句替换为对象之间的关系,从而提高代码的可读性和可维护性

  4. 需要将算法的实现和使用分离

    策略模式将算法的实现和使用分离,使得客户端可以独立于具体算法进行变化,从而提高系统的灵活性和可扩展性

  5. 需要将变化的部分封装成独立的对象

    策略模式将算法封装成策略对象,并将策略对象作为客户端和上下文对象的一部分,从而将变化的部分封装成独立的对象,方便扩展和修改


优点

  1. 灵活性高

    策略模式允许客户端在运行时动态地选择算法,从而提高系统的灵活性和可扩展性

  2. 可维护性好

    策略模式将算法封装成独立的策略对象,使得算法的实现和使用分离,从而提高了代码的可维护性

  3. 避免使用条件语句

    策略模式避免了使用过多的条件语句来选择不同的算法,从而提高了代码的可读性和可维护性

  4. 增加代码复用性

    策略模式将算法封装成独立的策略对象,可以在不同的上下文中重复使用相同的算法,从而提高了代码的复用性

  5. 符合开闭原则

    策略模式通过定义一组算法族,并通过策略对象进行封装和使用,可以方便地添加新的算法或修改现有算法,从而符合开闭原则


缺点

  1. 增加对象数量

    策略模式将每个算法都封装成独立的策略对象,可能会增加对象数量,从而增加了系统的复杂度

  2. 客户端需要了解所有策略

    客户端需要了解所有可用的策略,并在运行时选择合适的策略,可能会增加客户端的复杂度

  3. 可能增加系统的运行开销

    策略模式需要在运行时动态地选择算法,可能会增加系统的运行开销


实现方式

支付类实现

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 支付策略工厂类,用于创建和管理不同的支付策略
*/
class PaymentStrategyFactory {
/**
* 构造函数,初始化一个空的策略映射
*/
constructor() {
this.strategies = new Map()
}

/**
* 注册一个新的支付策略
* @param {string} name 策略的名称
* @param {PaymentStrategy} strategy 实现了PaymentStrategy接口的策略实例
* @throws {Error} 如果策略没有实现PaymentStrategy接口,则抛出错误
*/
register(name, strategy) {
if (!(strategy instanceof PaymentStrategy)) {
throw new Error("注册的策略必须实现PaymentStrategy接口")
}
this.strategies.set(name, strategy)
}

/**
* 注销一个已注册的支付策略
* @param {string} name 要注销的策略名称
* @returns {Error} 如果策略被成功注销,则无返回值;如果策略不存在,则打印警告
*/
unregister(name) {
if (this.strategies.has(name)) {
this.strategies.delete(name)
} else {
console.warn(`尝试注销不存在的支付策略: ${name}`)
}
}

/**
* 获取一个指定名称的支付策略
* @param {string} name 策略的名称
* @return {PaymentStrategy} 返回指定名称的支付策略实例
* @throws {Error} 如果未找到指定名称的策略,则抛出错误
*/
getStrategy(name) {
const strategy = this.strategies.get(name)
if (!strategy) {
throw new Error(`未找到名为 ${name} 的支付策略`)
}
return strategy
}
}


基础支付类实现

1
2
3
4
5
6
7
class BasicPaymentStrategy {
pay(amount) {
throw new Error("pay() 方法必须在子类中实现")
}
}

export default BasicPaymentStrategy


收银台类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 结账类,用于处理支付过程
*/
class Checkout {
/**
* 构造函数,初始化一个支付工厂
* @param {PaymentStrategyFactory} paymentFactory 用于创建支付策略的工厂
*/
constructor(paymentFactory) {
this.paymentFactory = paymentFactory
}

/**
* 处理支付请求
* @param {number} amount 需要支付的金额
* @param {string} strategyName 支付策略的名称
* @throws {Error} 如果找不到指定的支付策略,则抛出错误
*/
processPayment(amount, strategyName) {
const strategy = this.paymentFactory.getStrategy(strategyName)
strategy.pay(amount)
}
}


具体支付类实现

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 支付宝支付策略类,继承自基础支付策略类 BasicPaymentStrategy。
*/
import BasicPaymentStrategy from '../Basic/BasicPaymentStrategy'

class AliPayStrategy extends BasicPaymentStrategy {
/**
* 构造函数,初始化支付宝支付策略。
*/
constructor() {
super() // 调用父类的构造函数
this._partnerId = null // 合作伙伴ID,初始化为null
this._sellerId = null // 卖家ID,初始化为null
}

/**
* 设置支付所需的合作伙伴和卖家ID。
* @param {string} partnerId 合作伙伴ID
* @param {string} sellerId 卖家ID
* @returns {AliPayStrategy} 返回当前实例,支持链式调用
* @throws {Error} 如果缺少合作伙伴ID或卖家ID,则抛出错误
*/
setCredentials(partnerId, sellerId) {
if (!partnerId || !sellerId) {
throw new Error('partnerId 和 sellerId 都是必填项')
}
this._partnerId = partnerId
this._sellerId = sellerId

return this
}

/**
* 执行支付操作。
* @param {number} amount 需要支付的金额
* @throws {Error} 如果未设置合作伙伴ID或卖家ID,则抛出错误
*/
pay(amount) {
if (!this._partnerId || !this._sellerId) {
throw new Error('请先调用 setCredentials 设置 PartnerId 和 SellerId')
}
console.log(`使用支付宝支付,PartnerId: ${this._partnerId}, SellerId: ${this._sellerId}, 金额: ${amount}`)
}
}

export default new AliPayStrategy // 导出一个初始化好的 AliPayStrategy 实例


怎么使用

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 导入策略模式相关的类和支付策略的实现
*/
import { CheckStand, PaymentStrategy } from '../StrategyPattern'
import WeChatPayStrategy from '../SubClass/Expansion/WeChatPayStrategy'
import AliPayStrategy from '../SubClass/Expansion/AliPayStrategy'
import creditCardStrategy from '../SubClass/Expansion/CreditCardStrategy'

/**
* 注册不同的支付策略并设置其凭证或细节
* 1. 微信支付: 设置appId和apiKey
* 2. 支付宝支付: 设置合作伙伴ID和卖家ID
* 3. 信用卡支付: 设置卡号、安全码和到期日期
*/
PaymentStrategy.register("wechat", WeChatPayStrategy.setCredentials("wxAppId123", "apiKey456"))
PaymentStrategy.register("alipay", AliPayStrategy.setCredentials("aliPartnerId789", "sellerId012"))
PaymentStrategy.register("creditCard", creditCardStrategy.setCardDetails('1234567890123456', '123', '12/23'))

/**
* 处理不同方式的支付
* 1. 微信支付 100 元
* 2. 支付宝支付 200 元
* 3. 信用卡支付 200 元
*/
CheckStand.processPayment(100, "wechat") // 使用微信支付
CheckStand.processPayment(200, "alipay") // 使用支付宝支付
CheckStand.processPayment(200, "creditCard") // 使用信用卡支付

/**
* 动态添加新的支付策略
* 可以通过扩展PaymentStrategy来创建新的支付方式
*/
class NewPaymentStrategy extends PaymentStrategy {
// 代码参考其他具体支付策略类
}
PaymentStrategy.register("newMethod", NewPaymentStrategy.setCredentials())

/**
* 测试新添加的支付策略
* 使用新方法支付50元
*/
CheckStand.processPayment(50, "newMethod")

/**
* 移除不再支持的支付策略
* 从支持列表中移除微信支付
*/
PaymentStrategy.unregister("wechat")


场景

  1. 表单验证

    当需要根据不同的条件执行不同的验证逻辑时,可以使用策略模式

    例如,根据用户输入的不同数据类型(文本、数字、日期等),选择不同的验证算法

  2. 排序算法

    如果一个应用程序需要支持多种排序算法(如冒泡排序、快速排序、归并排序等),可以使用策略模式

    根据数据量大小、数据结构等因素,选择合适的排序策略

  3. 价格计算

    在线购物网站可能需要根据不同的促销活动或会员级别来计算产品的价格

    使用策略模式可以使得价格计算算法独立于产品和促销活动的变化

  4. 缓存策略

    在应用程序中实现缓存时,可能会有多种缓存策略可供选择,例如基于时间过期的策略、基于访问频率的策略等

    策略模式可以用于管理这些不同的缓存策略

  5. 日志记录

    根据日志级别(如调试、信息、警告、错误等),选择不同的日志记录策略

    例如,在生产环境中可能只记录错误日志,而在开发环境中记录所有日志


源代码