设计模式-策略模式

定义

Define a family of algorithms,encapsulate each one,and make them interchangeable.

定义一组算法将每个算法封装起来,并且使他们之间可以互换。

策略模式同样可以用来解决责任链模式中一堆if else的问题。

类图和代码

抽象出一个策略接口声明一个需要做的算法操作,以及一个上下文context用于执行算法。

这里我模拟一个原来项目中用到的操作:前端传入订单和支付方式,后端调用对应的策略向微信,支付宝或者银联提供的接口调用支付。为了简化操作,这里的订单我使用支付金额描述(实际项目中订单金额应通过后台系统进行计算),也去除了调用预支付下单接口的操作。

设计模式-策略模式\strategy

策略接口:

1
2
3
4
5
public interface IPayStrategy {

void doPay(int money);

}

分别写策略去实现:

1
2
3
4
5
6
public class AliPay implements IPayStrategy {
@Override
public void doPay(int money) {
System.out.println("调用了支付宝支付接口,支付"+money+"元");
}
}

其他微信、银联支付与之类似。

策略上下文,用于支付策略的执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StrategyContext {

private IPayStrategy iStrategy;

public StrategyContext(IPayStrategy iStrategy) {
this.iStrategy = iStrategy;
}

public IPayStrategy getiStrategy() {
return iStrategy;
}

public void setiStrategy(IPayStrategy iStrategy) {
this.iStrategy = iStrategy;
}

public void exec(int money){
iStrategy.doPay(money);
}
}

到这里为止,类图中画的这么多就基本完成了,但是很明显有一个问题:由于上下文中只负责调用托管的策略中的支付方法,所以我们还需要创建策略并托管给上下文。所以这里我打算使用一个策略枚举保存策略的种类,使用最简单的工厂方法创建一个策略,这样就避免了我们的业务模块和算法模块有太多的耦合。

策略枚举如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum StrategyEnums {
ALI("ali"),
WEIXIN("wx"),
UNION("union");


StrategyEnums(String value) {
this.value = value;
}

private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

工厂方法根据枚举类型创建策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StrategyFactory {

public static IPayStrategy createStrtegy(StrategyEnums strategyEnums){
IPayStrategy IPayStrategy = null;
switch (strategyEnums){
case ALI:
IPayStrategy = new AliPay();
break;
case WEIXIN:
IPayStrategy = new WeixinPay();
break;
case UNION:
IPayStrategy = new UnionPay();
break;
default:
}
return IPayStrategy;
}

}

可能有人会觉得这里同样有大量的case或者if else,但是我的理解是:必要的细节逻辑还是要有的,但是要在业务代码中隐藏起来,细节交由承担该职责的类来处理

现在创建策略和执行策略的上下文都已经完成了,现在我们需要做的步骤是:根据支付参数获得支付枚举,再利用支付枚举创建对应策略,然后委托给上下文执行这个策略。通常情况下,我们的支付参数很可能是一个标志,而不是一个枚举,所以很多情况下我们可能需要一个util或者helper类帮助我们将标志转化为枚举,假如前端传入的是字符串类型参数,工具类可以是:

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

StrategyEnums[] strategyEnums = StrategyEnums.values();

public StrategyEnums convert(String type){
for (StrategyEnums strategyEnum : strategyEnums) {
if(strategyEnum.getValue().equals(type)) return strategyEnum;
}
return null;
}

}

可以将标志到枚举的过程放入到门面类中,使得调用方只需要调用,而无需处理任务细节,这里我再加上一个门面模式的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HandlerFaced {

public void pay(PayModel payModel){

StrategyEnums strategyEnum = PayEnumsConvertUtil.convert(payModel.getPayType());

IPayStrategy strategy = StrategyFactory.createStrtegy(strategyEnum);

StrategyContext strategyContext = new StrategyContext(strategy);

strategyContext.exec(payModel.getMoney());
}

}

调用方调用就很简单了:

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

public static void main(String[] args) {
HandlerFaced handlerFaced = new HandlerFaced();

PayModel payModel = new PayModel("ali", 5);

handlerFaced.pay(payModel);
}

}

执行结果

调用了支付宝支付接口,支付5元

借助Spring

策略模式同样可以借助Spring来装一波。

首先将所有实现类加上@Component注解(@Service也行,本质差不多)并为注解中value赋值:

1
2
3
4
5
6
7
@Component("ali")
public class AliPay implements IPayStrategy {
@Override
public void doPay(int money) {
System.out.println("调用了支付宝支付接口,支付"+money+"元");
}
}

其他银联支付策略和微信支付策略类似。

接下来,我们无需门面类进行委托调用,只需要注入上下文类执行pay方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class StrategyContext {

private final Map<String, IPayStrategy> stringMap = new ConcurrentHashMap<>();

//会自动注入@component里写的字符串作为key,类型为IpayStrategy的beanMap。因为spring里的bean工厂本质上也就是一个ConcurrentHashMap

@Autowired
public StrategyContext(Map<String,IPayStrategy> stringStringMap) {
this.stringMap.clear();
//存入声明的map里

stringStringMap.forEach((k,v) -> this.stringMap.put(k,v));
}

public void pay(PayModel payModel){
if(payModel.getPayType() != null && payModel.getPayType().length() > 0)
//将传进来的支付类型作为key,取得对应bean执行即可,在业务中该字符串通常会约定好
stringMap.get(payModel.getPayType()).doPay(payModel.getMoney());

}
}

测试类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

@Autowired
private StrategyContext strategyContext;


@Test
public void strategyTest(){
PayModel payModel = new PayModel("union",10);
strategyContext.pay(payModel);

PayModel payModel1 = new PayModel("weixin", 20);
strategyContext.pay(payModel1);

}

}

执行结果:

调用了银联支付接口,支付10元
调用了微信支付接口,支付20元

总结

优点:算法自由切换;避免代码臃肿;扩展性良好,很符合开闭原则;

缺点:类数量膨胀。

坚持原创、技术分享。请作者喝杯茶吧!