引言
生成器模式是一種創建型設計模式,?使你能夠分步驟創建復雜對象。?該模式允許你使用相同的創建代碼生成不同類型和形式的對象。
?問題
假設有這樣一個復雜對象,?在對其進行構造時需要對諸多成員變量和嵌套對象進行繁復的初始化工作。?這些初始化代碼通常深藏于一個包含眾多參數且讓人基本看不懂的構造函數中;?甚至還有更糟糕的情況,?那就是這些代碼散落在客戶端代碼的多個位置。
?如果為每種可能的對象都創建一個子類,這可能會導致程序變得過于復雜。
例如,?我們來思考如何創建一個?房屋
House對象。?建造一棟簡單的房屋,?首先你需要建造四面墻和地板,?安裝房門和一套窗戶,?然后再建造一個屋頂。?但是如果你想要一棟更寬敞更明亮的房屋,?還要有院子和其他設施?(例如暖氣、?排水和供電設備),?那又該怎么辦呢?
最簡單的方法是擴展?房屋
基類,?然后創建一系列涵蓋所有參數組合的子類。?但最終你將面對相當數量的子類。?任何新增的參數?(例如門廊類型)?都會讓這個層次結構更加復雜。
另一種方法則無需生成子類。?你可以在?房屋
基類中創建一個包括所有可能參數的超級構造函數,?并用它來控制房屋對象。?這種方法確實可以避免生成子類,?但它卻會造成另外一個問題。
?擁有大量輸入參數的構造函數也有缺陷:這些參數也不是每次都要全部用上的。
通常情況下,?絕大部分的參數都沒有使用,?這使得對于構造函數的調用十分不簡潔。?例如,?只有很少的房子有游泳池,?因此與游泳池相關的參數十之八九是毫無用處的。?
解決方案
生成器模式建議將對象構造代碼從產品類中抽取出來,?并將其放在一個名為生成器的獨立對象中。
該模式會將對象構造過程劃分為一組步驟,?比如?build-Walls
創建墻壁和?build-Door
創建房門創建房門等。?每次創建對象時,?你都需要通過生成器對象執行一系列步驟。?重點在于你無需調用所有步驟,?而只需調用創建特定對象配置所需的那些步驟即可。
當你需要創建不同形式的產品時,?其中的一些構造步驟可能需要不同的實現。?例如,?木屋的房門可能需要使用木頭制造,?而城堡的房門則必須使用石頭制造。
在這種情況下,?你可以創建多個不同的生成器,?用不同方式實現一組相同的創建步驟。?然后你就可以在創建過程中使用這些生成器?(例如按順序調用多個構造步驟)?來生成不同類型的對象。
?例如,?假設第一個建造者使用木頭和玻璃制造房屋,?第二個建造者使用石頭和鋼鐵,?而第三個建造者使用黃金和鉆石。?在調用同一組步驟后,?第一個建造者會給你一棟普通房屋,?第二個會給你一座小城堡,?而第三個則會給你一座宮殿。?但是,?只有在調用構造步驟的客戶端代碼可以通過通用接口與建造者進行交互時,?這樣的調用才能返回需要的房屋。
主管
你可以進一步將用于創建產品的一系列生成器步驟調用抽取成為單獨的主管類。?主管類可定義創建步驟的執行順序,?而生成器則提供這些步驟的實現。
嚴格來說,?你的程序中并不一定需要主管類。?客戶端代碼可直接以特定順序調用創建步驟。?不過,?主管類中非常適合放入各種例行構造流程,?以便在程序中反復使用。
此外,?對于客戶端代碼來說,?主管類完全隱藏了產品構造細節。?客戶端只需要將一個生成器與主管類關聯,?然后使用主管類來構造產品,?就能從生成器處獲得構造結果了。
生成器模式結構
- 生成器 (Builder)接口聲明在所有類型生成器中通用的產品構造步驟。
- 具體生成器 (ConcreteBuilders)提供構造過程的不同實現。具體生成器也可以構造不遵循通用接口的產品。
- 產品 (Products)是最終生成的對象。由不同生成器構造的產品無需屬于同一類層次結構或接口。
- 主管 (Director))類定義調用構造步驟的順序,這樣你就可以創建和復用特定的產品配置。
- 客戶端(Client)必須將某個生成器對象與主管類關聯。一般情況下,你只需通過主管類構造函數的參數進行一次性關聯即可。此后主管類就能使用生成器對象完成后續所有的構造任務。但在客
- 戶端將生成器對象傳遞給主管類制造方法時還有另一種方式。在這種情況下,你在使用主管類生產產品時每次都可以使用不同的生成器。
?偽代碼
下面關于生成器模式的例子演示了你可以如何復用相同的對象構造代碼來生成不同類型的產品——例如汽車?(Car)——及其相應的使用手冊?(Manual)。
汽車是一個復雜對象,?有數百種不同的制造方法。?我們沒有在?汽車
類中塞入一個巨型構造函數,?而是將汽車組裝代碼抽取到單獨的汽車生成器類中。?該類中有一組方法可用來配置汽車的各種部件。
如果客戶端代碼需要組裝一輛與眾不同、?精心調教的汽車,?它可以直接調用生成器。?或者,?客戶端可以將組裝工作委托給主管類,?因為主管類知道如何使用生成器制造最受歡迎的幾種型號汽車。
你或許會感到吃驚,?但確實每輛汽車都需要一本使用手冊?(說真的,?誰會去讀它們呢?)。?使用手冊會介紹汽車的每一項功能,?因此不同型號的汽車,?其使用手冊內容也不一樣。?因此,?你可以復用現有流程來制造實際的汽車及其對應的手冊。?當然,?編寫手冊和制造汽車不是一回事,?所以我們需要另外一個生成器對象來專門編寫使用手冊。?該類與其制造汽車的兄弟類都實現了相同的制造方法,?但是其功能不是制造汽車部件,?而是描述每個部件。?將這些生成器傳遞給相同的主管對象,?我們就能夠生成一輛汽車或是一本使用手冊了。
最后一個部分是獲取結果對象。?盡管金屬汽車和紙質手冊存在關聯,?但它們卻是完全不同的東西。?我們無法在主管類和具體產品類不發生耦合的情況下,?在主管類中提供獲取結果對象的方法。?因此,?我們只能通過負責制造過程的生成器來獲取結果對象。
// 只有當產品較為復雜且需要詳細配置時,使用生成器模式才有意義。下面的兩個
// 產品盡管沒有同樣的接口,但卻相互關聯。
class Car is// 一輛汽車可能配備有 GPS 設備、行車電腦和幾個座位。不同型號的汽車(// 運動型轎車、SUV 和敞篷車)可能會安裝或啟用不同的功能。class Manual is// 用戶使用手冊應該根據汽車配置進行編制,并介紹汽車的所有功能。// 生成器接口聲明了創建產品對象不同部件的方法。
interface Builder ismethod reset()method setSeats(……)method setEngine(……)method setTripComputer(……)method setGPS(……)// 具體生成器類將遵循生成器接口并提供生成步驟的具體實現。你的程序中可能會
// 有多個以不同方式實現的生成器變體。
class CarBuilder implements Builder isprivate field car:Car// 一個新的生成器實例必須包含一個在后續組裝過程中使用的空產品對象。constructor CarBuilder() isthis.reset()// reset(重置)方法可清除正在生成的對象。method reset() isthis.car = new Car()// 所有生成步驟都會與同一個產品實例進行交互。method setSeats(……) is// 設置汽車座位的數量。method setEngine(……) is// 安裝指定的引擎。method setTripComputer(……) is// 安裝行車電腦。method setGPS(……) is// 安裝全球定位系統。// 具體生成器需要自行提供獲取結果的方法。這是因為不同類型的生成器可能// 會創建不遵循相同接口的、完全不同的產品。所以也就無法在生成器接口中// 聲明這些方法(至少在靜態類型的編程語言中是這樣的)。//// 通常在生成器實例將結果返回給客戶端后,它們應該做好生成另一個產品的// 準備。因此生成器實例通常會在 `getProduct(獲取產品)`方法主體末尾// 調用重置方法。但是該行為并不是必需的,你也可讓生成器等待客戶端明確// 調用重置方法后再去處理之前的結果。method getProduct():Car isproduct = this.carthis.reset()return product// 生成器與其他創建型模式的不同之處在于:它讓你能創建不遵循相同接口的產品。
class CarManualBuilder implements Builder isprivate field manual:Manualconstructor CarManualBuilder() isthis.reset()method reset() isthis.manual = new Manual()method setSeats(……) is// 添加關于汽車座椅功能的文檔。method setEngine(……) is// 添加關于引擎的介紹。method setTripComputer(……) is// 添加關于行車電腦的介紹。method setGPS(……) is// 添加關于 GPS 的介紹。method getProduct():Manual is// 返回使用手冊并重置生成器。// 主管只負責按照特定順序執行生成步驟。其在根據特定步驟或配置來生成產品時
// 會很有幫助。由于客戶端可以直接控制生成器,所以嚴格意義上來說,主管類并
// 不是必需的。
class Director is// 主管可同由客戶端代碼傳遞給自身的任何生成器實例進行交互。客戶端可通// 過這種方式改變最新組裝完畢的產品的最終類型。主管可使用同樣的生成步// 驟創建多個產品變體。method constructSportsCar(builder: Builder) isbuilder.reset()builder.setSeats(2)builder.setEngine(new SportEngine())builder.setTripComputer(true)builder.setGPS(true)method constructSUV(builder: Builder) is// ……// 客戶端代碼會創建生成器對象并將其傳遞給主管,然后執行構造過程。最終結果
// 將需要從生成器對象中獲取。
class Application ismethod makeCar() isdirector = new Director()CarBuilder builder = new CarBuilder()director.constructSportsCar(builder)Car car = builder.getProduct()CarManualBuilder builder = new CarManualBuilder()director.constructSportsCar(builder)// 最終產品通常需要從生成器對象中獲取,因為主管不知曉具體生成器和// 產品的存在,也不會對其產生依賴。Manual manual = builder.getProduct()
?生成器模式適合應用場景
用生成器模式可避免?“重疊構造函數?(telescoping constructor)”?的出現。
?假設你的構造函數中有十個可選參數,?那么調用該函數會非常不方便;?因此,?你需要重載這個構造函數,?新建幾個只有較少參數的簡化版。?但這些構造函數仍需調用主構造函數,?傳遞一些默認數值來替代省略掉的參數。
class Pizza {Pizza(int size) { …… }Pizza(int size, boolean cheese) { …… }Pizza(int size, boolean cheese, boolean pepperoni) { …… }// ……
生成器模式讓你可以分步驟生成對象,?而且允許你僅使用必須的步驟。?應用該模式后,?你再也不需要將幾十個參數塞進構造函數里了。
當你希望使用代碼創建不同形式的產品?(例如石頭或木頭房屋)?時,?可使用生成器模式。
?如果你需要創建的各種形式的產品,?它們的制造過程相似且僅有細節上的差異,?此時可使用生成器模式。
基本生成器接口中定義了所有可能的制造步驟,?具體生成器將實現這些步驟來制造特定形式的產品。?同時,?主管類將負責管理制造步驟的順序。
?使用生成器構造組合樹或其他復雜對象。
?生成器模式讓你能分步驟構造產品。?你可以延遲執行某些步驟而不會影響最終產品。?你甚至可以遞歸調用這些步驟,?這在創建對象樹時非常方便。
生成器在執行制造步驟時,?不能對外發布未完成的產品。?這可以避免客戶端代碼獲取到不完整結果對象的情況。
實現方法
-
清晰地定義通用步驟,?確保它們可以制造所有形式的產品。?否則你將無法進一步實施該模式。
-
在基本生成器接口中聲明這些步驟。
-
為每個形式的產品創建具體生成器類,?并實現其構造步驟。
不要忘記實現獲取構造結果對象的方法。?你不能在生成器接口中聲明該方法,?因為不同生成器構造的產品可能沒有公共接口,?因此你就不知道該方法返回的對象類型。?但是,?如果所有產品都位于單一類層次中,?你就可以安全地在基本接口中添加獲取生成對象的方法。
-
考慮創建主管類。?它可以使用同一生成器對象來封裝多種構造產品的方式。
-
客戶端代碼會同時創建生成器和主管對象。?構造開始前,?客戶端必須將生成器對象傳遞給主管對象。?通常情況下,?客戶端只需調用主管類構造函數一次即可。?主管類使用生成器對象完成后續所有制造任務。?還有另一種方式,?那就是客戶端可以將生成器對象直接傳遞給主管類的制造方法。
-
只有在所有產品都遵循相同接口的情況下,?構造結果可以直接通過主管類獲取。?否則,?客戶端應當通過生成器獲取構造結果。
生成器模式優缺點
- ?你可以分步創建對象,?暫緩創建步驟或遞歸運行創建步驟。
- ?生成不同形式的產品時,?你可以復用相同的制造代碼。
- ?單一職責原則。?你可以將復雜構造代碼從產品的業務邏輯中分離出來。
- ?由于該模式需要新增多個類,?因此代碼整體復雜程度會有所增加。
與其他模式的關系
-
在許多設計工作的初期都會使用工廠方法模式?(較為簡單,?而且可以更方便地通過子類進行定制),?隨后演化為使用???????抽象工廠模式、?原型模式或生成器模式(更靈活但更加復雜)。
-
生成器重點關注如何分步生成復雜對象。?抽象工廠專門用于生產一系列相關對象。?抽象工廠會馬上返回產品,?生成器則允許你在獲取產品前執行一些額外構造步驟。
-
你可以在創建復雜組合模式樹時使用生成器,?因為這可使其構造步驟以遞歸的方式運行。
-
你可以結合使用生成器和橋接模式:?主管類負責抽象工作,?各種不同的生成器負責實現工作。
-
抽象工廠、?生成器和原型都可以用???????單例模式來實現。?