【SpringBoot】策略和模板模式的思考与实践

一、应用场景

之所以会将策略和模板模式放在一起,是因为这两种模式用的最多最广泛,而且基本都是联合使用的。在开始之前,先复习一下模式的定义:

  • 模板模式(Template Pattern)

模板模式是在一个抽象类中定义执行的方法,每个方法中都有一个对应的业务流程模板,它的子类需要按照需要来重写模板流程中的方法,子类只需要对这些基本方法进行实现即可,子类并不需要对模板方法进行实现,这种设计模式也属于行为型模式。

【SpringBoot】策略和模板模式的思考与实践_第1张图片

  • 策略模式(Strategy Pattern)

策略模式是指一个策略接口被多个策略实现类所实现,通常会创建一个工厂类获取接口实现 bean,具体使用哪一种根据用户选择的类型来和 Map 里的 key 做匹配,策略模式是一种行为性模式。

不管是策略模式还是模板模式,都是运用了  java 多态这一特点,父类引用指向之类对象,通常情况下,模板模式使用的是抽象类,而策略模式使用的是接口而已。其子类都需要子类重写其父类方法或者实现接口方法,两者都满足开闭原则,使得系统在不影响其它功能的前提下更容易拓展。但是两者又有一些差异,模板模式是一种耦合的模式,策略模式是一种松散的模式。模板模式中,通常只有一个业务方法的入口,策略模式中的接口通常会有多个。

单纯的策略模式和模板模式在实践中应用很少,一般都是两种模式结合起来使用,如下图所示,这里即使用了模板模式的高内聚的业务流程,也是用了策略模式的松散性,相同的内容放在抽象类中进行处理,特有的内容放在具体的实现类里面进行操作。

二、应用实践

在介绍了其应用场景后,在这里将结合实际的业务场景来介绍两种设计模式的合并使用。这里采用的是下单支付的场景,用户下单支付成功后,需要邮件和短信通知用户,并且给用户发积分并发送 MQ。

1、PayTypeEnum 枚举类

@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);
    }

}

2、PayDto 请求体

@Data
public class PayDto {

    /**
     * 订单号
     */
    private String orderNo;

    /**
     * 支付方式 1 支付宝 2 微信 3 银联
     */
    private Integer payType;

}

3、BaseBusiness 类

首先,我们需要定义一个接口类,如下图所示,定义了业务流程方法,发送邮件以及发送短信等方法。

public interface BaseBusiness {

    /**
     * 查询支付方式
     */
    PayTypeEnum getCode();

    /**
     * 处理业务流程
     */
    Result handleOrderFlow(PayDto pay);

    /**
     * 发送邮件
     */
    boolean sendEmail(PayDto pay);

    /**
     * 发送手机短信
     */
    boolean sendPhone(PayDto pay);

}

4、AbstractAppBusinessTemplate 类

实现接口的模板抽象类,定义了业务的流程顺序,以及抽象的支付方法。同时也实现了发送短信和邮件的方法,还有一个发送消息的方法。

@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;
    }

}

5、AliPayAppBusinessStrategy 类

阿里业务类型实现类,这里实现了支付的方法,以及发送短信和邮件的方法,这里不同的业务可能配置不同的短信发送服务,通用的短信发送在抽象模板进行处理,特有的可以在具体的实现类里面实现。

@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);
    }
}

6、UnionPayAppBusinessStrategy 类

@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);
    }
}

7、WeixinAppBusinessStrategy 类

@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);
    }
}

8、PayAppBusinessFactory 工厂类

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);
    }

}

9、controller

    @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;
    }

}

10、测试接口 localhost:8080/testUtils/handle2

入参: 

{
  "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":"操作成功"}

三、过程中的一些思考与总结

1、Java 类实现某个接口后,可以不用实现接口中的所有方法

接口中的方法都是抽象的,实际修饰符是 public abstract ,我们平时都省略了。所以抽象类不需要实现接口中的方法,但是抽象类的子类(抽象类除外)就要同时实现抽象类以及接口中的所有抽象方法。

2、sendEmail、sendPhone 方法执行顺序

调用接口,以阿里支付为例,先执行 AlipayAppBusinessStrategy 里的 sendEmail 方法,然后 super 调用父类(AbstractAppBusinessTemplate )的 sendEmail 方法,然后再执行 AlipayAppBusinessStrategy 里的 sendEmail 方法里 super 下面的代码。

意图:因为发邮件,每个支付通道的格式以及内容不一样,但是发送是一样的,各自的邮件内容及格式可以写在 AlipayAppBusinessStrategy 里,然后 AbstractAppBusinessTemplate 里的 sendEmail 方法写发送功能代码,因为这部分是一样的。

3、策略类中实现 baseBusiness 接口可写可不写(implements BaseBusiness)

三个策略类中实现 baseBusiness 接口可写可不写,因为继承了父类,父类实现了 baseBusiness 接口,那三个策略类就要全部实现该 baseBusiness 接口中所有的方法

4、abstract 抽象类特征
  • 子类在继承抽象类后,必须实现抽象类中的抽象方法
  • 抽象类中可以有抽象的方法,也可以有普通方法
  • 如果类中含有抽象的方法,当前类必须为抽象类

5、interface 接口特征

在 java 中,可以使用关键字定义一个接口,一个接口由变量的定义和方法定义两部分组成。

   [public] interface 接口名 {

          [public] [static] [final] 变量;

          [public] [abstract] 方法;

   }

实现接口:要让一个类遵循某组特定的接口需要使用implements 关键字

[public] class 类名 implements Interface1,Interface2,...{
    //实现所有接口中声明的方法
}
  • 实现接口的类,称为实现类
  • 实现类必须实现接口中所有的方法
  • 如果不实现接口的方法,那么该实现类也要为一个抽象类 

四、参考文档

springboot-策略和模板模式的思考与实践

你可能感兴趣的:(spring,boot,后端,java)