設(shè)計模式--Builder模式的思考

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

重疊構(gòu)造器模式

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

  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(...)
  }

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

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

工廠模式

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

 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模式

嚴(yán)格的JavaBean是只有空構(gòu)造函數(shù),其他屬性一律使用set方法,當(dāng)然必要參數(shù)可以放在構(gòu)造函數(shù)中,那么就變成下面的這種形式.

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

雖然少了冗長的參數(shù)列表,但是缺點(diǎn)也是很明顯:

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

Builder模式

有句話說得好,遇到難以解決的問題就加一層中間層來代理抽象.Builder模式正式如此,對象本身創(chuàng)建麻煩,那么就使用一個代理對象來主導(dǎo)創(chuàng)建與檢驗,兼顧了重疊器模式的安全性以及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對象選擇必要的參數(shù),然后最后build()構(gòu)建出自己想要的參數(shù).Builder有很多優(yōu)勢,也很靈活:

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

當(dāng)然缺點(diǎn)也有:

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

Mybatis中Builder模式應(yīng)用

Mybatis擁有種類繁多的配置,那么builder就很適合其配置類對象,以MappedStatement類為例子.
MappedStatement擁有數(shù)十項配置,如果使用構(gòu)造函數(shù)或者靜態(tài)工廠那么對于開發(fā)人員可能是難以接受的體驗.一大堆參數(shù),還需要點(diǎn)進(jìn)去才能知道每一個參數(shù)的意義,在這樣的情況下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的靜態(tài)內(nèi)部類,擁有可以訪問MappedStatement任意屬性的權(quán)利.那么其就可以直接實例化mappedStatement對象,然后使用該對象直接訪問屬性,從而簡化Builder模式,也很好的創(chuàng)建出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來控制,代碼中把其構(gòu)造函數(shù)聲明為包級別權(quán)限

  MappedStatement() {
    // constructor disabled
  }

總結(jié)

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

參考

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評論 19 139
  • 設(shè)計模式匯總 一、基礎(chǔ)知識 1. 設(shè)計模式概述 定義:設(shè)計模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,096評論 1 15
  • 1 場景問題# 1.1 繼續(xù)導(dǎo)出數(shù)據(jù)的應(yīng)用框架## 在討論工廠方法模式的時候,提到了一個導(dǎo)出數(shù)據(jù)的應(yīng)用框架。 對于...
    七寸知架構(gòu)閱讀 6,169評論 1 64
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,842評論 18 399
  • 《雨和文字》 清晨 頂著帆布包的背帶 跳到背后 看見母親開始為自己做飯 我不愛吃,你們吃…… 背著笨重的帆布包 回...
    木槳紙閱讀 250評論 0 0

友情鏈接更多精彩內(nèi)容