天天看點

設計模式之Builder建造者模式

在java開發中我們經常需要建立對象 ,我們常用的建立對象的方法有兩種 

1  使用構造器來建立對象 如果有多個可變參數 ,就需要寫多個構造方法,這種方法在遇到多個參數時不好控制

2. javabean的寫法,私有化成員變量, 私有構造方法 ,通過setter和getter來設定和擷取值 ,這種構造的缺點是傳入的參數不好檢測,例如有些非空的資料等

3.靜态工廠

現在我們介紹的builder模式建立的對象 适用于有多個可變參數和一定數量的限制參數的時候

建造者模式(Builder):将一個複雜對象的建構與它的表示分離,使得同樣的建構過程可以建立不同的表示。

使用場景:

1、當建立複雜對象的算法應該獨立于該對象的組成部分以及它們的裝配方式時。

2、當構造過程必須允許被構造的對象有不同的表示時。

使用範例:

//抽象産品類,使用了模闆方法模式,不同産品有不同的“組成部分part” 
abstract class AbstractProduct{ 
  protected abstract void part01(); 
  protected abstract void part02(); 
  protected abstract void part03(); 
    
  //模闆方法給出了預設的組裝方式,生成預設的産品 
  public final AbstractProduct defaultProduct() { 
    part01(); 
    part02(); 
    part03(); 
    return this;//傳回目前對象,即預設組裝方式的産品 
  } 
} 

//具體的産品A、B,不同産品實作了不同的“組成部分part” 
class ConcreteProductA extends AbstractProduct{ 
  protected void part01() { 
    System.out.println("産品A :part01() ..."); 
  } 
  protected void part02() { 
    System.out.println("産品A :part02() ..."); 
  } 
  protected void part03() { 
    System.out.println("産品A :part03() ..."); 
  } 
} 

class ConcreteProductB extends AbstractProduct{ 
  protected void part01() { 
    System.out.println("産品B :part01() ..."); 
  } 
  protected void part02() { 
    System.out.println("産品B :part02() ..."); 
  } 
  protected void part03() { 
    System.out.println("産品B :part03() ..."); 
  } 
} 

//抽象建造者,制定每一種産品應該實作的組合方式buildPart()和生産buildProduct()的标準 
abstract class AbstractBuilder{ 
  public abstract void buildPart(); 
  public abstract AbstractProduct buildProduct(); 
} 


/* 
* 具體建造者,如果對于預設産品(即當調用抽象産品中的defaultProduct()方法)不滿意時, 
* 可以不調用它來獲得産品,而是使用具體的建造者來改變産品的生産組裝方式,以得到不同的産品 
*/ 
class ConcreteBuilderA extends AbstractBuilder{ 
  private AbstractProduct productA = new ConcreteProductA(); 
    
  public void buildPart() { 
    this.productA.part03(); 
    this.productA.part02(); 
    this.productA.part01(); 
  } 
    
  public AbstractProduct buildProduct() { 
    return this.productA; 
  } 
} 

class ConcreteBuilderB extends AbstractBuilder{ 
  private AbstractProduct productB = new ConcreteProductB(); 
    
  public void buildPart() { 
    this.productB.part02(); 
    this.productB.part01(); 
    //特地省略掉産品B中的一個組成部分,例如該部分的功能顧客不需要 
//    this.productB.part03(); 
  } 
    
  public AbstractProduct buildProduct() { 
    return this.productB; 
  } 
} 

//導演類,預先持有各個産品的建造者,為需要不同于預設産品的使用者提供不同的組裝方式 
class Director{ 
  private AbstractBuilder builderA = new ConcreteBuilderA(); 
  private AbstractBuilder builderB = new ConcreteBuilderB();    
    
  public AbstractProduct getProductA() { 
    this.builderA.buildPart(); 
    return this.builderA.buildProduct(); 
  } 
    
  public AbstractProduct getProductB() { 
    this.builderB.buildPart(); 
    return this.builderB.buildProduct(); 
  } 
} 

//測試類 
public class Client { 
  public static void main(String[] args) { 
    System.out.println("利用模闆方法模式獲得預設的産品A"); 
    AbstractProduct defualtProductA = new ConcreteProductA().defaultProduct();    
     
    System.out.println("\n利用Director類獲得不同組裝方式的産品A"); 
    Director director = new Director(); 
    director.getProductA(); 
     
    System.out.println("\n利用Director類獲得不同組裝方式的産品B"); 
    director.getProductB(); 
  } 
}      

測試結果:

利用模闆方法模式獲得預設的産品A
産品A :part01() ...
産品A :part02() ...
産品A :part03() ...
利用Director類獲得不同組裝方式的産品A
産品A :part03() ...
産品A :part02() ...
産品A :part01() ...
利用Director類獲得不同組裝方式的産品B
産品B :part02() ...
産品B :part01() ...      

  與抽象工廠的差別:在建造者模式裡,有個指導者,由指導者來管理建造者,使用者是與指導者聯系的,指導者聯系建造者最後得到産品。即建造模式可以強制實行一種分步驟進行的建造過程。

  建造模式是将複雜的内部建立封裝在内部,對于外部調用的人來說,隻需要傳入建造者和建造工具,對于内部是如何建造成成品的,調用者無需關心。

  舉個簡單的例子,如汽車,有很多部件,車輪,方向盤,發動機還有各種小零件等等,部件很多,但遠不止這些,如何将這些部件裝配成一部汽車,這個裝配過程也很複雜(需要很好的組裝技術), builder模式就是為了将部件群組裝分開。

Builder模式好處和優點

使用Builder模式必然會導緻寫兩遍相關屬性的代碼和SETTER方法,看起來有點吃力不讨好。然而需要看到的是,用戶端代碼的可用性和可讀性得到了大大提高。與此同時,構造函數的參數數量明顯減少調用起來非常直覺。

Builder方法另外一個優勢在于,單個builder建構多個對象時Builder參數可在建立期間進行調整,還可以根據對象不同而進行改變。這就像我越來越推崇的以“不變”應“萬變”。Builder模式特别适合那些屬性個數很多的類,我認為沒有必要給那些本不需要設定值傳遞參數(設定null)。

Builder模式在提高代碼可讀性的同時,使用IDE提供的代碼補全功能也更加容易。Builder模式在與構造函數一起使用帶來的更大優勢在Josh Bloch的Effective Java第二版第2條中有詳細闡述。

Builder模式的代價和缺點

使用Builder模式是肯定會增加代碼量的。此外,盡管用戶端的代碼可讀性明顯改善,但随之而來的用戶端代碼變得更加冗長。我還是堅持這一觀點:相比較參數數量的增加,相同類型的參數混在一起,可選參數的增加而言,改善代碼可讀性更有價值。

Builder會增加個類代碼,這也意味着開發者在給類增加屬性時有時會忘記給該屬性添加支援的builder。為了克服這個問題,通常我會将builder嵌套到類中,這樣可以很容易地發現哪個相關的builder需要更新。盡管忘記的風險依舊存在,但是這風險就像忘記給類的新屬性增加toString()、 equals(Object)、 hashCode()或其他類基于是所有屬性的方法一樣。

在我的Builder實作中,我會用Builder的構造函數而不是set方法傳遞客戶需要的屬性。這樣做的好處在于,對象總是能被一次完整的執行個體化,而不是靠開發人員調用時用set方法補充額外的屬性完成執行個體化。這也展現了不可變性帶來的好處。然而,相應地也會造成自己設定的屬性方法可讀性降低。

正如它名字表示的那樣,Builder隻是一個替代構造器的選擇不能直接用于降低非構造函數方法的參數數量,但是結合參數對象的使用就能達到這一點。有關更多反對用Builder來進行對象建構的讨論可以參見 A dive into the Builder pattern上的相關評論。

繼續閱讀