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与对应策略类名的关系
- 策略的创建和使用逻辑部分,改用动态读取配置文件和通过反射自动创建策略