从一个功能设计聊聊策略模式的使用

背景描述

线上业务销售一种 VIP 虚拟卡,对于已售出的 VIP 虚拟卡在给供应商结算时需要根据销售渠道不同进行区分,例如,Android 端销售的虚拟卡按实际销售金额结算;iOS端销售的虚拟卡按实际销售金额 70% 结算(做苹果开发的都知道,苹果公司雁过拔毛,要拔走 30%);还有就是可能与其他商品捆绑销售,捆绑销售的金额根据合作不同部门、不同商品金额都不同。目前情况即是如此,虚拟卡销售渠道很多,不同渠道结算金额不同,而且还会扩展(最近就在经历扩展,华为属于 Android 渠道,但是也开始玩苹果那套规则,拔 30% 的毛)。

PS. 一句话需求,销售商品结算,不同销售渠道销售金额不同,而且随时会扩展新的渠道。

代码实现

如果设计模式玩溜了,对于这种业务场景,很容易想到使用策略(Strategy)模式来完成功能,所以这里也直接上代码实现:

策略接口定义

1
2
3
4
5
6
7
8
9
public interface CalculateStrategy {
/**
* 计算金额
* @param param 参数
* @return 计算结果
* @throws Exception
*/
public Integer calculate(StrategyParam param) throws Exception;
}

策略实现类

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
// Android 渠道结算策略
public class AndroidVipOrderStrategy implements CalculateStrategy {

@Override
public Integer calculate(StrategyParam param) throws Exception {
Order order = queryOrder(param);
return order.getOnlineAmount;
}
}

// iOS 渠道结算策略
public class IOSVipOrderStrategy implements CalculateStrategy {

@Override
public Integer calculate(StrategyParam param) throws Exception {
Order order = queryOrder(param);
return order.getOnlineAmount * 0.7;
}
}

// 捆绑组合渠道结算策略
public class BindVipOrderStrategy implements CalculateStrategy {

@Override
public Integer calculate(StrategyParam param) throws Exception {
return 199;
}
}

策略Context类

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 StrategyContext {

private StrategyParam param;

public StrategyContext(StrategyParam param) {
this.param = param;
}

public Integer calculate() {
CalculateStrategy result = getStrategy();
if (result == null) {
return 0;
}
return result.calculate(param);
}

public CalculateStrategy getStrategy() {
CalculateStrategy result = null;
SourceTypeEnum type = SourceTypeEnum.valueOf(param.getVipSourceType());
switch (type) {
case ANDROID:
result = new AndroidVipOrderStrategy();
break;
case IOS:
result = new IOSVipOrderStrategy();
break;
case BIND:
result = new BindVipOrderStrategy();
break;
default:
throw new UnsupportedOperationException();
}
return result;
}
}

客户端调用

1
2
3
4
5
6
public class Client {
public static void main(String args[]) throws Exception {
StrategyParam param = new StrategyParam();
System.out.println(new StrategyContext(param).calculate());
}
}

类图描述

观察者模式的四要素

定义与动机

定义:

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让他们可以相互替换。策略模式让算法独立于使用它的客户端而变化,也称为政策模式(Policy)。策略模式是一种对象行为型模式。

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

动机:

完成一项任务,往往可以有很多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成任务

为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法。在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类

结构与分析

模式结构:

类图:

  • Context封装角色:上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
  • Strategy抽象策略角色:策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。
  • ConcreteStratrgy具体策略角色:实现抽象策略中的操作,该类含有具体的算法。

优点 & 缺点

优点:

  • 对“开闭原则”的完美支持,可以在不修改原有系统的基础上灵活的增加新的算法和行为;
  • 策略模式提供了管理相关算法族的办法
  • 策略模式提供了可以替换继承关系的办法
  • 策略模式可以避免使用多重条件转移语句

缺点:

  • 客户端必须知道所有的策略类
  • 策略模式将产生很多策略类

应用场景

可以在以下情况中选择使用粗略模式:

  • 如果在一个系统里面有很多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态的让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

策略模式的扩展-枚举策略

枚举类定义

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
public enum CalculateStrategy {

ANDROID_VIP_ORDER {
@Override
public Integer calculate(StrategyParam param) throws Exception {
Order order = queryOrder(param);
return order.getOnlineAmount;
}
},

IOS_VIP_ORDER {
@Override
public Integer calculate(StrategyParam param) throws Exception {
Order order = queryOrder(param);
return order.getOnlineAmount * 0.7;
}
},

BINDVIPORDER {
@Override
public Integer calculate(StrategyParam param) throws Exception {
return 199;
}
};

/**
* 计算金额
* @param param 参数
* @return 计算结果
* @throws Exception
*/
public abstract Integer calculate(StrategyParam param) throws Exception;
}

Client调用类

1
2
3
public static void main(String args[]) throws Exception {
Integer result = CalculateStrategy.ANDROID_VIP_ORDER.calculate(new StrategyParam());
}

参考资料

感谢您的阅读,本文由 董宗磊的博客 版权所有。如若转载,请注明出处:董宗磊的博客(https://dongzl.github.io/2019/11/06/09-design-pattern-strategy/
GRIT:一种微服务场景下分布式事务协议实现
10-MySQL-InnoDB-Transaction