GeekIBLi

设计模式之美-策略模式

2021-08-07

策略模式

策略模式(Strategy Pattern)也叫做政策模式(Policy Pattern),它是将定义的算法封装起来,
让它们之间可以相互替换,从而让算法的变化不影响到使用算法的用户;它属于行为型模式。可以在一定程度上规避
if-else/switch等
策略模式使用的面向对象的继承和多态机制,从而实现同一行为在不同的场景下具备不同的实现;

应用

策略模式在实际应用场景中有很多的应用,凡是设计到选择的场景基本都可以使用策略模式来实现;比如购买一个商品选择支付方式,是选择银行卡还是快捷支付,微信?支付宝等;

  • 假如一个系统中有很多类,而它们的区别仅仅在与它们的行为不同;
  • 一个系统需要动态的从几种算法中选择一种;一些平台型产品中肯定会用到的
  • 需要屏蔽算法规则;

策略模式类图

通过上图可以看到,策略模式主要包含3个角色:
  • 上下文角色(Context): 用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略/算法的直接访问,封装可能
    存在的变化
  • 抽象策略角色(IStrategy): 规定策略或算法的行为
  • 具体的策略角色(ConcreteStrategy): 具体的策略逻辑或算法

这里的上下文角色仅仅是一个称谓,大家只要知道他在策略模式中的作用就可以了,就是桥接客户端和策略/方法的作用;

举个例子

背景: 电商场景下,用户购买一个商品,支付时可以选择一种优惠策略,如果没有优惠,则使用默认的策略,即无任何优惠;

1、上面两层是定义的一个营销策略的接口,然后提供不同的策略来实现;
2、第三层是策略生成所使用的工厂和客户端调用策略中间的上下文,承接上下,桥接模式;
3、最下层的Demo可以看成是客户端;

定义策略接口 制定标准方法

1
2
3
public interface IPromotionStrategy {
void doPromote();
}

CashBackStrategy 现金折返策略

1
2
3
4
5
6
public class CashBackStrategy implements IPromotionStrategy{
@Override
public void doPromote() {
System.out.println("直接返现");
}
}

CouponStrategy 优惠券策略

1
2
3
4
5
6
7
public class CouponStrategy implements IPromotionStrategy{
// 优惠券策略
@Override
public void doPromote() {
System.out.println("使用优惠券抵扣");
}
}

GroupBuyStrategy 团购优惠策略

1
2
3
4
5
6
public class GroupBuyStrategy implements IPromotionStrategy{
@Override
public void doPromote() {
System.out.println("团购 5人成团");
}
}

EmptyStrategy 默认策略

1
2
3
4
5
6
public class EmptyStrategy implements IPromotionStrategy{
@Override
public void doPromote() {
System.out.println("无任何优惠");
}
}

PromoteActivity 客户端和具体算法之间的上下文,用于桥接

1
2
3
4
5
6
7
8
9
10
11
12
public class PromoteActivity {

private IPromotionStrategy promotionStrategy;

public PromoteActivity(IPromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}

public void execute(){
promotionStrategy.doPromote();
}
}

PromoteStrategyFactory 策略工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class PromoteStrategyFactory {
private PromoteStrategyFactory() {
}

// 策略容器
private static Map<String, IPromotionStrategy> promotionStrategyMap = new HashMap<>();
// 默认空策略-没有任何优惠
private static IPromotionStrategy emptyStrategy = new EmptyStrategy();
// 这个可以放到配置文件或者放到数据库,在项目启动的时候加载到服务中就可以了
static {
promotionStrategyMap.put(PromoteKey.CASH_BACK, new CashBackStrategy());
promotionStrategyMap.put(PromoteKey.COUPON, new CouponStrategy());
promotionStrategyMap.put(PromoteKey.CROUP_BUY, new GroupBuyStrategy());
}

// 简单工厂创建营销策略
public static IPromotionStrategy createPromoteStrategy(String key) {
IPromotionStrategy promotionStrategy = promotionStrategyMap.get(key);
if (promotionStrategy == null) {
return emptyStrategy;
}
return promotionStrategy;
}

// 所有定义好的策略
private interface PromoteKey {
String COUPON = "coupon";
String CROUP_BUY = "groupBuy";
String CASH_BACK = "cashBack";
}

// 因为策略模式需要用户知道所有可用的策略,所以这个方法暴露给客户端
public static Set<String> getPromoteKeySet() {
return promotionStrategyMap.keySet();
}
}

策略模式下客户端写法

1
2
3
4
5
public static void main(String[] args) {
String strategy = PromoteStrategyFactory.getPromoteKeySet().stream().findAny().get();
IPromotionStrategy promoteStrategy = PromoteStrategyFactory.createPromoteStrategy(strategy);
promoteStrategy.doPromote();
}

非策略模式下客户端写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
String strategy = "客户选择的策略";
IPromotionStrategy promotionStrategy;
if ("团购".equals(strategy)){
promotionStrategy = new GroupBuyStrategy();
}else if ("优惠券".equals(strategy)){
promotionStrategy = new CouponStrategy();
}else if ("现金折返".equals(strategy)){
promotionStrategy = new CashBackStrategy();
}else {
promotionStrategy = new EmptyStrategy();
}
promotionStrategy.doPromote();
}

以上的对比,哪种方式比较优雅,立见高下了吧😄😄😄

策略模式的优点

  • 策略模式是符合开闭原则的
  • 避免使用多重条件转移语句 if-else语句、switch语句等
  • 使用策略模式可以提高算法的保密性和安全性(客户端通过上下文组建来调用具体的算法-桥接)

策略模式的缺点

  • 客户端必须要知道全部可用的策略,然后由用户决定使用那个策略,决定权在于客户
  • 代码中会产生很多的策略类,增加代码量和系统的维护难度

👤个人体会

1、结合业务场景来设计,体会设计模式的思路,学设计模式最重要的是思想;
2、设计模式一般在框架中体现的比较多,大家可以多学习一些框架源码的设计理念,如果你是初级,可能一下子理解不了,慢慢体会吧,当你工作一段时间之后,见过一些工业生产的项目,看过一些优秀的框架源码的时候,会有一个 “柳暗花明又一村”的阶段;
3、举个例子,Controller我们都写过的,一个项目中的接口路径是唯一的,项目启动的时候,由Spring加载到容器中,
当由请求进来时是,Spring是根据path来返回具体的控制器,也就是Controller具体的方法;
4、还有Comparator比较器,不同的容器内部做数据排序的时候由不同的实现,这也是策略模式的体现;
5、本文中的例子虽然简单,但是包含了简单工厂模式,桥接模式,策略模式。任何一种设计模式都难以独立存在,这个大家要注意;