各位观众老爷们,晚上好!我是你们的老朋友,今儿咱们唠唠JavaScript
里的Strategy
模式,看看这玩意儿怎么在算法切换中大显身手。保证让你听得懂,学得会,用得上!
一、啥是Strategy模式?(别怕,不是战略忽悠局)
简单来说,Strategy
模式就像一个工具箱,里面放着各种不同的“策略”(算法)。你想用哪个,就拿哪个出来用。 关键在于,调用者并不知道具体用了哪个策略,它只知道“给我完成这个任务就行了”。
用人话讲,就像你去饭馆点菜。 你说:“老板,来个宫保鸡丁!” 你才不管厨师用的是哪个牌子的酱油,哪个产地的鸡丁,你只关心最后上来的菜是不是宫保鸡丁的味道。
专业一点的定义:
Strategy
模式是一种行为型设计模式,它允许你定义一系列算法,并将每一个算法封装到独立的类中,使得它们可以互相替换。Strategy
模式让算法独立于使用它的客户端而变化。
二、Strategy模式的好处(谁用谁知道!)
- 算法可替换: 随时切换算法,就像换电池一样方便。
- 代码可复用: 每个算法都是一个独立的类,可以在多个地方复用。
- 扩展性好: 想加新算法?没问题,直接加个新类就行,不用改动原有代码。
- 避免
if-else
地狱: 告别一堆if-else
判断,代码更清晰易懂。
三、Strategy模式的组成部分(拆开看看里面有啥)
- Context(环境类): 负责接收客户的请求,并委托给某个策略对象来处理。它维护一个对策略对象的引用。 就像饭馆里的服务员,负责点单,然后把单子交给厨师。
- Strategy(策略接口): 定义所有策略类都需要实现的接口。 就像菜谱,规定了每道菜都要有啥食材,怎么烹饪。
- ConcreteStrategy(具体策略类): 实现具体的算法。 就像不同的厨师,他们用不同的方法来做同一道菜。
四、实战演练:优惠券策略(买买买!)
假设我们要做一个电商网站,经常搞促销活动。常见的优惠券类型有:
- 满减券: 满多少减多少。
- 折扣券: 打几折。
- 无门槛券: 直接减。
如果我们不用Strategy
模式,代码可能会变成这样:
function calculateDiscount(type, price, threshold, discount) {
if (type === '满减') {
if (price >= threshold) {
return price - discount;
} else {
return price;
}
} else if (type === '折扣') {
return price * discount;
} else if (type === '无门槛') {
return price - discount;
} else {
return price;
}
}
console.log(calculateDiscount('满减', 100, 50, 20)); // 80
console.log(calculateDiscount('折扣', 100, 0, 0.8)); // 80
console.log(calculateDiscount('无门槛', 100, 0, 10)); // 90
这段代码看起来还行,但如果优惠券类型越来越多,if-else
会变得越来越长,维护起来非常痛苦。
现在,让我们用Strategy
模式来重构这段代码:
// 策略接口
class DiscountStrategy {
calculate(price) {
throw new Error('calculate方法必须被子类实现');
}
}
// 满减策略
class FullReductionStrategy extends DiscountStrategy {
constructor(threshold, discount) {
super();
this.threshold = threshold;
this.discount = discount;
}
calculate(price) {
if (price >= this.threshold) {
return price - this.discount;
} else {
return price;
}
}
}
// 折扣策略
class DiscountRateStrategy extends DiscountStrategy {
constructor(discountRate) {
super();
this.discountRate = discountRate;
}
calculate(price) {
return price * this.discountRate;
}
}
// 无门槛策略
class NoThresholdStrategy extends DiscountStrategy {
constructor(discount) {
super();
this.discount = discount;
}
calculate(price) {
return price - this.discount;
}
}
// 环境类
class ShoppingCart {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
calculateTotalPrice(price) {
return this.strategy.calculate(price);
}
}
// 使用
const cart1 = new ShoppingCart(new FullReductionStrategy(50, 20));
console.log('满减:', cart1.calculateTotalPrice(100)); // 80
const cart2 = new ShoppingCart(new DiscountRateStrategy(0.8));
console.log('折扣:', cart2.calculateTotalPrice(100)); // 80
const cart3 = new ShoppingCart(new NoThresholdStrategy(10));
console.log('无门槛:', cart3.calculateTotalPrice(100)); // 90
cart1.setStrategy(new DiscountRateStrategy(0.5)); // 动态切换策略
console.log("切换策略后:", cart1.calculateTotalPrice(100)); // 50
哇! 代码瞬间变得优雅多了!
代码解释:
DiscountStrategy
是策略接口,定义了calculate
方法。FullReductionStrategy
、DiscountRateStrategy
、NoThresholdStrategy
是具体策略类,分别实现了不同的优惠券算法。ShoppingCart
是环境类,它维护了一个对策略对象的引用,并负责调用策略对象的calculate
方法。- 我们可以通过
setStrategy
方法动态切换策略。
五、Strategy模式的应用场景(不止优惠券!)
- 排序算法: 不同的排序算法(冒泡排序、快速排序等)可以作为不同的策略。
- 支付方式: 不同的支付方式(支付宝、微信支付、银行卡支付等)可以作为不同的策略。
- 数据验证: 不同的数据验证规则可以作为不同的策略。
- 路由选择: 根据不同的条件选择不同的路由策略。
- 缓存策略: 使用不同的缓存算法。
六、Strategy模式的注意事项(别踩坑!)
- 策略类过多: 如果策略类过多,可能会增加代码的复杂性。
- 客户端需要了解所有策略: 客户端需要知道有哪些策略可以选择,这可能会增加客户端的负担。
- 策略之间的耦合: 如果策略之间存在依赖关系,可能会增加代码的复杂性。
七、Strategy模式与其他模式的比较(知己知彼,百战不殆)
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Strategy | 算法可替换、代码可复用、扩展性好、避免if-else 地狱 |
策略类过多、客户端需要了解所有策略、策略之间的耦合 | 需要在运行时动态切换算法,且算法之间相互独立 |
Template Method | 定义算法的骨架,将一些步骤延迟到子类实现,可以避免重复代码 | 子类需要继承父类,可能会限制子类的灵活性 | 算法的步骤基本相同,但某些步骤需要根据具体情况进行调整 |
State | 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类 | 状态类过多、状态之间的转换可能会比较复杂 | 对象的行为取决于它的状态,且状态之间存在复杂的转换关系 |
Factory Method | 定义一个创建对象的接口,让子类决定实例化哪个类,可以降低对象创建的耦合度 | 需要创建多个工厂类,可能会增加代码的复杂性 | 需要在运行时动态创建对象,且对象的类型取决于具体情况 |
八、用ES6+简化Strategy模式 (让代码更简洁!)
ES6 引入了一些新特性,比如箭头函数、类等,可以让我们更简洁地实现Strategy
模式。
// 策略接口 (函数式接口)
const discountStrategies = {
fullReduction: (price, threshold, discount) => (price >= threshold ? price - discount : price),
discountRate: (price, discountRate) => price * discountRate,
noThreshold: (price, discount) => price - discount,
};
// 环境类
class ShoppingCart {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
calculateTotalPrice(price, ...args) {
return this.strategy(price, ...args);
}
}
// 使用
const cart1 = new ShoppingCart(discountStrategies.fullReduction);
console.log('满减:', cart1.calculateTotalPrice(100, 50, 20)); // 80
const cart2 = new ShoppingCart(discountStrategies.discountRate);
console.log('折扣:', cart2.calculateTotalPrice(100, 0.8)); // 80
const cart3 = new ShoppingCart(discountStrategies.noThreshold);
console.log('无门槛:', cart3.calculateTotalPrice(100, 10)); // 90
cart1.setStrategy(discountStrategies.discountRate); // 动态切换策略
console.log("切换策略后:", cart1.calculateTotalPrice(100, 0.5)); // 50
代码解释:
- 我们使用对象字面量
discountStrategies
来存储不同的策略函数。 ShoppingCart
的calculateTotalPrice
方法接受一个策略函数,并调用它来计算总价。- 我们可以通过
setStrategy
方法动态切换策略。
这种方式更加简洁,但是牺牲了一定的类型安全性。
九、总结(划重点啦!)
Strategy
模式是一种非常有用的设计模式,它可以让我们更灵活地处理算法切换的问题。 记住,它就像一个工具箱,里面放着各种不同的“策略”,你想用哪个,就拿哪个出来用。 它能帮你告别if-else
地狱,让代码更清晰易懂,更易于维护。
今天就讲到这里,希望大家有所收获。 下次有机会再和大家分享其他的设计模式! 散会!