概述
策略模式:(Strategy Design Pattern)定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。
策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
打个比方说,我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。
何时使用:
- 一个系统有许多许多类,而区分它们的只是他们直接的行为。
UML 类图:
角色组成:
- Context上下文: 也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
- 抽象策略角色:抽象策略角色,是对策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。
- 具体策略角色: 用于实现抽象策略中的操作,即实现具体的算法。
通用代码
Context上下文:
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
// 上下文接口
public void contextInterface() {
strategy.algorithmInterface();
}
}
抽象策略角色:
public interface Strategy {
// 算法方法
void algorithmInterface();
}
具体策略角色
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法A实现");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法B实现");
}
}
public class ConcreteStrategyC implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法C实现");
}
}
使用:
public class Test {
public static void main(String[] args) {
Context context;
context = new Context(new ConcreteStrategyA());
context.contextInterface();
context = new Context(new ConcreteStrategyB());
context.contextInterface();
context = new Context(new ConcreteStrategyC());
context.contextInterface();
}
}
结果:
算法A实现
算法B实现
算法C实现
如何利用策略模式避免分支判断
看这样一个案例:给不同的订单不同的折扣策略。不使用策略模式时,我们是这样写代码的。
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 enum OrderType {
NORMAL, // 普通订单
GROUPON, // 团购订单
PROMOTION // 促销订单
}
public class Order {
OrderType type;
public Order(OrderType type) {
this.type = type;
}
public OrderType getType() {
return type;
}
public void setType(OrderType type) {
this.type = type;
}
}
如何来移除掉分支判断逻辑呢?那策略模式就派上用场了。我们使用策略模式对上面的代码重构,将不同类型订单的打折策略设计成策略类,并由工厂类来负责创建策略对象。具体的代码如下所示:
public interface DiscountStrategy {
double calDiscount(Order order);
}
public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public double calDiscount(Order order) {
System.out.println("经过计算,该订单不打折!");
return 1;
}
}
public class GrouponDiscountStrategy implements DiscountStrategy {
@Override
public double calDiscount(Order order) {
System.out.println("经过计算,该订单打八折!");
return 0.8;
}
}
public class PromotionDiscountStrategy implements DiscountStrategy {
@Override
public double calDiscount(Order order) {
System.out.println("经过计算,该订单打七折!");
return 0.7;
}
}
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);
}
}
测试:
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.discount(new Order(OrderType.NORMAL));
orderService.discount(new Order(OrderType.GROUPON));
orderService.discount(new Order(OrderType.PROMOTION));
}
}
测试结果:
经过计算,该订单不打折!
经过计算,该订单打八折!
经过计算,该订单打七折!
测试用例的两种写法结果都是一样的
总结
策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。
- 策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。
- 策略的创建由工厂类来完成,封装策略创建的细节。
- 策略模式包含一组策略可选,客户端代码如何选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。