Skip to content

Latest commit

 

History

History
156 lines (119 loc) · 5.06 KB

《设计模式之美》之策略模式.md

File metadata and controls

156 lines (119 loc) · 5.06 KB

《设计模式之美》之策略模式

Strategy Design Pattern,定义是

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

策略模式的目的是,将策略的定义、创建和使用部分进行解耦

举个耦合在一起的例子

public class OrderService {
  public double discount(Order order) {
    double discount = 0.0;
    OrderType type = order.getType();
    if (type.equals(OrderType.NORMAL)) { // 普通订单
      //...省略折扣计算算法代码
    } else if (type.equals(OrderType.GROUPON)) { // 团购订单
      //...省略折扣计算算法代码
    } else if (type.equals(OrderType.PROMOTION)) { // 促销订单
      //...省略折扣计算算法代码
    }
    return discount;
  }
}
  • 此处不同订单对应的折扣计算逻辑即为不同的策略

先看下策略模式大概有哪些部分组成

策略的定义

public interface Strategy {
  void algorithmInterface();
}

public class ConcreteStrategyA implements Strategy {
  @Override
  public void  algorithmInterface() {
    //具体的算法...
  }
}

public class ConcreteStrategyB implements Strategy {
  @Override
  public void  algorithmInterface() {
    //具体的算法...
  }
}
  • 通过接口将不同的策略进行抽象,利用面向接口而非实现编程的思想,可以实现策略可以随意替换

策略的创建

  • 上面代码if else判断逻辑中,就需要创建不同的策略进行使用
  • 当策略很多时,if else的逻辑可能会影响到可读性
  • 策略模式中建议,可以通过工厂模式将策略的创建逻辑解耦出来

比如可以写成如下所示

public class StrategyFactory {
  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }

    if (type.equals("A")) {
      return new ConcreteStrategyA();
    } else if (type.equals("B")) {
      return new ConcreteStrategyB();
    }

    return null;
  }
}

进一步,如果每个策略是无状态的,我们可以通过一个静态的map,实现存储好每个策略,通过查表法来创建每个策略。这样则能完全将if else逻辑简化掉。如下所示

public class StrategyFactory {
  private static final Map<String, Strategy> strategies = new HashMap<>();

  static {
    strategies.put("A", new ConcreteStrategyA());
    strategies.put("B", new ConcreteStrategyB());
  }

  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    return strategies.get(type);
  }
}

我们来看下上面打折实例通过策略模式重构后的样子

// 策略的定义
public interface DiscountStrategy {
  double calDiscount(Order order);
}
// 省略NormalDiscountStrategy、GrouponDiscountStrategy、PromotionDiscountStrategy类代码...

// 策略的创建
public class DiscountStrategyFactory {
  private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();

  static {
    strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
    strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
    strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
  }

  public static DiscountStrategy getDiscountStrategy(OrderType type) {
    return strategies.get(type);
  }
}

// 策略的使用
public class OrderService {
  public double discount(Order order) {
    OrderType type = order.getType();
    DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
    return discountStrategy.calDiscount(order);
  }
}

总结

  • 策略模式通过1. 将策略封装到不同的类中;2. 通过工厂模式将策略的创建进行封装;
  • 控制了代码的复杂度(比如复杂的策略算法可能代码逻辑很多)
  • 当有新策略加入时,最小化代码改动的地方,降低bug出现几率,尽量满足开闭原则

后续

上面总结中说到,策略模式能够在新策略加入时尽量满足开闭原则,那有没有可能做到完全符合开闭原则呢?

当然是可以的。先看一下怎么样才算是完全符合开闭原则

  • 我理解如果有新策略加入时,我们只需要增加一个策略类,在策略类中编写核心的策略算法(对扩展开放);不需要对原来策略的创建和使用做修改。(对修改关闭)
  • 这样就算完全符合开闭原则了

显然,上面的代码无法做到对修改关闭,策略的创建部分还是需要添加一点点逻辑的。(其实上面的实现已经很好了,切忌过度设计)

解决办法也比较简单,核心就是利用语言的“反射”特性(很多面向对象语言都有该特性,即通过简单的描述如字符串,创建对应的对象出来)

  • 在配置文件中配置ordertype与对应策略类名的关系
  • 策略的创建和使用逻辑部分,改用动态读取配置文件和通过反射自动创建策略