软件开发中,使用硬编码(Hard Coding)实现将导致系统违背开闭原则,扩展性较差,且维护困难,这时就可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法:->策略类 ->策略模式。
模式概念
策略模式(Strategy Pattern) 是一种对象行为模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互换。策略模式让算法的变化独立于使用算法的客户。
策略模式允许在运行时选择算法的行为,而不是在编译时静态绑定。这种模式非常适合用于需要动态选择算法的场景。
模式结构
- Context(环境类) :持有一个策略对象的引用,最终给客户端使用。
- Strategy(策略接口) :定义所有支持的算法的公共接口。
- ConcreteStrategy(具体策略类) :实现策略接口,提供具体的算法实现。
每一个封装算法的类称之为策略类,策略模式提供了一种可插入式算法的实现方案。
代码实现
// 策略接口
class Strategy {
execute() {
throw new Error("execute method must be implemented");
}
}
// 具体策略类A
class ConcreteStrategyA extends Strategy {
execute() {
console.log("Executing ConcreteStrategyA");
}
}
// 具体策略类B
class ConcreteStrategyB extends Strategy {
execute() {
console.log("Executing ConcreteStrategyB");
}
}
// 环境类
class Context {
constructor(strategy) {
this.strategy = strategy;
}
//设置策略
setStrategy(strategy) {
this.strategy = strategy;
}
//执行策略算法
executeStrategy() {
this.strategy.execute();
}
}
// 使用示例
const context = new Context(new ConcreteStrategyA());
context.executeStrategy(); // 输出: Executing ConcreteStrategyA
context.setStrategy(new ConcreteStrategyB());
context.executeStrategy(); // 输出: Executing ConcreteStrategyB
在这段代码中:
Strategy是策略接口,定义了所有策略必须实现的execute方法。ConcreteStrategyA和ConcreteStrategyB是具体策略类,实现了Strategy接口。Context是环境类,持有一个策略对象的引用,并提供executeStrategy方法来执行策略。
通过在运行时切换 context 的策略对象,可以改变执行的算法。
模式效果
-
优点:
- 算法的封装:每个算法封装在独立的类中,符合单一职责原则,易于管理。
- 易于扩展:新增算法只需新增一个具体策略类,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活的增加新的算法或行为,符合开闭原则。
- 易于切换:可以在运行时自由切换算法,增加灵活性。
- 解耦算法和使用算法的类:算法的实现和使用算法的类解耦,算法相似情况下,避免了多重条件选择语句。
-
缺点:
- 客户端必须知道所有的策略类:客户端需要了解所有策略类的具体实现,以便在运行时做出选择。
- 增加系统复杂度:可能会增加系统中类的个数。
- 无法同时使用多个:客户端无法同时使用多个策略类,一次只能使用一个策略。
模式应用
策略模式属于比较简单的设计模式,将不同的策略独立封装,可以轻松的在使用时切换策略,比如以下几种场景中可以使用:
- 动画的效果选择
先将不同的动画效果封装到独立的类中,需要时根据不同的条件应用不同的动画效果。
- 数组的排序算法
将不同的排序算法如快速排序、冒泡排序、选择排序等封装到独立的类中,处理数据时,根据不同的标准使用不同的排序算法。
- 支付业务
电子商务应用中,可能需要支持多种支付方式,每种支付方式都是一个策略。
典型策略模式和非典型策略模式
典型策略模式实现,策略通常是由实现了共同接口的类组成的,而在我们平时开发过程中,策略直接由对象的属性表示,这是 JavaScript语言特性支持的一种简化实现方式,可能并不会封装较多的策略类。
非典型策略模式指的是不严格遵循策略模式原始定义,但在实现上仍然利用了策略模式核心思想的设计方式。这种灵活性使得策略模式在 JavaScript 中特别有用,尤其是在处理需要高度动态行为的应用程序时。
使用映射来代替 if-else 语句是一种常见的模式,特别是在有多个条件分支时。以下是如何使用映射来实现支付策略的例子:
const paymentStrategies = {
//会员
1: money => {
// ... 一些别的逻辑
return money * 0.8;
},
//优惠卷
2: money => {
// ... 一些别的逻辑
return money - 5;
},
//普通
3: money => {
// ... 一些别的逻辑
return money;
},
};
function payUsingMapping(payType, money) {
const strategy = paymentStrategies[payType];
if (!strategy) {
throw new Error(`Payment strategy for type ${payType} is not defined.`);
}
return strategy(money);
}
console.log(payUsingMapping(1, 362));
console.log(payUsingMapping(3, 564));
这种代码方式使用策略模式的思想,将策略的实现与使用策略的代码解耦,支付时只需要调用payUsingMapping方法传入支付方式和金钱即可,无需考虑具体的支付流程,修改策略或添加新策略也与payUsingMapping无关。
如果你学过策略模式,在写代码时就可以写出更加优雅的代码,可以减少 if-else 语句的使用频率,降低代码耦合度,代码的可扩展性也会提高。
❤今天的分享就到这里,希望可以帮助到你!假如你对文章感兴趣,可以来我的公众号:小新学研社。