策略模式是什么?

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

UML 类图

用 UML 类图来描述策略模式的结构,在模式中各个角色之间的关系:
image.png
根据上图,总结了模式中各个角色的职责以及它们之间的关系:

  • 抽象策略是所有具体策略的通用接口,它声明了公共的策略方法。
  • 具体策略实现了具体的策略算法。
  • 上下文维护一个指向具体策略的引用,并且仅通过策略接口与该对象交互。
  • 客户端创建一个特定的策略对象,并将其传递给上下文。

案例

让我们通过一个案例来帮助我们进一步理解策略模式。假设现在有一个在线商城系统,为了促进消费,商城经常会有打折促销活动。不同的时节会有不同的打折策略,比如周末有满 200 减 20 的活动;双十一有五折的活动。为此,我们定义了一个打折策略的接口,屏蔽不同时节打折策略的差异。

  1. public interface DiscountStrategy {
  2. BigDecimal applyDiscount(final BigDecimal originalPrice);
  3. }

现在,我们可以为各种时节实现具体的打折策略。

  1. // No discount
  2. public class NoDiscountStrategy implements DiscountStrategy {
  3. @Override
  4. public BigDecimal applyDiscount(final BigDecimal originalPrice) {
  5. return new BigDecimal(originalPrice.toPlainString());
  6. }
  7. }
  8. // discount strategy of the weekends
  9. public class WeekendStrategy implements DiscountStrategy {
  10. @Override
  11. public BigDecimal applyDiscount(BigDecimal originalPrice) {
  12. // 20 off on purchase of 200 or above
  13. BigDecimal salePrice;
  14. if (originalPrice.compareTo(new BigDecimal("200")) > 0) {
  15. salePrice = originalPrice.subtract(new BigDecimal("20"));
  16. } else {
  17. salePrice = new BigDecimal(originalPrice.toPlainString());
  18. }
  19. return salePrice;
  20. }
  21. }
  22. // discount strategy of Double Eleven
  23. public class DoubleElevenStrategy implements DiscountStrategy {
  24. @Override
  25. public BigDecimal applyDiscount(final BigDecimal originalPrice) {
  26. // discount of 50%.
  27. return originalPrice.multiply(new BigDecimal("0.5"));
  28. }
  29. }

顾客购买商品后,系统会生成一张订单,订单会根据打折策略计算最后的实际总价。

  1. public class Order {
  2. private List<OrderItem> orderItems = new ArrayList<>();
  3. // constructor, getters and setters
  4. public BigDecimal calculateSalePrice(DiscountStrategy discountStrategy) {
  5. // calculate the total price of all order items
  6. BigDecimal totalPrice = orderItems.stream()
  7. .map(item -> item.getTotalPrice())
  8. .reduce(new BigDecimal("0"), (x, y) -> x.add(y));
  9. // calculate the real sale price with discount
  10. BigDecimal salePrice = discountStrategy.applyDiscount(totalPrice);
  11. return salePrice;
  12. }
  13. }

最后,我们通过一个用例来模拟一下各个时节订单是怎么计费的。

  1. public class StrategyMain {
  2. public static void main(String[] args) {
  3. Order order = new Order();
  4. order.addItem(new OrderItem("Apple", new BigDecimal("9.8"), 10));
  5. order.addItem(new OrderItem("Orange", new BigDecimal("6.6"), 10));
  6. order.addItem(new OrderItem("Banana", new BigDecimal("3.9"), 15));
  7. BigDecimal fullPrice = order.calculateSalePrice(new NoDiscountStrategy());
  8. System.out.printf("This order costs $%s at ordinary times.\n", fullPrice);
  9. BigDecimal priceOnWeekends = order.calculateSalePrice(new WeekendStrategy());
  10. System.out.printf("This order costs $%s on weekends.\n", priceOnWeekends);
  11. BigDecimal priceOnDoubleEleven = order.calculateSalePrice(new DoubleElevenStrategy());
  12. System.out.printf("This order costs $%s on Double Eleven.\n", priceOnDoubleEleven);
  13. }
  14. }

案例源码

可在 GitHub 上查看上述案例的完整代码。

参考资料

本文参考的资料如下: