设计模式之模板方法模式:实现可扩展性设计(Java示例)

概述

在实际开发中,常常会遇到一项基本功能需要支撑不同业务的情况。比如订单发货,有普通的整包发货,有分销单的发货,采购单的发货,有多商品的整包或拆包发货等。要想支持这些业务的发货,显然不能在一个通用流程里用一堆的 if-else 来应对。

遵循“开闭”原则,我们应当尽量提供一个可扩展的设计,允许新的业务来覆写部分方法来实现定制的发货。“开闭原则”意味着,我们总是在原有基础上新增方法,而不是改动原有方法。这可以做到最小化影响。

使用模板方法设计模式,正是一种应对和增强系统可扩展性的方法。 定义好通用流程, 并设置一系列钩子方法, 而具体业务只要覆写部分钩子方法即可实现自己的需求。下面给出一个简化版的发货可扩展性实现。

代码示例

定义发货接口

package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public interface Express {

  /**
   * 通用发货接口
   * @param expressParam 发货参数
   * @return 发货包裹ID
   */
  int postExpress(ExpressParam expressParam);
}
package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public class ExpressParam {

  private String orderNo;  // 订单编号
  private String exId;     // 发货公司ID
  private String exNo;     // 发货单号

  public ExpressParam(String orderNo, String exId, String exNo) {
    this.orderNo = orderNo;
    this.exId = exId;
    this.exNo = exNo;
  }

  public String getOrderNo() {
    return orderNo;
  }

  public void setOrderNo(String orderNo) {
    this.orderNo = orderNo;
  }

  public String getExId() {
    return exId;
  }

  public void setExId(String exId) {
    this.exId = exId;
  }

  public String getExNo() {
    return exNo;
  }

  public void setExNo(String exNo) {
    this.exNo = exNo;
  }

  @Override
  public String toString() {
    return "ExpressParam{" +
           "orderNo='" + orderNo + '\'' +
           ", exId='" + exId + '\'' +
           ", exNo='" + exNo + '\'' +
           '}';
  }
}
package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public class Order {

  private String orderNo;
  private Integer orderType;

  public Order(String orderNo, Integer orderType) {
    this.orderNo = orderNo;
    this.orderType = orderType;
  }

  public String getOrderNo() {
    return orderNo;
  }

  public void setOrderNo(String orderNo) {
    this.orderNo = orderNo;
  }

  public Integer getOrderType() {
    return orderType;
  }

  public void setOrderType(Integer orderType) {
    this.orderType = orderType;
  }
}

定义默认发货实现

默认发货实现是针对普通商品。采用抽象类来实现。 普通发货要检测订单商品是否是分销的,这里简便起见用订单号代替。

package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 * Provide a default implementation of Express
 */
public abstract class AbstractExpress implements Express {

  public int postExpress(ExpressParam expressParam) {
    checkExpressParam(expressParam);
    Order order = getOrder(expressParam.getOrderNo());
    checkOrder(order);
    return execute(order, expressParam);
  }

  protected void checkExpressParam(ExpressParam expressParam) {
    // basic express param check, probably not be overriden
  }

  protected void checkOrder(Order order) {
    // check if order can express. may be overriden
    if (Integer.valueOf(5).equals(order.getOrderType()) || order.getOrderNo().startsWith("F")) {
      throw new IllegalArgumentException("Fenxiao order can not be expressed by own");
    }
  }

  protected Order getOrder(String orderNo) {
    // here is just for creating order , probably not overriden
    return new Order(orderNo, 0);
  }

  /**
   * 发货的默认实现
   * @param order  订单信息
   * @param expressParam  发货参数
   * @return 发货包裹ID
   *
   * Note: Suggest this method be overriden !
   */
  protected int execute(Order order, ExpressParam expressParam) {
    System.out.println("success express for normal order: " + expressParam);
    return 1;
  }

}

普通发货实现

普通发货实现直接继承抽象类,不覆写任何方法。

package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public class NormalExpress extends AbstractExpress {
}

分销发货

分销发货要放过分销商品的检测。因此要覆写 checkOrder 方法。此外,也会覆写 execute 方法。

package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public class FenxiaoExpress extends AbstractExpress {

  public Order getOrder(String orderNo) {
    return new Order(orderNo, 5);
  }

  protected void checkOrder(Order order) {
    // let order check pass
  }

  protected int execute(Order order, ExpressParam expressParam) {
    System.out.println("success express for fenxiao order: " + expressParam);
    return 1;
  }

}

采购单的发货

采购单的发货需要推送消息,同步分销单的发货。

package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public class CaigouExpress extends AbstractExpress {

  protected int execute(Order order, ExpressParam expressParam) {
    pushMessage(order, expressParam);
    System.out.println("success express for caigou order: " + expressParam);
    return 1;
  }

  private void pushMessage(Order order, ExpressParam expressParam) {
    System.out.println("push message to trigger fenxiao order to express");
  }

}

客户端使用

package zzz.study.patterns.templateMethod.express;

/**
 * Created by shuqin on 17/4/6.
 */
public class Client {

  public static void main(String[] args) {
    ExpressParam expressParam = new ExpressParam("201704062033113366", "1", "666888");
    Express normal = new NormalExpress();
    normal.postExpress(expressParam);

    try {
      ExpressParam expressParamInvalid = new ExpressParam("F201704062033123456", "1", "666888");
      normal.postExpress(expressParamInvalid);
    } catch (Exception ex) {
      String exInfo = String.format("Failed to post express for %s , Reason: %s", expressParam, ex.getMessage());
      System.err.println(exInfo);
    }

    Express fenxiao = new FenxiaoExpress();
    ExpressParam fenxiaoExpressParam = new ExpressParam("F201704062033123456", "1", "666888");
    fenxiao.postExpress(fenxiaoExpressParam);

    Express caigou = new CaigouExpress();
    ExpressParam caigouExpressParam = new ExpressParam("201704062033113366", "1", "666888");
    caigou.postExpress(caigouExpressParam);

  }

}

小结

通过模板方法模式,比较优雅地将通用流程及逻辑与定制的部分分离, 新的业务只要覆写相应方法,就可以完成自己的需求,而无需改动核心流程代码。模板方法模式的不足在于:在实际业务中可能对 AbstractExpress 拆分出新的更细的可覆写的业务方法,这会导致各个业务的整体发货逻辑理解起来不够直观。同时,当在 AbstractExpress 中拆分中新的方法时, 需要回归测试来保障原有发货不受影响。

实际上,这种实现在 JDK 容器类的实现发挥的淋漓尽致。 接口定义行为,抽象类定义默认实现, 而具体类通过覆写某些方法实现定制化功能。

posted @ 2017-04-06 21:24  琴水玉  阅读(2607)  评论(0编辑  收藏  举报