之所以会将策略和模板模式放在一起,是因为这两种模式用的最多最广泛,而且基本都是联合使用的。在开始之前,先复习一下模式的定义:
模板模式是在一个抽象类中定义执行的方法,每个方法中都有一个对应的业务流程模板,它的子类需要按照需要来重写模板流程中的方法,子类只需要对这些基本方法进行实现即可,子类并不需要对模板方法进行实现,这种设计模式也属于行为型模式。
策略模式是指一个策略接口被多个策略实现类所实现,通常会创建一个工厂类获取接口实现 bean,具体使用哪一种根据用户选择的类型来和 Map 里的 key 做匹配,策略模式是一种行为性模式。
不管是策略模式还是模板模式,都是运用了 java 多态这一特点,父类引用指向之类对象,通常情况下,模板模式使用的是抽象类,而策略模式使用的是接口而已。其子类都需要子类重写其父类方法或者实现接口方法,两者都满足开闭原则,使得系统在不影响其它功能的前提下更容易拓展。但是两者又有一些差异,模板模式是一种耦合的模式,策略模式是一种松散的模式。模板模式中,通常只有一个业务方法的入口,策略模式中的接口通常会有多个。
单纯的策略模式和模板模式在实践中应用很少,一般都是两种模式结合起来使用,如下图所示,这里即使用了模板模式的高内聚的业务流程,也是用了策略模式的松散性,相同的内容放在抽象类中进行处理,特有的内容放在具体的实现类里面进行操作。
在介绍了其应用场景后,在这里将结合实际的业务场景来介绍两种设计模式的合并使用。这里采用的是下单支付的场景,用户下单支付成功后,需要邮件和短信通知用户,并且给用户发积分并发送 MQ。
@Getter
public enum PayTypeEnum {
ALIPAY(1, "支付宝支付"),
WEIXIN(2, "微信支付"),
UNIPAY(3,"银联支付"),
;
PayTypeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Integer code;
private String msg;
public static PayTypeEnum getPayType(Integer code) {
return Arrays.stream(PayTypeEnum.values()).filter(e -> e.getCode().equals(code)).findFirst().orElse(null);
}
}
@Data
public class PayDto {
/**
* 订单号
*/
private String orderNo;
/**
* 支付方式 1 支付宝 2 微信 3 银联
*/
private Integer payType;
}
首先,我们需要定义一个接口类,如下图所示,定义了业务流程方法,发送邮件以及发送短信等方法。
public interface BaseBusiness {
/**
* 查询支付方式
*/
PayTypeEnum getCode();
/**
* 处理业务流程
*/
Result handleOrderFlow(PayDto pay);
/**
* 发送邮件
*/
boolean sendEmail(PayDto pay);
/**
* 发送手机短信
*/
boolean sendPhone(PayDto pay);
}
实现接口的模板抽象类,定义了业务的流程顺序,以及抽象的支付方法。同时也实现了发送短信和邮件的方法,还有一个发送消息的方法。
@Slf4j
public abstract class AbstractAppBusinessTemplate implements BaseBusiness {
/**
* 模板方法:处理业务流程
*/
@Override
public Result handleOrderFlow(PayDto pay) {
// step1 支付
boolean result = doPay(pay);
if (!result) {
return Result.failed("支付失败!");
}
// step2 发送短信和邮件通知到客户
sendEmail(pay);
sendPhone(pay);
// step3 发送用户积分
grantUserScore(pay);
// step4 发送消息
sendMsgMQ(pay);
return Result.success("处理成功!");
}
// 普通方法
public void sendMsgMQ(PayDto pay){
log.info("send mq {}", JSONObject.toJSONString(pay));
}
public void grantUserScore(PayDto pay){}
// 订单支付
protected abstract boolean doPay(PayDto pay);
@Override
public boolean sendEmail(PayDto pay) {
log.info("send email for order {}", pay.getOrderNo());
return true;
}
@Override
public boolean sendPhone(PayDto pay) {
log.info("send phone for order {}", pay.getOrderNo());
return true;
}
}
阿里业务类型实现类,这里实现了支付的方法,以及发送短信和邮件的方法,这里不同的业务可能配置不同的短信发送服务,通用的短信发送在抽象模板进行处理,特有的可以在具体的实现类里面实现。
@Slf4j
@Service
public class AlipayAppBusinessStrategy extends AbstractAppBusinessTemplate {
@Override
protected boolean doPay(PayDto pay) {
try {
TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("支付宝支付业务流程");
return true;
}
@Override
public boolean sendEmail(PayDto pay) {
System.out.println("AlipayAppBusinessStrategyImpl, email,开始运行");
super.sendEmail(pay);
System.out.println("AlipayAppBusinessStrategyImpl, email,运行完成");
return true;
}
@Override
public boolean sendPhone(PayDto pay) {
return super.sendPhone(pay);
}
}
@Slf4j
@Service
public class UnionPayAppBusinessStrategy extends AbstractAppBusinessTemplate {
@Override
protected boolean doPay(PayDto pay) {
try {
TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("银联支付业务流程");
return true;
}
@Override
public boolean sendEmail(PayDto pay) {
return super.sendEmail(pay);
}
@Override
public boolean sendPhone(PayDto pay) {
return super.sendPhone(pay);
}
}
@Slf4j
@Service
public class WeixinPayAppBusinessStrategy extends AbstractAppBusinessTemplate {
@Override
protected boolean doPay(PayDto pay) {
try {
TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("微信支付业务流程");
return true;
}
@Override
public boolean sendEmail(PayDto pay) {
return super.sendEmail(pay);
}
@Override
public boolean sendPhone(PayDto pay) {
return super.sendPhone(pay);
}
}
PayAppBusinessFactory 工厂类获取接口实现 bean,并存储到 ConcurrentHashMap,通过枚举获取对应的实现 bean
@Component
@Slf4j
public class PayAppBusinessFactory implements ApplicationContextAware {
public static final ConcurrentHashMap BASE_BUSINESS_BEAN_MAP = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.info("PayAppBusinessFactory 启动开始");
Map map = applicationContext.getBeansOfType(BaseBusinessService.class);
map.forEach((key, value) -> BASE_BUSINESS_BEAN_MAP.put(value.getCode(), value));
log.info("PayAppBusinessFactory 启动完成");
}
public static T getTrafficMode(PayTypeEnum code) {
return (T) BASE_BUSINESS_BEAN_MAP.get(code);
}
}
@PostMapping(value = "handle2")
public Result handle2(@RequestBody Pay) {
BaseBusinessService baseBusinessService = PayAppBusinessFactory.getTrafficMode(PayTypeEnum.getPayType(pay.getPayType()));
Result result = baseBusinessService.handleOrderFlow(pay);
log.info("result is {}", JSONObject.toJSONString(result));
return result;
}
}
入参:
{
"orderNo": "test1",
"payType": 1
}
返回结果:
{
"code": 200,
"data": "处理成功!",
"message": "操作成功"
}
日志打印:
PayAppBusinessFactory 启动开始
PayAppBusinessFactory 启动完成
支付宝支付业务流程
AlipayAppBusinessStrategyImpl, email,开始运行
send email for order test1
AlipayAppBusinessStrategyImpl, email,运行完成
send phone for order test1
send mq {"orderNo":"test1","payType":1}
result is {"code":200,"data":"处理成功!","message":"操作成功"}
接口中的方法都是抽象的,实际修饰符是 public abstract ,我们平时都省略了。所以抽象类不需要实现接口中的方法,但是抽象类的子类(抽象类除外)就要同时实现抽象类以及接口中的所有抽象方法。
调用接口,以阿里支付为例,先执行 AlipayAppBusinessStrategy 里的 sendEmail 方法,然后 super 调用父类(AbstractAppBusinessTemplate )的 sendEmail 方法,然后再执行 AlipayAppBusinessStrategy 里的 sendEmail 方法里 super 下面的代码。
意图:因为发邮件,每个支付通道的格式以及内容不一样,但是发送是一样的,各自的邮件内容及格式可以写在 AlipayAppBusinessStrategy 里,然后 AbstractAppBusinessTemplate 里的 sendEmail 方法写发送功能代码,因为这部分是一样的。
三个策略类中实现 baseBusiness 接口可写可不写,因为继承了父类,父类实现了 baseBusiness 接口,那三个策略类就要全部实现该 baseBusiness 接口中所有的方法。
5、interface 接口特征
在 java 中,可以使用关键字定义一个接口,一个接口由变量的定义和方法定义两部分组成。
[public] interface 接口名 {
[public] [static] [final] 变量;
[public] [abstract] 方法;
}
实现接口:要让一个类遵循某组特定的接口需要使用implements 关键字
[public] class 类名 implements Interface1,Interface2,...{
//实现所有接口中声明的方法
}
springboot-策略和模板模式的思考与实践