設計模式--Builder模式的思考

在日常開發中總是會遇到多參數的情況,那么對于多參數,尤其是可選參數眾多的情況,可能有如下的一些解決方案.

重疊構造器模式

重疊構造器模式在Java代碼中很常見,其解決的問題是參數過多情況下又不想給調用方帶來過多的實例化對象負擔.在這種情況下調用方只需要選擇一個適合自己的構造函數調用就好.

  public Configuration(Integer maxConnect) {
    this(maxConnect, 0);
  }

  public Configuration(Integer maxConnect, Integer minConnect) {
    this("default", maxConnect, minConnect);
  }

  public Configuration(String password, Integer maxConnect, Integer minConnect) {
    this(...)
  }

然而事實總不是那么如人所愿,這個模式有很多缺點.

  1. 層層嵌套,導致整個實例化過程其實是一條直線,一通到底,也就注定了其過程不夠靈活.
  2. 對于參數較少的構造函數不得不弄一堆的默認值填充,導致其看起來不是很優雅.
  3. 增加參數對于這種模式無疑是困難重重,需要從底到上一層一層修改.

工廠模式

工廠模式本意在于封裝具體的創建流程,提供出簡單便捷的入口,但是在多參數情況下其能改進的只是讓實例化過程不再是一條直線,工廠中可以根據具體參數制造出Configuration及其子類.其本質與重疊構造器模式并沒有太大的區別,只是把構造器邏輯提取到相應工廠,所以工廠模式并不能解決上述問題.

 public static Configuration newInstance(String password) {
    return new Configuration("default", password, "default");
  }

  public static Configuration newInstance(String password, String username) {
    return new Configuration(username, password, "default");
  }

  public static Configuration newInstance(String password, String username, String url,) {
    return new Configuration(username, password, url);
  }

JavaBean模式

嚴格的JavaBean是只有空構造函數,其他屬性一律使用set方法,當然必要參數可以放在構造函數中,那么就變成下面的這種形式.

    Configuration configuration = new Configuration();
    configuration.setPassword("default");
    configuration.setUrl("http://mrdear.cn");
    configuration.setUsername("default");

雖然少了冗長的參數列表,但是缺點也是很明顯:

  1. 對象的創建過程被分解,按照意圖,new的過程就是創建,剩下的一律不算創建,但這種模式下的創建實際上是兩步,創建與填值.
  2. 對修改開放,該模式暴露了過多set方法,使得任意能獲取到該實例的地方都可以隨意修改器內容,對于全局性的config實例或者其他單例實例這是致命的缺點.

Builder模式

有句話說得好,遇到難以解決的問題就加一層中間層來代理抽象.Builder模式正式如此,對象本身創建麻煩,那么就使用一個代理對象來主導創建與檢驗,兼顧了重疊器模式的安全性以及JavaBean模式的靈活性.

public class Configuration {
  private String username;
  private String password;
  private String url;

  public Configuration(String username, String password, String url) {
    this.username = username;
    this.password = password;
    this.url = url;
  }

  public static ConfigurationBuilder builder() {
    return new ConfigurationBuilder();
  }

  public static class ConfigurationBuilder {
    private String username;
    private String password;
    private String url;

    ConfigurationBuilder() {
    }

    public ConfigurationBuilder username(String username) {
      this.username = username;
      return this;
    }

    public ConfigurationBuilder password(String password) {
      this.password = password;
      return this;
    }

    public ConfigurationBuilder url(String url) {
      this.url = url;
      return this;
    }

    public Configuration build() {
      // can some check
      return new Configuration(this.username, this.password, this.url);
    }

    public String toString() {
      return new StringBuilder().append((String)"Configuration.ConfigurationBuilder(username=").append((String)this.username).append((String)", password=").append((String)this.password).append((String)", url=").append((String)this.url).append((String)")").toString();
    }
  }
}

如上面代碼,客戶端使用Builder對象選擇必要的參數,然后最后build()構建出自己想要的參數.Builder有很多優勢,也很靈活:

  1. 把線性的構造結構用build方法變成了分支結構,你可以使用build構造該類的子類以及其他相關類.
  2. 很靈活,組合的形式可以在各自builder加強約束校驗,并且這些業務邏輯不會在污染你的原類.當不符合的參數應及時拋出IllegalArgumentException
  3. 可作為參數傳遞,比如Mybatis中就大量使用了這種傳遞方式讓客戶端更加方便的構造配置類.
  4. 使用.filed()形式構建參數,只要命名有一定規范,就很清楚參數的作用,編寫出來的代碼也更加容易閱讀,不用點進去看具體參數來選擇適合自己的方法了.

當然缺點也有:

  1. 構造想要的類之前必須構造一個builder中間類,對于一些經常循環中實例化的類是很不適合的.大量對象被重復創建會帶來性能上的影響.因此對于一些復雜的配置類使用builder時最合適不過的了.

Mybatis中Builder模式應用

Mybatis擁有種類繁多的配置,那么builder就很適合其配置類對象,以MappedStatement類為例子.
MappedStatement擁有數十項配置,如果使用構造函數或者靜態工廠那么對于開發人員可能是難以接受的體驗.一大堆參數,還需要點進去才能知道每一個參數的意義,在這樣的情況下Builder模式就是一個很好的解決方式.

public final class MappedStatement {

  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
    ......
}

org.apache.ibatis.mapping.MappedStatement.Builder作為MappedStatement的靜態內部類,擁有可以訪問MappedStatement任意屬性的權利.那么其就可以直接實例化mappedStatement對象,然后使用該對象直接訪問屬性,從而簡化Builder模式,也很好的創建出MappedStatement的實例.

  public static class Builder {
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
      mappedStatement.resultMaps = new ArrayList<ResultMap>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }

    public Builder resource(String resource) {
      mappedStatement.resource = resource;
      return this;
    }
    ......
}

另外為了保證MappedStatement對象必須使用Builder來控制,代碼中把其構造函數聲明為包級別權限

  MappedStatement() {
    // constructor disabled
  }

總結

Builder模式本質上是一種特殊的工廠模式,按照流水線方式調用,然后最后檢查產品是否合格,流水線之間可以任意組合,達到了高度的靈活性.

參考

Effective Java : 遇到多個構造器參數時考慮構建器(Builder模式)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,869評論 18 139
  • 設計模式匯總 一、基礎知識 1. 設計模式概述 定義:設計模式(Design Pattern)是一套被反復使用、多...
    MinoyJet閱讀 3,970評論 1 15
  • 1 場景問題# 1.1 繼續導出數據的應用框架## 在討論工廠方法模式的時候,提到了一個導出數據的應用框架。 對于...
    七寸知架構閱讀 5,847評論 1 64
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,754評論 18 399
  • 《雨和文字》 清晨 頂著帆布包的背帶 跳到背后 看見母親開始為自己做飯 我不愛吃,你們吃…… 背著笨重的帆布包 回...
    木槳紙閱讀 233評論 0 0