設計模式-建造者模式-Java
目錄
文章目錄
內容
沒有人買車會只買一個輪胎或者方向盤,大家買的都是一輛包含輪胎、方向盤和發動機等多個部件的完整汽車。如何將這些部件組裝成一輛完整的汽車並返回給用戶,這是建造者模式
需要解決的問題。建造者模式又稱爲生成器模式,它是一種較爲複雜、使用頻率也相對較低的創建型模式。建造者模式爲客戶端返回的不是一個簡單的產品,而是一個由多個部件組成的複雜產品。
1、示例案例-遊戲角色的設計
Sunny軟件公司遊戲開發小組決定開發一款名爲《Sunny羣俠傳》的網絡遊戲,該遊戲採用主流的RPG(Role Playing Game,角色扮演遊戲)模式,玩家可以在遊戲中扮演虛擬世界中的一個特定角色,角色根據不同的遊戲情節和統計數據(如力量、魔法、技能等)具有不同的能力,角色也會隨着不斷升級而擁有更加強大的能力。
作爲RPG遊戲的一個重要組成部分,需要對遊戲角色進行設計,而且隨着該遊戲的升級將不斷增加新的角色。不同類型的遊戲角色,其性別、臉型、服裝、髮型等外部特性都有所差
異,例如“天使”擁有美麗的面容和披肩的長髮,並身穿一襲白裙;而“惡魔”極其醜陋,留着光頭並穿一件刺眼的黑衣。 Sunny公司決定開發一個小工具來創建遊戲角色,可以創建不同類型的角色並可以靈活增加新的角色。
Sunny公司的開發人員通過分析發現,遊戲角色是一個複雜對象,它包含性別、臉型等多個組成部分,不同的遊戲角色其組成部分有所差異,如圖1-1所示:
圖1-1 幾種不同的遊戲角色(注:本圖中的遊戲角色造型來源於網絡,特此說明)
無論是何種造型的遊戲角色,它的創建步驟都大同小異,都需要逐步創建其組成部分,再將各組成部分裝配成一個完整的遊戲角色。如何一步步創建一個包含多個組成部分的複雜對
象,建造者模式爲解決此類問題而誕生。
2、建造者模式概述
建造者模式是較爲複雜的創建型模式,它將客戶端與包含多個組成部分(或部件)的複雜對象的創建過程分離,客戶端無須知道複雜對象的內部組成部分與裝配方式,只需要知道所需
建造者的類型即可。它關注如何一步一步創建一個的複雜對象,不同的具體建造者定義了不同的創建過程,且具體建造者相互獨立,增加新的建造者非常方便,無須修改已有代碼,系統具有較好的擴展性。
2.1、建造者模式定義
建造者模式(Builder Pattern):將一個複雜對象的構建與塔的表示分離,使得同樣的構建過程可以創建不同的表示。構建者模式是一種對象創建型模式。
2.2、建造者模式要點
建造者模式一步一步創建一個複雜的對象,它允許用戶只通過指定複雜對象的類型和內容就可以構建它們,用戶不需要知道內部的具體構建細節。建造者模式結構如圖2.2-1所示:
圖2.2-1 建造者模式結構圖
2.3、建造者模式結構圖中角色
建造者模式結構圖中包含如下幾個角色:
- Builder(抽象建造者):它爲創建一個產品Product對象的各個部件指定抽象接口,在該接口中一般聲明兩類方法,一類方法是buildPartX(),它們用於創建複雜對象的各個部件;另一類方法是getResult(),它們用於返回複雜對象。Builder既可以是抽象類,也可以是接口。
- ConcreteBuilder(具體建造者):它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義並明確它所創建的複雜對象,也可以提供一個方法返回創建好的複雜產品對象。
- Product(產品角色):它是被構建的複雜對象,包含多個組成部件,具體建造者創建該產品的內部表示並定義它的裝配過程。
- Director(指揮者):指揮者又稱爲導演類,它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端一般只需要與指揮者進行交互,在客戶端確定具體建造者的類型,並實例化具體建造者對象(也可以通過配置文件和反射機制),然後通過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。
2.4、建造者模式典型代碼實現
-
產品代碼2.4-1:
class Product { private String partA; //定義部件,部件可以是任意類型,包括值類型和引用類型 private String partB; private String partC; //partA的Getter方法和Setter方法省略 //partB的Getter方法和Setter方法省略 //partC的Getter方法和Setter方法省略 }
-
抽象建造者帶2.4-2:
abstract class Builder { //創建產品對象 protected Product product=new Product(); public abstract void buildPartA(); public abstract void buildPartB(); public abstract void buildPartC(); //返回產品對象 public Product getResult() { return product; } }
在抽象類Builder中聲明瞭一系列抽象的buildPartX()方法用於創建複雜產品的各個部件,具體建造過程在ConcreteBuilder中實現,此外還提供了工廠方法getResult(),用於返回一個建造好的完整產品。
在ConcreteBuilder中實現了buildPartX()方法,通過調用Product的setPartX()方法可以給產品對象的成員屬性設值。不同的具體建造者在實現buildPartX()方法時將有所區別,如setPartX()方法的參數可能不一樣,在有些具體建造者類中某些setPartX()方法無須實現(提供一個空實現)。而這些對於客戶端來說都無須關心,客戶端只需知道具體建造者類型即可。
在建造者模式的結構中還引入了一個指揮者類Director,該類主要有兩個作用:一方面它隔離了客戶與創建過程;另一方面它控制產品的創建過程,包括某個buildPartX()方法是否被調用以及多個buildPartX()方法調用的先後次序等。指揮者針對抽象建造者編程,客戶端只需要知道具體建造者的類型,即可通過指揮者類調用建造者的相關方法,返回一個完整的產品對象。在實際生活中也存在類似指揮者一樣的角色,如一個客戶去購買電腦,電腦銷售人員相當於指揮者,只要客戶確定電腦的類型,電腦銷售人員可以通知電腦組裝人員給客戶組裝一臺電腦。
-
指揮者代碼2.4-4:
class Director { private Builder builder; public Director(Builder builder) { this.builder=builder; } public void setBuilder(Builder builder) { this.builder=builer; } //產品構建與組裝方法 public Product construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); return builder.getResult(); } }
在指揮者類中可以注入一個抽象建造者類型的對象,其核心在於提供了一個建造方法
construct(),在該方法中調用了builder對象的構造部件的方法,最後返回一個產品對象。對於客戶端而言,只需關心具體的建造者即可。
-
客戶端類代碼2.4-5片段如下所示:
…… Builder builder = new ConcreteBuilder(); //可通過配置文件實現 Director director = new Director(builder); Product product = director.construct(); ……
可以通過配置文件來存儲具體建造者類ConcreteBuilder的類名,使得更換新的建造者時無須修改源代碼,系統擴展更爲方便。在客戶端代碼中,無須關心產品對象的具體組裝過程,只需指定具體建造者的類型即可。
3、建造者模式與抽象工廠模式
建造者模式與抽象工廠模式有點相似,但是建造者模式返回一個完整的複雜產品,而抽象工廠模式返回一系列相關的產品;在抽象工廠模式中,客戶端通過選擇具體工廠來生成所需對
象,而在建造者模式中,客戶端通過指定具體建造者類型並指導Director類如何去生成對象,側重於一步步構造一個複雜對象,然後將結果返回。如果將抽象工廠模式看成一個汽車配件生產廠,生成不同類型的汽車配件,那麼建造者模式就是一個汽車組裝廠,通過對配件進行組裝返回一輛完整的汽車。
4、遊戲角色設計(建造者模式)完整解決方案
Sunny公司開發人員決定使用建造者模式來實現遊戲角色的創建,其基本結構如圖4-1所示:
圖4-1 遊戲角色創建圖
在圖4-1中,ActorController充當指揮者,ActorBuilder充當抽象建造者,HeroBuilder、AngelBuilder和DevilBuilder充當具體建造者,Actor充當複雜產品。完整代碼如下所示:
-
角色類代碼4-1:
//Actor角色類:複雜產品,考慮到代碼的可讀性,只列出部分成員屬性,且成員屬性的類型均 爲String,真實情況下,有些成員屬性的類型需自定義 package builder; // Actor角色類:複雜產品 public class Actor { private String type; // 角色類型 private String sex; // 性別 private String face; // 臉型 private String costume; // 服飾 private String hairstyle; // 髮型 public Actor() {} public String getType() { return type; } public void setType(String type) { this.type = type; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getFace() { return face; } public void setFace(String face) { this.face = face; } public String getCostume() { return costume; } public void setCostume(String costume) { this.costume = costume; } public String getHairstyle() { return hairstyle; } public void setHairstyle(String hairstyle) { this.hairstyle = hairstyle; } }
-
角色建造器類代碼4-2:
package builder; // 角色建造器:抽象建造者 public abstract class ActorBuilder { protected Actor actor = new Actor(); public abstract void buildType(); public abstract void buildSex(); public abstract void buildFace(); public abstract void buildCostume(); public abstract void buildHairstyle(); // 工廠方法,返回一個完整的遊戲角色對象 public Actor createActor() { return this.actor; } }
-
英雄角色建造器類代碼4-3:
package builder; //英雄角色建造器:具體建造者 public class HeroBuilder extends ActorBuilder{ @Override public void buildType() { actor.setType("英雄"); } @Override public void buildSex() { actor.setSex("男"); } @Override public void buildFace() { actor.setFace("英俊"); } @Override public void buildCostume() { actor.setCostume("盔甲"); } @Override public void buildHairstyle() { actor.setHairstyle("飄逸"); } }
-
天使角色建造器類代碼4-4:
package builder; //天使角色建造器:具體建造者 public class AngelBuilder extends ActorBuilder{ @Override public void buildType() { actor.setType("天使"); } @Override public void buildSex() { actor.setSex("女"); } @Override public void buildFace() { actor.setFace("漂亮"); } @Override public void buildCostume() { actor.setCostume("白裙"); } @Override public void buildHairstyle() { actor.setHairstyle("披肩長髮"); } }
-
惡魔角色建造器類代碼4-5:
package builder; //惡魔角色建造器:具體建造者 public class DevilBuilder extends ActorBuilder { @Override public void buildType() { actor.setType("惡魔"); } @Override public void buildSex() { actor.setSex("妖"); } @Override public void buildFace() { actor.setFace("醜陋"); } @Override public void buildCostume() { actor.setCostume("黑衣"); } @Override public void buildHairstyle() { actor.setHairstyle("光頭"); } }
-
指揮者類代碼4-6:
package builder; //遊戲角色創建控制器:指揮者 public class ActorController { //逐步構建複雜產品對象 public Actor construct(ActorBuilder ab) { ab.buildType(); ab.buildSex(); ab.buildFace(); ab.buildCostume(); ab.buildHairstyle(); return ab.createActor(); } }
-
工具類代碼4-7:
package builder; import java.util.Properties; // 工具類 public class Utils { // 返回角色建造者對象 public static ActorBuilder getActorBuilder() { try { Properties prop = new Properties(); prop.load(Utils.class.getClassLoader().getResourceAsStream("actor.properties")); String className = prop.getProperty("className"); return (ActorBuilder)(Class.forName(className).newInstance()); }catch (Exception e) { e.printStackTrace(); return null; } } }
-
測試類代碼4-8:
package builder; public class TestActor { public static void main(String args[]) { ActorBuilder ab; //針對抽象建造者編程 ab = Utils.getActorBuilder(); //反射生成具體建造者對象 ActorController ac = new ActorController(); Actor actor; actor = ac.construct(ab); //通過指揮者創建完整的建造者對象 String type = actor.getType(); System.out.println(type + "的外觀:"); System.out.println("性別:" + actor.getSex()); System.out.println("面容:" + actor.getFace()); System.out.println("服裝:" + actor.getCostume()); System.out.println("髮型:" + actor.getHairstyle()); } }
-
測試結果:
天使的外觀: 性別:女 面容:漂亮 服裝:白裙 髮型:披肩長髮
在建造者模式中,客戶端只需實例化指揮者類,指揮者類針對抽象建造者編程,客戶端根據需要傳入具體的建造者類型,指揮者將指導具體建造者一步一步構造一個完整的產品(逐步
調用具體建造者的buildX()方法),相同的構造過程可以創建完全不同的產品。在遊戲角色實例中,如果需要更換角色,只需要修改配置文件,更換具體角色建造者類即可;如果需要增加新角色,可以增加一個新的具體角色建造者類作爲抽象角色建造者的子類,再修改配置文件即可,原有代碼無須修改,完全符合“開閉原則”。
5、關於Director的進一步討論
5.1、省略Director
在有些情況下,爲了簡化系統結構,可以將Director和抽象建造者Builder進行合併,在Builder中提供逐步構建複雜產品對象的construct()方法。由於Builder類通常爲抽象類,因此可以將construct()方法定義爲靜態(static)方法。如果將遊戲角色設計中的指揮者類ActorController省
略,ActorBuilder類的代碼5.1-1修改如下:
abstract class ActorBuilder
{
protected static Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public static Actor construct(ActorBuilder ab) {
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
return actor;
}
}
對應的客戶端代碼也將發生修改,其代碼片段5.1-2如下所示:
……
ActorBuilder ab;
ab = Utils.getActorBuilder();
Actor actor;
actor = ActorBuilder.construct(ab);
……
除此之外,還有一種更簡單的處理方法,可以將construct()方法的參數去掉,直接在construct()
方法中調用buildPartX()方法,代碼如5.1-3下所示:
abstract class ActorBuilder {
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public Actor construct() {
this.buildType();
this.buildSex();
this.buildFace();
this.buildCostume();
this.buildHairstyle();
return actor;
}
}
客戶端代碼代碼片段5.1-4如下所示:
……
ActorBuilder ab;
ab = Utils.getActorBuilder();
Actor actor;
actor = ab.construct();
……
此時,construct()方法定義了其他buildPartX()方法調用的次序,爲其他方法的執行提供了一個流程模板,這與我們在後面要學習的模板方法模式非常類似。
以上兩種對Director類的省略方式都不影響系統的靈活性和可擴展性,同時還簡化了系統結構,但加重了抽象建造者類的職責,如果construct()方法較爲複雜,待構建產品的組成部分較
多,建議還是將construct()方法單獨封裝在Director中,這樣做更符合“單一職責原則”。
5.2、鉤子方法的引入
建造者模式除了逐步構建一個複雜產品對象外,還可以通過Director類來更加精細地控制產品的創建過程,例如增加一類稱之爲鉤子方法(HookMethod)的特殊方法來控制是否對某個
buildPartX()的調用。
鉤子方法的返回類型通常爲boolean類型,方法名一般爲isXXX(),鉤子方法定義在抽象建造者類中。例如我們可以在遊戲角色的抽象建造者類ActorBuilder中定義一個方法isBareheaded(),用於判斷某個角色是否爲“光頭(Bareheaded)”,在ActorBuilder爲之提供一個默認實現,其返回值爲false,代碼5.2-1如下所示:
abstract class ActorBuilder {
protected Actor actor = new Actor();
public abstract void buildType();
複雜對象的組裝與創建——建造者模式(三)
127
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
//鉤子方法
public boolean isBareheaded() {
return false;
}
public Actor createActor() {
return actor;
}
}
如果某個角色無須構建頭髮部件,例如“惡魔(Devil)”,則對應的具體建造器DevilBuilder將覆
蓋isBareheaded()方法,並將返回值改爲true,代碼5.2-2如下所示:
class DevilBuilder extends ActorBuilder {
public void buildType() {
actor.setType("惡魔");
}
public void buildSex() {
actor.setSex("妖");
}
public void buildFace() {
actor.setFace("醜陋");
}
public void buildCostume() {
actor.setCostume("黑衣");
}
public void buildHairstyle() {
actor.setHairstyle("光頭");
}
//覆蓋鉤子方法
public boolean isBareheaded() {
return true;
}
}
此時,指揮者類ActorController的代碼5.2-3修改如下:
class ActorController {
public Actor construct(ActorBuilder ab) {
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
//通過鉤子方法來控制產品的構建
if(!ab.isBareheaded()) {
ab. buildHairstyle();
}
actor=ab.createActor();
return actor;
}
}
當在客戶端代碼中指定具體建造者類型並通過指揮者來實現產品的逐步構建時,將調用鉤子方法isBareheaded()來判斷遊戲角色是否有頭髮,如果isBareheaded()方法返回true,即沒有頭髮,則跳過構建髮型的方法buildHairstyle();否則將執行buildHairstyle()方法。通過引入鉤子方
法,我們可以在Director中對複雜產品的構建進行精細的控制,不僅指定buildPartX()方法的執行順序,還可以控制是否需要執行某個buildPartX()方法。
6、建造者模式總結
建造者模式的核心在於如何一步一步構建一個包含多個組成部件的完整對象,使用相同的構建過程構建不同的產品,在軟件開發中,如果我們需要創建複雜對象並希望系統具備很好的靈活性和可擴展性可以考慮使用建造者模式。
6.1、優缺點
- 主要優點:
- (1):在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象。
- (2):每一個具體的建造者都相對獨立,而與其他具體建造者無關,因此可以很方法的替換具體建造者或者增刪新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象。由於指揮者類針對抽象建造者編程,增加薪的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合“開閉原則”。
- (3):可以更加精確地控制產品的創建過程。將複雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也方便使用程序來控制創建過程。
- 主要缺點:
- (1):建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此使用範圍受到一定的限制。
- (2):如果產品的內部變化複雜,可能導致定義很多的具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和運行成本。
6.2、適用場景
- (1) 需要生成的產品對象有複雜的內部結構,這些產品對象通常包含多個成員屬性。
- (2) 需要生成的產品對象的屬性相互依賴,需要指定其生成順序。
- (3) 對象的創建過程獨立於創建該對象的類。在建造者模式中通過引入了指揮者類,將創建過程封裝在指揮者類中,而不在建造者類和客戶類中。
- (4) 隔離複雜對象的創建和使用,並使得相同的創建過程可以創建不同的產品
後記 :
本項目爲參考某馬視頻開發,相關視頻及配套資料可自行度娘或者聯繫本人。上面爲自己編寫的開發文檔,持續更新。歡迎交流,本人QQ:806797785
前端項目源代碼地址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼地址:https://gitee.com/gaogzhen/JAVA