設計模式之模板方法模式:實現可擴展性設計(Java示例)

概述

在實際開發中,經常會遇到一項基本功能須要支撐不一樣業務的狀況。好比訂單發貨,有普通的整包發貨,有分銷單的發貨,採購單的發貨,有多商品的整包或拆包發貨等。要想支持這些業務的發貨,顯然不能在一個通用流程裏用一堆的 if-else 來應對。java

遵循「開閉」原則,咱們應當儘可能提供一個可擴展的設計,容許新的業務來覆寫部分方法來實現定製的發貨。「開閉原則」意味着,咱們老是在原有基礎上新增方法,而不是改動原有方法。這能夠作到最小化影響。express

使用模板方法設計模式,正是一種應對和加強系統可擴展性的方法。 定義好通用流程, 並設置一系列鉤子方法, 而具體業務只要覆寫部分鉤子方法便可實現本身的需求。下面給出一個簡化版的發貨可擴展性實現。設計模式

代碼示例

定義發貨接口

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

定義默認發貨實現

默認發貨實現是針對普通商品。採用抽象類來實現。 普通發貨要檢測訂單商品是不是分銷的,這裏簡便起見用訂單號代替。ide

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

}

普通發貨實現

普通發貨實現直接繼承抽象類,不覆寫任何方法。post

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

}

採購單的發貨

採購單的發貨須要推送消息,同步分銷單的發貨。this

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 容器類的實現發揮的淋漓盡致。 接口定義行爲,抽象類定義默認實現, 而具體類經過覆寫某些方法實現定製化功能。code

相關文章
相關標籤/搜索