JavaScript内核与高级编程之:`JavaScript`的`Strategy`模式:其在算法切换中的应用。

各位观众老爷们,晚上好!我是你们的老朋友,今儿咱们唠唠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

哇! 代码瞬间变得优雅多了!

代码解释:

  1. DiscountStrategy 是策略接口,定义了 calculate 方法。
  2. FullReductionStrategyDiscountRateStrategyNoThresholdStrategy 是具体策略类,分别实现了不同的优惠券算法。
  3. ShoppingCart 是环境类,它维护了一个对策略对象的引用,并负责调用策略对象的 calculate 方法。
  4. 我们可以通过 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

代码解释:

  1. 我们使用对象字面量 discountStrategies 来存储不同的策略函数。
  2. ShoppingCartcalculateTotalPrice 方法接受一个策略函数,并调用它来计算总价。
  3. 我们可以通过 setStrategy 方法动态切换策略。

这种方式更加简洁,但是牺牲了一定的类型安全性。

九、总结(划重点啦!)

Strategy模式是一种非常有用的设计模式,它可以让我们更灵活地处理算法切换的问题。 记住,它就像一个工具箱,里面放着各种不同的“策略”,你想用哪个,就拿哪个出来用。 它能帮你告别if-else地狱,让代码更清晰易懂,更易于维护。

今天就讲到这里,希望大家有所收获。 下次有机会再和大家分享其他的设计模式! 散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注