日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

設(shè)計模式 --建造者模式

 新進(jìn)小設(shè)計 2021-07-13

前言

我們都知道設(shè)計模式分為創(chuàng)建型,結(jié)構(gòu)型和行為型。創(chuàng)建型有,單例模式,工廠模式,建造者模式和原型模式。

今天,我們再來學(xué)習(xí)另外一個比較常用的創(chuàng)建型設(shè)計模式,Builder 模式,中文翻譯為建造者模式或者構(gòu)建者模式,也有人叫它生成器模式。

很多博客總結(jié)的關(guān)于建造者模式的作用是:創(chuàng)建復(fù)雜對象的時候,用建造者模式可以使客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié)。 這雖然是一個很重要的特征,但是還有一個特征以及作用很多人都并不知道。我們接下來就來好好的探討一番。

建造者模式的作用

在平時的開發(fā)中,創(chuàng)建一個對象最常用的方式是,使用 new 關(guān)鍵字調(diào)用類的構(gòu)造函數(shù)來完成。雖然這是最簡單最常用的,但是我們仔細(xì)想一想真的是所有場景都適用嗎?

場景分析

  有如下的一個場景:
  有一個工廠類,這個工廠類有如下幾個成員變量:
      1. 工廠名字
      2. 工廠員工列表
      3. 工廠設(shè)備列表

要想讓這個工廠正常的運(yùn)行,這3個成員變量必須被正確賦值。那么此時,我們最常見的方法就是在構(gòu)造方法中實(shí)現(xiàn)這些成員變量的賦值。

當(dāng)成員變量不多的時候,像上訴說的3個,這樣并沒有什么問題,但是當(dāng)成員變量變成6個,12個甚至更多,那繼續(xù)沿用現(xiàn)在的設(shè)計思路,構(gòu)造函數(shù)的參數(shù)列表會變得很長,代碼在可讀性和易用性上都會變差。在使用構(gòu)造函數(shù)的時候,我們就容易搞錯各參數(shù)的順序,傳遞進(jìn)錯誤的參數(shù)值,導(dǎo)致非常隱蔽的 bug。

解決辦法

解決這個問題的辦法你應(yīng)該也已經(jīng)想到了,那就是用 set() 函數(shù)來給成員變量賦值,以替代冗長的構(gòu)造函數(shù)。我們將必填的成員變量,放在我們的構(gòu)造方法中,強(qiáng)制創(chuàng)建類對象的時候就要填寫。將其他不是必填的成員變量我們通過 set() 函數(shù)來設(shè)置,讓使用者自主選擇填寫或者不填寫。

這樣代碼在可讀性和易用性上提高了很多。

引入構(gòu)造模式

當(dāng)我們的如下幾個需求的時候,上訴使用set()函數(shù)的設(shè)計思路可能就不太滿足了

  1. 我們剛剛說的,將必填的成員變量放到構(gòu)造方法。如果必填項很多,那構(gòu)造方法又會出現(xiàn)我們之前說的那個問題。但是,如果我們把必填項也通過 set() 方法設(shè)置,那校驗(yàn)這些必填項是否已經(jīng)填寫的邏輯就無處安放了。
  2. 除此之外,假設(shè)配置項之間有一定的依賴關(guān)系,比如,如果用戶設(shè)置了員工列表, 就必須顯式地設(shè)置員工工資;或者配置項之間有一定的約束條件,比如,員工和工資必須一一對應(yīng)。如果我們繼續(xù)使用現(xiàn)在的設(shè)計思路,那這些配置項之間的依賴關(guān)系或者約束條件的校驗(yàn)邏輯就無處安放了。
  3. 如果我們希望這個工廠類對象某些屬性如name是不可變的,也就是說,對象在創(chuàng)建好之后,就不能再修改這些內(nèi)部的屬性值。要實(shí)現(xiàn)這個功能,我們就不能在外部暴露 set() 方法。

為了解決這些問題,建造者模式就派上用場了。

使用建造者來實(shí)現(xiàn)我們的需求

我們可以把校驗(yàn)邏輯放置到 Builder 類中,先創(chuàng)建建造者,并且通過 set() 方法設(shè)置建造者的變量值,然后在使用 build() 方法真正創(chuàng)建對象之前,做集中的校驗(yàn),校驗(yàn)通過之后才會創(chuàng)建對象。除此之外,我們把 Factory 的構(gòu)造函數(shù)改為 private 私有權(quán)限。這樣我們就只能通過建造者來創(chuàng)建 Factory 類對象。并且,F(xiàn)actory 沒有為不可變屬性提供任何 set() 方法,這樣我們創(chuàng)建出來的對象就做到了相對不可。代碼如下:


public class Factory {
  private String factoryName;  //工廠名字
  private List<Integer> employeeIds; //員工列表
  private Map<Integer,Integer> salaryMap; //員工工資
  private List<String> equipmentName; //設(shè)備列表

  //私有構(gòu)造方法
  private Factory(){}
  private Factory(Builder builder) {
    this.factoryName = builder.factoryName;
    this.employeeIds = builder.employeeIds;
    this.salaryMap = builder.salaryMap;
    this.equipmentName = builder.equipmentName;
  }
  //...省略getter方法...

  //我們將Builder類設(shè)計成了Factory的內(nèi)部類。
  //我們也可以將Builder類設(shè)計成獨(dú)立的非內(nèi)部類FactoryBuilder。
  public static class Builder {
    private String factoryName;  
    private List<Integer> employeeIds; 
    private Map<Integer,Integer> salaryMap; 
    private List<String> equipmentName; 

    public Factory build() {
      // 校驗(yàn)邏輯放到這里來做,包括必填項校驗(yàn)、依賴關(guān)系校驗(yàn)、約束條件校驗(yàn)等
      if (StringUtils.isBlank(factoryName)) {
        throw new IllegalArgumentException("...");
      }
      
      for(Integer employeeId : employeeIds){
        if (salaryMap.get(employeeId) == null) {
            throw new IllegalArgumentException("...");
        }
      }
      return new ResourcePoolConfig(this);
    }

    public Builder setFactoryName(String factoryName) {
      if (StringUtils.isBlank(factoryName)) {
        throw new IllegalArgumentException("...");
      }
      this.factoryName = factoryName;
      return this;
    }

    public Builder setEmployeeIds(List<Integer> employeeIds) {
      // 員工不能為0
      if (employeeIds.size() == 0) {
        throw new IllegalArgumentException("...");
      }
      this.employeeIds = employeeIds;
      return this;
    }

    public Builder setSalaryMap(Map<Integer,Integer> salaryMap) {
      if (salaryMap.size() == 0) {
        throw new IllegalArgumentException("...");
      }
      this.salaryMap = salaryMap;
      return this;
    }

    public Builder setEquipmentName(List<String> equipmentName) {
      // 設(shè)備必須大于兩個
      if (equipmentName.siez() < 2) {
        throw new IllegalArgumentException("...");
      }
      this.minIdle = minIdle;
      return this;
    }
  }
}

// 這段代碼會拋出IllegalArgumentException,因?yàn)樵O(shè)備只有一臺
List<Integer> employeeIds =  Collections.singletonList(1);
Map<Integer, Integer> salaryMap = Collections.singletonMap(1, 9000);
List<String> equipmentName =  Collections.singletonList("新型設(shè)備");
Factory factory = new Factory.Builder()
        .setFactoryName("萬能工廠")
        .setEmployeeIds(employeeIds)
        .setSalaryMap(salaryMap)
        .setEquipmentName(equipmentName)
        .build();

總結(jié)

我們來與工廠模式做一個對比,建造者模式是讓建造者類來負(fù)責(zé)對象的創(chuàng)建工作。工廠模式,是由工廠類來負(fù)責(zé)對象創(chuàng)建的工作。

那它們之間有什么區(qū)別呢?實(shí)際上,工廠模式是用來創(chuàng)建不同但是相關(guān)類型的對象(繼承同一父類或者接口的一組子類),由給定的參數(shù)來決定創(chuàng)建哪種類型的對象。

建造者模式是用來創(chuàng)建一種類型的復(fù)雜對象,通過設(shè)置不同的可選參數(shù),“定制化”地創(chuàng)建不同的對象。網(wǎng)上有一個經(jīng)典的例子很好地解釋了兩者的區(qū)別。

顧客走進(jìn)一家餐館點(diǎn)餐,我們利用工廠模式,根據(jù)用戶不同的選擇,來制作不同的食物,比如披薩、漢堡、沙拉。對于披薩來說,用戶又有各種配料可以定制,比如奶酪、西紅柿、起司,我們通過建造者模式根據(jù)用戶選擇的不同配料來制作披薩。

實(shí)際上,我們也不要太學(xué)院派,非得把工廠模式、建造者模式分得那么清楚,我們需要知道的是,每個模式為什么這么設(shè)計,能解決什么問題。只有了解了這些最本質(zhì)的東西,我們才能不生搬硬套,才能靈活應(yīng)用,甚至可以混用各種模式創(chuàng)造出新的模式,來解決特定場景的問題。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多