设计模式 —— 模板方法模式

944 阅读4分钟

简介

模板方法模式(Template Method Pattern)定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。

模板方法中的方法可以分为两大类:模板方法和基本方法。

模板方法

一个模板方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。 一个抽象类可以有任意多个模板方法,而不限于一个。每一个模板方法都可以调用任意多个具体方法。

基本方法

基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。

  1. 抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在Java语言里抽象方法以abstract关键字标示;
  2. 具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或置换;
  3. 钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现是一个空实现,作为方法的默认实现。

实例

对接过第三方支付的小伙伴都知道,在接入第三方支付时,一般支付结果都会通过异步回调的形式,通知商户服务器。而我们得到这些数据时一般都是同一流程的处理方式:验签 — 更新订单状态 — 给第三方支付服务器响应。 下面以支付宝和微信为例:支付宝和微信的验签方式和响应结果方式都是不一样的, 而更新订单状态都是商户这边处理所以业务逻辑是一样的。

抽象模板类:

public abstract class AbstractPayNotifyTemplate {

    /**
     * 支付异步回调处理
     *
     * @param params 回调参数
     */
    public void onNotify(Map<String, String> params) {
        //验证签名
        final boolean sign = verifySign(params);
        if (sign) {
            // 给第三方支付服务器回复支付失败状态
            setResponse(PayStatus.ERROR);
            return;
        }
        //从参数获取订单编号并更新订单支付状态,为支付成功
        final String orderSn = params.get("out_trade_no");
        updateOrderPayStatusSuccess(orderSn);
        // 给第三方支付服务器回复支付成功状态
        setResponse(PayStatus.SUCCESS);
    }

    /**
     * 验签
     *
     * @param params 回调参数
     * @return 验签结果
     */
    protected abstract boolean verifySign(Map<String, String> params);

    /**
     * 更新订单支付状态为支付成功
     *
     * @param orderSn 订单编号
     */
    private void updateOrderPayStatusSuccess(String orderSn) {
        // 根据订单编号更新订单支付状态为支付成功
    }

    /**
     * 给第三方支付返回
     *
     * @param status 支付状态
     */
    protected abstract void setResponse(PayStatus status);
}

具体模板类:

// 支付宝支付回调类
public class AliPayNotifyTemplate extends AbstractPayNotifyTemplate {

    @Override
    protected boolean verifySign(Map<String, String> params) {
        // 调用支付宝验签接口, 并返回验签结果
        return true;
    }

    @Override
    protected void setResponse(PayStatus status) {
        String res = Objects.equals(PayStatus.SUCCESS, status) ? "success" : "error";

        // 调用 ResponseUtils 直接返回 res 字符串
    }
}

// 微信支付回调类
public class WxPayNotifyTemplate extends AbstractPayNotifyTemplate {

    @Override
    protected boolean verifySign(Map<String, String> params) {
        // 调用微信支付验签接口, 并返回验签结果
        return true;
    }

    @Override
    protected void setResponse(PayStatus status) {
        String returnCode = "FAIL";
        String returnMsg = "";
        if (Objects.equals(PayStatus.SUCCESS, status)) {
            returnCode = "SUCCESS";
            returnMsg = "OK";
        }
        String res = String.format("<xml><return_code><![CDATA[%s]]></return_code><return_msg><![CDATA[%s]]></return_msg></xml>", returnCode, returnMsg);

        // 调用 ResponseUtils 返回 res xml格式内容
    }
}

当然这只是简单例子,帮助理解模板方法模式。实际项目中还带有各种参数、异常的处理、工具类的封装,以及其他设计模式的应用。

优点

  1. 提高代码复用性。将相同部分的代码放在抽象的父类中;

  2. 提高了拓展性。将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为;

  3. 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。(此处实例没有体现)

缺点

引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。

适用场景

  1. 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现;
  2. 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复;
  3. 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。