標籤:java 設計模式 建造者
一、動機與定義
建立對象時,我們知道可以使用工廠方式來建立,使調用者和具體實現解耦,但是有一種情況,當要建立的多個對象之間重複性較大,只有建立步驟、組裝順序或者內部構件不同時,原廠模式就需要進一步的演化了,如我們去KFC,有很多種套餐,比如套餐1(薯條+可樂+漢堡),套餐2(雞肉卷+薯條+可樂),這個套餐就是我們要擷取的複雜物件,那麼程式如何建立出這種對象呢。
我們看到套餐的內容很多是一樣的,那麼我們是不是可以考慮將建立單個食品(如雞肉卷、可樂等)方法提取出來,使用單獨一個類協調這些食品的組合比較好呢,如下,具體食品建立者:
//食物的抽象建立者public abstract class FoodBuilder { // 套餐 protected SetMealProduct sm = new SetMealProduct(); // 建立漢堡 public abstract void buildHamburg(); // 建立薯條 public abstract void buildChips(); // 建立可樂 public abstract void buildCola(); // 建立雞肉卷 public abstract void buildChickenRolls(); public SetMealProduct getResult() { return sm ; }} 具體食品建立者:
//具體食品建立者:KFC//這樣未來還可以支援麥當勞public class KFCFoodBuilder extends FoodBuilder { @Override public void buildHamburg() { sm.getFoodList().add( "漢堡"); } @Override public void buildChips() { sm.getFoodList().add( "薯條"); } @Override public void buildCola() { sm.getFoodList().add( "可樂"); } @Override public void buildChickenRolls() { sm.getFoodList().add( "雞肉卷" ); }} 套餐產品類:
//套餐public class SetMealProduct { // 套餐名稱 private String name; // 套餐中的食品集合 private List<String> foodList = new ArrayList<String>(); public List<String> getFoodList() { return foodList ; } public void setFoodList(List<String> foodList) { this.foodList = foodList; } public String getName() { return name ; } public void setName(String name) { this.name = name; }} 負責產生套餐,組裝順序的類:
//套餐一(薯條+可樂+漢堡)class FirstDirector { private FoodBuilder fb; public FirstDirector(FoodBuilder fb) { this.fb = fb; } public SetMealProduct construct() { fb.buildChips(); // 薯條 fb.buildHamburg(); // 漢堡 fb.buildCola(); // 可樂 return fb .getResult(); }}// 套餐二(雞肉卷+薯條+可樂)class SecondDirector { private FoodBuilder fb; public SecondDirector(FoodBuilder fb) { this.fb = fb; } public SetMealProduct construct() { fb.buildChickenRolls(); // 雞肉卷 fb.buildChips(); // 薯條 fb.buildCola(); // 可樂 return fb .getResult(); }} 調用者:
FirstDirector fDirector = new FirstDirector( new KFCFoodBuilder()); SecondDirector sDirector = new SecondDirector( new KFCFoodBuilder()); fDirector.construct(); sDirector.construct();
這樣的話,調用者不需要關心底層如何建立,如何組裝的,直接獲得就行了,其實這就是建立者模式的思想。
二、結構與類圖
建造者模式主要用於分步驟的建立一些複雜的對象,將這些步驟一個個的獨立起來,使用一個指揮者來控制這些步驟的順序。
建造者模式有4個角色: 1、Builder:抽象建造者,定義了產品的各個組件的建立方法,具體如何構建由子類實現; 2、ConcreteBuilder:具體建造者,實現了各個組件的建立,返回一個建立好的對象; 3、Director:指揮者,也叫導演者,負責各個組件的組裝、步驟的執行順序,調用Builder建立具體產品,它是建造者模式的核心,有兩個職責,1個是隔離了使用者與產品的產生過程,另一個負責產品的生產過程; 4、Product:具體產品,由各個組件構成的複雜產品。
三、適用情境及效果(優缺點)
建造者模式核心是建立者和指揮者,建立者負責具體建立的組件、零件或步驟,指揮者負責步驟順序,組件、零件組裝順序,多少等。所以以下情境可以考慮使用建造者模式: 1、產品類非常複雜,產品類的組成組件或每個具體建立的步驟比較固定,需要根據不同的組裝方法或執行順序產生不同的產品結果時,可以考慮使用建造者模式,如開篇的KFC的例子,組件(可樂、雞肉卷等)穩定,組裝方法不穩定,產生不通結果(套餐);有的時候,這種情況也可以考慮使用模版方法,模版方法更簡單一些,但沒有建造者這麼靈活; 2、產品類非常複雜,產品類組件組裝方法或建立步驟比較固定,需要根據不同的組件或步驟,產生不同的產品時,即需要用相同的建立過程建立不同的產品時,舉個常見的例子,生活中我們自己組裝電腦,組裝電腦的方法基本固定,將cpu,硬碟,記憶體,顯卡等等插到主板上,串連顯示器滑鼠鍵盤,開機,這個步驟就是指揮者乾的事,但是具體的cpu是intel還是AMD的,記憶體是金士頓還是三星的等等是不固定,提供cpu,記憶體,硬碟等組件就是建立者乾的事; 3、產品類非常複雜,具體組件和步驟,還有組裝方法,順序都經常變化,也可以使用這個模式,不過此時最好和其他模式一起來用,比如和模版模式一起使用; 4、產生的產品對象的屬性相互依賴,需要指定執行順序時,可以考慮; 使用後的效果(優點): 1、封裝、解耦,這個是建立型設計模式基本的作用,封裝底層,調用者不需要知道如何複雜的建立,只需要直接使用即可。 2、擴充方便,可以很方便的增加具體的建造者,使用者使用不同建造者就能得到不同的產品。 3、產品組裝過程、執行順序和具體組件、步驟分離,非常靈活,很容易擴充出新的組裝方法和執行順序。 4、建造者模式可以對複雜產品的建立過程進行精細化控制,比起其他建立型模式能更好的反應產品的建立過程,每個建造者的具體步驟都是獨立的,因此可以逐步細化,而不對其他模組產生影響。 缺點:如果產品內部變化複雜,可能會導致需要定義很多具體建造者(Builder)和指揮者(Director),導致系統變得非常龐大。 建造者模式一般建立的產品都具有較多的共同點,組成部分也相似,如果產品之間差異較大,則不適合使用建造者模式。
五、模式擴充
1、簡化模式,如果變化在於指揮者,組裝步驟和執行順序,建造者只有1個,那麼可以省略抽象建造者。再簡單的,如果組裝方法和步驟穩定,可以省略指揮者,直接將步驟合并到建造者中。 2、你會發現,建造者模式和原廠模式非常相似,其實他倆還是有區別的,建造者主要功能是對組件的組裝,步驟的執行順序進行控制,也就是組件已經定了,我來控制組裝方法,而原廠模式的側重點是在於建立,如何建立具體的組件,至於怎麼組裝不是它關心的。可以參考上面提到的組裝電腦的例子,建造者模式重點在於如何組裝,而原廠模式在於如何建立cpu,建立記憶體等。 原廠模式更像一種思想,將不確定的,易變的東西延遲到子類去實現(或者說留給以後實現),達到某種程度的解耦。抽象工廠也是介面,但它不是因為不確定,而是產品等級,產品族太多,進行了一層封裝而已。 建造者模式不是建造模式,有人說改名“導演模式”更能反應它的本質,兩者都是一種封裝,建造者是過程性的封裝,工廠是結構性的封裝。 3、建造者模式經常和其他模式混合使用,比如和模版模式混合使用,建造者模式中有一個角色被忽略了,就是組件,組件的建立很多時候使用模版模式就非常合適。還有這些組件的建立使用了原廠模式、原型模式等,甚至有時候結合單例來控制類執行個體的產生,不論用什麼模式,不論怎麼組合,最終的目的一定是能給系統帶來好處,而不是臃腫的結構,複雜的代碼。 4、靈活配置建立順序,我們可以把建造者模式中的指揮者角色配置到設定檔中,定義好統一標準,讀取設定檔來執行建立者的各個方法,這樣後續修改指揮者的執行順序就非常方便了。其實很多模式都可以把步驟提取到設定檔中,雖然靈活,但是代碼複雜了,設定檔管理不善也是個問題,這也是一個取捨。 5、建造者模式真的只能建立對象嗎?前面我們一直說建造者模式控制組件組裝,步驟的執行順序都是為了建立對象,其實還有更深一個層次,控制步驟的執行順序不僅僅是建立對象,還有初始化對象工作,甚至對象的執行步驟工作,比如我們使用建造者模式建立一個文檔對象,完全可以擷取一個已經開啟的文檔,未開啟的文檔,已經初始化一些內容的文檔等等。
小結,建造者模式其實就是將複雜物件的建立分解成很多精細的小步驟或小組件,然後使用一個指揮者的類來控制這些步驟的執行或組件的組裝,從而達到解耦合,易擴充的目的,調用者只需要使用指揮類和建造類,從而達到了對底層對象建立的封裝目的。因為建造者是抽象類別,不是具體實現,後續擴充也不影響現有代碼,符合了開閉原則。