设计模式在电商业务下的实践——模版方法模式

专栏

持续更新中。

背景

设计模式在电商业务下的实践——状态模式 中我们曾提到过订单的支付方法,

image-20211207233615479.png 现在我们稍微深入一下。一般来说,微服务架构下,订单中心和支付中心是分开的,这里订单中心的支付处理其实只是监听到了支付中心的支付成功通知消息后,对订单的状态做一个更新。而支付的具体业务流程是在支付中心完成的,支付页面也是由支付中心统一收口的,俗称收银台。

image-20211217002352883.png 在收银台页面,我们一般可以选择不同的付款方式:

181638890528_.pic_hd_副本.png

最开始,我们可能会写出支付接口的代码形式如下

初始代码

支付方式枚举

public enum PayTypeEnum {

    AliPay(1, "支付宝支付"),
    WeXinPay(2, "微信支付"),
    //...
    ;

    private int code;
    private String desc;

    PayTypeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return this.code;
    }

    public String getDesc() {
        return this.desc;
    }

    public static PayTypeEnum getByCode(int code) {
        for (PayTypeEnum payTypeEnum : values()) {
            if (code == payTypeEnum.getCode()) {
                return payTypeEnum;
            }
        }
        throw new IllegalArgumentException("PayTypeEnum not exist, code=" + code);
    }
}
复制代码

支付service类

这里支付方法假设有四个小步骤,如代码所示

public class PayService {

    /**
     * 支付方法
     *
     * @param payRequest
     * @return
     */
    public PayResponse pay(PayRequest payRequest) {
        try {
            //1.基础参数校验
            requestCheck(payRequest);
            //2.构建支付参数,并调用支付接口获取结果
            PayResponse payResponse;
            PayTypeEnum payTypeEnum = PayTypeEnum.getByCode(payRequest.getPayType());
            switch (payTypeEnum) {
                case AliPay:
                    payResponse = aliPaySendRequest(payRequest);
                    break;
                case WeXinPay:
                    payResponse = weXinPaySendRequest(payRequest);
                    break;
                default:
                    throw new IllegalArgumentException("PayTypeEnum not exist, code=" + payRequest.getPayType());
            }
            //3.如果失败,做额外打点监控(钩子方法)
            if (!payResponse.getRes()) {
                monitorFail(payRequest, payResponse);
            }
            //4.记录结果
            record(payRequest, payResponse);
            //其他步骤...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null);
        }
    }

    /**
     * 基础参数校验
     * @param payRequest
     */
    private void requestCheck(PayRequest payRequest) {
        //校验金额是否大于0
        if (payRequest.getTotalAmount().compareTo(BigDecimal.ZERO) != 1) {
            throw new RuntimeException("金额必须大于0");
        }
        //其余校验...
    }

    private void monitorFail(PayRequest payRequest, PayResponse payResponse) {
        //对失败进行监控
        //log...s
    }

    private void record(PayRequest payRequest, PayResponse payResponse) {
        //将请求信息做记录...
        //PayRecord payRecord = new PayRecord();
    }

    /**
     * 支付宝支付
     */
    private PayResponse aliPaySendRequest(PayRequest payRequest) {
        //构建支付宝支付参数
        AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
        alipayPayRequest.setOrderSn(payRequest.getOrderSn());
        alipayPayRequest.setTotalAmount(payRequest.getTotalAmount());
        //...

        //请求支付宝接口并返回结果,这里模拟一个出来
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        AlipayPayResponse alipayPayResponse = new AlipayPayResponse();
        alipayPayResponse.setTradeNo("2014112400001000340011111118");
        alipayPayResponse.setRes(true);

        return new PayResponse(alipayPayResponse.getRes(), alipayPayResponse.getMsg(), alipayPayResponse.getTradeNo());
    }

    /**
     * 微信支付
     */
    private PayResponse weXinPaySendRequest(PayRequest payRequest) {
        //构建微信支付参数
        WexinPayRequest wexinPayRequest = new WexinPayRequest();
        wexinPayRequest.setOrderSn(payRequest.getOrderSn());
        wexinPayRequest.setTotalAmount(payRequest.getTotalAmount());
        //...

        //请求微信接口并返回结果,这里模拟一个出来
        //WexinPayResponse wexinPayResponse = call(wexinPayRequest);
        WexinPayResponse wexinPayResponse = new WexinPayResponse();
        wexinPayResponse.setTradeNo("2014112400001000340011111118");
        wexinPayResponse.setRes(true);

        return new PayResponse(wexinPayResponse.getRes(), wexinPayResponse.getMsg(), wexinPayResponse.getTradeNo());
    }
}

复制代码

是不是发现,这里PayService类有点过长了,它承担了太多的责任,违背了单一职责原则。并且,每增加一种支付方式,都会导致该类越来越长,可读性极差,而且还会修改原有经过严格测试的流程代码,从而违背开闭原则。

为了解决这两个问题,我们尝试使用策略模式来优化一下。

优化代码

抽象支付类

封装一些通用的方法,开放扩展具体的支付方法

public abstract class AbstractPayService {
  
  	abstract PayTypeEnum getPayTypeEnum();

    @PostConstruct
    void register() {
        PayServiceFactory.register(getPayTypeEnum(), this);
    }

    public abstract PayResponse pay(PayRequest payRequest);

    protected final void requestCheck(PayRequest payRequest) {
        //校验金额是否大于0
        if (payRequest.getTotalAmount().compareTo(BigDecimal.ZERO) != 1) {
            throw new RuntimeException("金额必须大于0");
        }
        //其余校验...
    }

    protected final void monitorFail(PayRequest payRequest, PayResponse payResponse) {
        //对失败进行监控
        //log...s
    }

    protected final void record(PayRequest payRequest, PayResponse payResponse) {
        //将请求信息做记录...
        //PayRecord payRecord = new PayRecord();
    }
}

复制代码

具体支付类

分别实现支付宝支付,微信支付的子类

/**
 * 支付宝支付
 */
@Service
public class AlipayPayService extends AbstractPayService{
  
  	@Override
    PayTypeEnum getPayTypeEnum() {
        return PayTypeEnum.AliPay;
    }

    @Override
    public PayResponse pay(PayRequest payRequest) {
        try {
            //基础校验
            requestCheck(payRequest);
            //构建支付宝支付参数,并调用支付宝支付接口获取结果
            PayResponse payResponse = sendRequest(payRequest);
            //如果失败,做额外打点监控
            if (!payResponse.getRes()) {
                monitorFail(payRequest, payResponse);
            }
            //记录结果
            record(payRequest, payResponse);
            //其他步骤...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null);
        }
    }

    private PayResponse sendRequest(PayRequest payRequest) {
        //构建支付宝支付参数
        AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
        alipayPayRequest.setOrderSn(payRequest.getOrderSn());
        alipayPayRequest.setTotalAmount(payRequest.getTotalAmount());
        //...

        //请求支付宝接口并返回结果,这里模拟一个出来
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        AlipayPayResponse alipayPayResponse = new AlipayPayResponse();
        alipayPayResponse.setTradeNo("2014112400001000340011111118");
        alipayPayResponse.setRes(true);

        return new PayResponse(alipayPayResponse.getRes(), alipayPayResponse.getMsg(), alipayPayResponse.getTradeNo());
    }
}

/**
 * 微信支付
 */
@Service
public class WeXinPayService extends AbstractPayService{

  	@Override
    PayTypeEnum getPayTypeEnum() {
        return PayTypeEnum.WeXinPay;
    }
  
    @Override
    public PayResponse pay(PayRequest payRequest) {
        try {
            //基础校验
            requestCheck(payRequest);
            //构建微信支付参数,调用微信支付接口
            PayResponse payResponse = sendRequest(payRequest);
            //如果失败,做额外打点监控
            if (!payResponse.getRes()) {
                monitorFail(payRequest, payResponse);
            }
            //记录结果
            record(payRequest, payResponse);
            //其他步骤...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null);
        }
    }

    private PayResponse sendRequest(PayRequest payRequest) {
        //构建微信支付参数
        WexinPayRequest wexinPayRequest = new WexinPayRequest();
        wexinPayRequest.setOrderSn(payRequest.getOrderSn());
        wexinPayRequest.setTotalAmount(payRequest.getTotalAmount());
        //...

        //请求微信接口并返回结果,这里模拟一个出来
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        WexinPayResponse wexinPayResponse = new WexinPayResponse();
        wexinPayResponse.setTradeNo("2014112400001000340011111118");
        wexinPayResponse.setRes(true);

        return new PayResponse(wexinPayResponse.getRes(), wexinPayResponse.getMsg(), wexinPayResponse.getTradeNo());
    }
}
复制代码

工厂类

public class PayServiceFactory {

    private static final Map<PayTypeEnum, AbstractPayService> payServiceMap = new HashMap<>();

    public static void register(PayTypeEnum payTypeEnum, AbstractPayService payService) {
        payServiceMap.put(payTypeEnum, payService);
    }

    public static AbstractPayService get(PayTypeEnum payTypeEnum) {
        return payServiceMap.get(payTypeEnum);
    }
}
复制代码

经过策略模式的优化,增加新的支付方式时,也可以基本不动原有的代码了,且每种支付方式的代码独立,代码看起来更整洁一些。但是,有没有发现,在微信支付类和支付宝支付类中,pay方法中的逻辑一样,只是sendRequest方法中的调用外部接口的逻辑不一致,我们尝试使用 模版方法模式 做进一步优化。

定义

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

模式的结构

模板方法模式包含以下主要角色。

抽象模板

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

② 基本方法:是整个算法中的一个步骤,包含以下几种类型。

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它,但里氏替换原则指导我们,尽量不要重写父类的非抽象方法。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

具体实现

具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

UML图

含钩子方法的模板方法模式的结构图

模式基本实现

抽象模版类

//含钩子方法的抽象类
abstract class HookAbstractClass {
    //模板方法
    public void TemplateMethod() {
        abstractMethod1();
        HookMethod1();
        if (HookMethod2()) {
            SpecificMethod();
        }
        abstractMethod2();
    }
    //具体方法
    public void SpecificMethod() {
        System.out.println("抽象类中的具体方法被调用...");
    }
    //钩子方法1
    public void HookMethod1() {
    }
    //钩子方法2
    public boolean HookMethod2() {
        return true;
    }
    //抽象方法1
    public abstract void abstractMethod1();
    //抽象方法2
    public abstract void abstractMethod2();
}
复制代码

具体子类

//含钩子方法的具体子类
class HookConcreteClass extends HookAbstractClass {
    public void abstractMethod1() {
        System.out.println("抽象方法1的实现被调用...");
    }
    public void abstractMethod2() {
        System.out.println("抽象方法2的实现被调用...");
    }
    public void HookMethod1() {
        System.out.println("钩子方法1被重写...");
    }
    public boolean HookMethod2() {
        return false;
    }
}
复制代码

测试类

public class HookTemplateMethod {
    public static void main(String[] args) {
        HookAbstractClass tm = new HookConcreteClass();
        tm.TemplateMethod();
    }
}
复制代码

由于钩子方法2返回false,所以抽象类中的SpecificMethod不会被调用,执行的结果如下

抽象方法1的实现被调用...
钩子方法1被重写...
抽象方法2的实现被调用...
复制代码

如果钩子方法 HookMethod1() 和钩子方法 HookMethod2() 的代码改变,则程序的运行结果也会改变。

优化支付方法

定义支付方式枚举

与之前一致

定义抽象支付类

pay方法为模版方法,定义了一系列的逻辑,将sendRequest方法开放给子类覆写即可,其余方法保持不变。

public abstract class AbstractPayService {

    abstract PayTypeEnum getPayTypeEnum();

    @PostConstruct
    final void register() {
        PayServiceFactory.register(getPayTypeEnum(), this);
    }

    /**
     * 支付模版方法
     * @param payRequest
     * @return
     */
    public final PayResponse pay(PayRequest payRequest) {
        try {
            //基础校验
            requestCheck(payRequest);
            //构建支付参数,并调用支付接口获取结果
            PayResponse payResponse = sendRequest(payRequest);
            //如果失败,做额外打点监控(钩子方法)
            if (!payResponse.getRes()) {
                monitorFail(payRequest, payResponse);
            }
            //记录结果
            record(payRequest, payResponse);
            //其他步骤...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null);
        }
    }

    private void requestCheck(PayRequest payRequest) {
        //校验金额是否大于0
        if (payRequest.getTotalAmount().compareTo(BigDecimal.ZERO) != 1) {
            throw new RuntimeException("金额必须大于0");
        }
        //其余校验...
    }

    private void monitorFail(PayRequest payRequest, PayResponse payResponse) {
        //对失败进行监控
        //log...s
    }

    private void record(PayRequest payRequest, PayResponse payResponse) {
        //将请求信息做记录...
        //PayRecord payRecord = new PayRecord();
    }

    /**
     * 抽象支付请求方法
     * @param payRequest
     * @return
     */
    abstract protected PayResponse sendRequest(PayRequest payRequest);
}

复制代码

定义具体支付类

各自实现sendRequest即可。

@Service
public class AlipayPayService extends AbstractPayService {

    @Override
    PayTypeEnum getPayTypeEnum() {
        return PayTypeEnum.AliPay;
    }

    @Override
    protected PayResponse sendRequest(PayRequest payRequest) {
        //构建支付宝支付参数
        AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
        alipayPayRequest.setOrderSn(payRequest.getOrderSn());
        alipayPayRequest.setTotalAmount(payRequest.getTotalAmount());
        //...

        //请求支付宝接口并返回结果,这里模拟一个出来
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        AlipayPayResponse alipayPayResponse = new AlipayPayResponse();
        alipayPayResponse.setTradeNo("2014112400001000340011111118");
        alipayPayResponse.setRes(true);

        return new PayResponse(alipayPayResponse.getRes(), alipayPayResponse.getMsg(), alipayPayResponse.getTradeNo());
    }
}

@Service
public class WexinPayService extends AbstractPayService {

    @Override
    PayTypeEnum getPayTypeEnum() {
        return PayTypeEnum.WeXinPay;
    }

    @Override
    protected PayResponse sendRequest(PayRequest payRequest) {
        //构建微信支付参数
        WexinPayRequest wexinPayRequest = new WexinPayRequest();
        wexinPayRequest.setOrderSn(payRequest.getOrderSn());
        wexinPayRequest.setTotalAmount(payRequest.getTotalAmount());
        //...

        //请求微信接口并返回结果,这里模拟一个出来
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        WexinPayResponse wexinPayResponse = new WexinPayResponse();
        wexinPayResponse.setTradeNo("2014112400001000340011111118");
        wexinPayResponse.setRes(true);

        return new PayResponse(wexinPayResponse.getRes(), wexinPayResponse.getMsg(), wexinPayResponse.getTradeNo());
    }
}
复制代码

封装工厂类

与之前一致

模式优缺点与应用

优点

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,便于代码复用,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 每个子类只需要负责属于自己的扩展功能,符合单一职责原则。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  • 子类数量控制的合理的话,代码看起来会比较清晰。

缺点

  • 对不同的实现都需要定义一个子类,这会导致类的个数增加,设计也更加抽象,类的数量过多,会间接地增加了系统实现的复杂度。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  • 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。不变的部分保留在父类中,避免代码重复。
  • 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

模式源码应用

  • MyBatis 源码中BaseExecutor的实现,一个基础的 SQL 执行类,实现了大部分 SQL 执行逻辑,然后把几个方法交给子类定制化完成,主要提供了缓存管理和事务管理的基本功能。
  • Servlet源码中HttpServlet的实现,service方法为模版方法,根据请求转发到需要子类实现的doPost,doGet等方法做下一步处理。
  • Spring源码中AbstractApplicationContext的实现,refresh方法,定义了bean初始化的流程,对于不同的子类例如注解形式或者xml形式会有不同的实现。
  • ...

总结

模版方法模式和策略模式很像,但是两者的适用场景不一样。模版方法模式抽象程度更高一些,把一些固定的东西也做了抽象,避免了代码重复等问题。灵活的使用钩子函数,可以有效控制没有必要的子类扩展。


参考


欢迎点赞,评论,分享,您的支持是我最大的动力,转发麻烦注明下出处~

猜你喜欢

转载自juejin.im/post/7042338836415774728