生成器模式——創建型模式(3)


前言

在前兩講,我們介紹了工廠方法和抽象工廠模式,這兩種模式都是完成對一個或者若干個內部結構相對簡單的對象的創建工作。換句話來說,這樣的對象內部之間沒有明顯的子部分或者說是各個子部分間的“組裝”過程。然而在現實世界里,確實存在着這樣的對象模型,可以將內部抽象成若干個子部分,而且需要通過一定的組建算法將它們構建在一起形成完整的最終對象。面對類似對象的創建工作,顯然工廠模式已經不善長,需要追尋新的模式來更好地應對上述需求。

動機

在實際軟件系統中,經常面臨着“一個復雜對象”的創建問題,而其通常是由多個子部分通過一定的構建算法組裝形成。由於用戶需求的變化,這個“復雜對象”的各個子部分也經常面臨着劇烈的變化,但是子部分之間組裝算法卻比較穩定。如何提供一種“封裝機制”來隔離出“復雜對象中各個子部分”的變化,從而保持系統對這個復雜對象的“穩定構建算法”不會隨着需求的改變而改變?面對這樣的需求,生成器模式可以較好地處理。

意圖

將一個復雜對象的構建和它的表示分離,便得同樣的構建過程可以創建不同的表示。

結構圖

image

  1. 抽象生成器(Builder)角色:為創建一個Product對象的各個部件指定的抽象接口。
  2. 具體生成器(ConcreteBuilder)角色:實現Builder的接口以構造和裝配該產品的各個部件,同時提供返還組裝完畢的產品接口。
  3. 指導者(Director)角色:構造一個使用Builder接口的對象,調用具體的生成器來創建產品。其本身並沒有產品的具體知道,擁有產品具體知識的是具體生成器對象,它們之間才真正具有直接的依賴關系。
  4. 產品(Product)角色:產品便是具體生成器來要生成的復雜對象。

代碼示例

 1:  public abstract class Builder{
 2:      public abstract void buildPartA();
 3:      public abstract void buildPartB();
 4:      public abstract Product getProduct();
 5:  }
 6:   
 7:  public class ConcreteBuilder1 extends Builder{
 8:      private Product product;
 9:      public void buildPartA(){
10:          product.addPart("PartA");
11:      }
12:      public void buildPartB(){
13:          product.addPart("PartB");
14:      }
15:   
16:      public Product getProduct(){
17:          if(product!=null)
18:              return product;
19:          else 
20:              return null;
21:      }
22:  }
23:   
24:  public class ConcreteBuilder2 extends Builder{
25:      private Product product;
26:      public void buildPartA(){
27:          product.addPart("PartX");
28:      }
29:      public void buildPartB(){
30:          product.addPart("PartY");
31:      }
32:   
33:      public Product getProduct(){
34:          if(product!=null)
35:              return product;
36:          else 
37:              return null;
38:      }
39:  }
40:   
41:  public class Director{
42:      public void construct(Builder builder){
43:          builder.buildPartA();
44:          builder.buildPartB();
45:      }
46:  }
47:   
48:  public class Product{
49:      private ArrayList<String> product;
50:      public void addPart(String partName){
51:          product.add(partName);
52:      }
53:   
54:      public void showProduct(){
55:          for (String part : product) {
56:              System.out.println(part);
57:          }
58:      }
59:   
60:  }
61:   
62:  public class Client{
63:      public static void main(String[] args){
64:          Director director=new Director();
65:   
66:          Builder concreteBuilder1=new ConcreteBuilder1();
67:          director.construct(concreteBuilder1);
68:          Product product1=concreteBuilder1.getProduct();
69:          product1.showProduct();
70:   
71:          Builder concreteBuilder2=new ConcreteBuilder2();
72:          director.construct(concreteBuilder2);
73:          Product product2=concreteBuilder2.getProduct();
74:          product2.showProduct();        
75:      }
76:  }
從上述示例代碼中,我們可以清楚地看到,生成器模式是如何將一個“復雜對象”分步地構建並組裝其各個子部分的,雖然在這里我們只是通過字符串來簡單地表示各個子部分對象,但是這並不妨礙詮釋對生成器模式的demo演示。在實際的軟件系統中,各個子部分對象完全有可能通過相應的工廠方法來生成,然后再交由生成器按照特定的構建算法將其組裝成一個完整的“復雜對象”。所以,在這里我們不必拘泥於細節的實現,只要理解生成器模式本質和實現方式即可,再通過聯想實際生活中的種種模式,相信具有OO思想的你,不難將其抽象成生成器模式所適宜的應用場景。

現實場景

其實,在現實場景中,有很多適用於生成器模式來解決的應用。比如Terrylee所描述的KFC場景,這種模式用於快餐店制作兒童餐。典型的兒童餐包括一個主食,一個輔食,一杯飲料和一個玩具(例如漢堡、炸雞、可樂和玩具車)。這些在不同的兒童餐中可以是不同的,但是組合成兒童餐的過程是相同的(這是關鍵點)。無論顧客點的是漢堡,三名治還是雞肉,過程都是一樣的。櫃台的員工直接把主食,輔食和玩具放在一起。這些是放在一個袋子中的。飲料被倒入杯中,放在袋子外邊。這些過程在相互競爭的餐館中是同樣的。在這樣一種場景中,主食、輔食、飲料和玩具根據所選擇的套餐種類的不同而不同,但是不管何種套餐,都具有這幾大部分,也就是說組裝的過程是一致的,不同的只是子部分的實現不同而已,但是這並不妨礙子部分的組裝流程。這不完全符合生成器模式的適用場景嗎?

這樣的例子有很多,比如我們日常工作學習所離不開的電腦,其制造過程何嘗不是一種抽象意義上的生成器模式呢?雖然不同品牌的電腦內部的各個組成部件會有所不同,根據自身的定位和需求使用不同廠家生產的部件,但是不管是蘋果電腦還是索尼電腦,它們之間的組成元素都是相同的,都有處理器、主板、顯卡、顯示器等核心組成部件。所謂的電腦生產過程,簡單來說就是按照一定順序將上述所有部件組裝起來,而這個組裝算法相對來說是穩定不變的,變得只是電腦各個子部分不同而已,與KFC例子類似,完完全全與生成器模式的應用相符合。大家可以充分發揮想像力,聯想到現實世界的種種應用場景與生成器模式結合起來。

實現要點

  1. 生成器模式主要用於“分步驟地構建一個復雜對象”,所謂的“分步驟”其實就是一個穩定的算法,不會因為創建不同的復雜對象而發生改變,變的部分是復雜對象中各個子部件實現。另外,Builder類接口必須有足夠普遍,以便為各種類型的具體生成器構造產品。
  2. 生成器模式中所創建的產品對象不需要抽象類,因為由具體生成器生成的產品,它們的表示相差如此之大以至於給不同的產品以公共父類沒有太大必要,也不太可能提取出一個公共父類。
  3. 在生成器用於構建各種子部件的接口方法,完全可以不是抽象方法(雖然我們示例代碼是這樣),提供一個默認實現,具體生成器只需要重寫需要重寫的接口即可,對於一些相對“通用”的接口,在情況允許的條件下,可以選擇直接使用默認實現。

運用效果

  1. 松散耦合:生成器模式可以用同一個構建算法構建出表現上完全不同的產品,實現產品構建和產品表現的分離。正是將產品的構建過程獨立開來,才使得生成器模式與具體產品的表現松散耦合,從而使得構建算法可以復用,而具體產品表現也可以靈活地、方便地擴展和切換。
  2. 可以很容易地改變產品的內部表示:在生成器模式中,由於指導者只是通過Builder來構建具體產品,但是具體產品中的各個子部件的創建和裝配方式卻被builder接口隱藏起來,Director並不清楚這些具體實現細節。也正是因為這樣,需要改變產品的內部表象時,我們只需要切換不同的Builder實現對象即可,而Director角色無需作出任何的改變。
  3. 復用性更好:生成器模式分離了構建算法和具體產品實現,這樣使得“穩定”的構建算法可以達到復用的效果,同樣的效果,具體產品的子部件的實現亦可以復用,一個相同的子部件實現可以配合不同的構建算法來使用。

適用性

  1. 需要創建的產品對象有復雜的內部結構時(這是基礎,不然不需要使用分步構建算法來創建和組裝產品的各個子部件部分)。
  2. 當創建復雜對象的算法應該獨立於該對象的組成部分以及它們的裝配方式時。
  3. 需要創建的復雜對象內部的各個子部分有一定的關聯性或者依賴性,這樣在可以通過生成器來強迫生成一定的創建和組裝順序。
  4. 當構造過程必須允許被構造的對象有不同的表示時。因為如果通過相同的構造過程只有一種內部表象時,就無所謂利用生成器模式呢,這時或許使用工廠方式模式更合適、方便些。

相關模式

  1. 生成器模式與工廠方法模式:在具體的生成器實現中,需要選擇具體的部件實現。一個可靠的方案就是將部件的實現工廠化,通過工廠方法來獲取具體子部件實現對象,然后再進行子部件的裝配。
  2. 生成器模式和抽象工廠模式:在生成器模式的Builder實現中,需要創建各個部件對象,而這些部件是相互有關聯的,通常是構成一個復雜對象的部件對象。也就是具體生成器需要獲取構成一個復雜對象的產品族,而產品族的實現可以使用抽象工廠來實現,這樣復雜對象的子部件由抽象工廠模式來創建,而生成器模式負責對復雜對象各個子部件的組裝構建工作。
  3. 生成器模式與模板方法模式:兩者有很大的相似和差異性。相似性:都是定義一個固定的算法骨架,然后把算法中的某些步驟交給其他類來完成,都能實現整體算法和某些具體步驟的分離。差異性:首先是目的性,生成器模式是用來構建復雜對象的,而模式方法是用來定義算法骨架的,尤其是一些復雜的業務功能的處理算法骨架;其次是模式的實現上,生成器使用委托的方式(注意這里的委托指的是Director委托具體的Builder來創建具體產品,而不是指具體生成器的實現方式),而模板方法是使用繼承的方式(這里是主要是從兩種模式的使用角度上來說的,其實個人認為你也可以生成器模式也是通過繼承的方式來完成的)。
  4. 生成器模式與組合模式:建於復雜的組合結構,可以通過生成器模式一步步地進行構建組合結構,通過分步構建的方式來完成對組合結構對象的創建。

總結

生成器模式的本質:分離整體構建算法和部件構造表示。構建一個復雜對象,需要將整體的構建過程與復雜對象子部件的構建過程分離開來,這樣才能使得程序結構更松散、易擴展,復用性好,同樣也會使代碼邏輯更清晰,意圖更明顯。生成器模式的重心還是在於分離整體構建算法與子部件的構建,分步驟構建對象只不過是整體構建算法的一個簡單表現,或者說是一個附帶產物。說了這么多,也摘抄了其他優秀博文和書籍的經典論述,希望大家對生成器模式會有一個全面、深刻的理解和掌握。下一篇將介紹原型模式,敬請期待。

參考資料

  1. 程傑著《大話設計模式》一書
  2. 陳臣等著《研磨設計模式》一書
  3. GOF著《設計模式》一書
  4. Terrylee .Net設計模式系列文章
  5. 呂震宇老師 設計模式系列文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM