設計模式--建造者模式

目錄

本文的結構如下:

  • 引言
  • 什么是建造者模式
  • 模式的結構
  • 典型代碼
  • 代碼示例
  • Builder模式變種
  • 建造者模式與抽象工廠模式
  • 優點和缺點
  • 適用環境
  • 模式應用

一、引言

玩過游戲的應該清楚,游戲中有很多角色,這些角色往往都有不同的外形,不同的能量值,不同的服飾,不同的武器等等,所以要創造一個游戲角色,就要創造構成這個游戲角色不同組成;再比如電腦,電腦是由CPU,主板,內存,顯示器等組成的,電腦生廠商需要用這些部件組裝成一部完整的電腦。

在軟件開發中,創建一個由多個部件組成的復雜產品,往往使用建造者模式。

二、什么是建造者模式

建造者模式又稱為生成器模式,它是一種較為復雜、使用頻率也相對較低的創建型模式,它將客戶端與包含多個組成部分(或部件)的復雜對象的創建過程分離。

建造者模式關注如何一步一步創建一個的復雜對象,不同的具體建造者定義了不同的創建過程,且具體建造者相互獨立,增加新的建造者非常方便,無須修改已有代碼,系統具有較好的擴展性。

建造者模式定義如下:

建造者模式(Builder Pattern):將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。建造者模式是一種對象創建型模式。

建造者模式一步一步創建一個復雜的對象,它允許用戶只通過指定復雜對象的類型和內容就可以構建它們,用戶不需要知道內部的具體構建細節。

三、模式的結構

建造者模式UML類圖如下:

image

在建造者模式結構圖中包含如下幾個角色:

  • Builder(抽象建造者):它為創建一個產品Product對象的各個部件指定抽象接口,在該接口中一般聲明兩類方法,一類方法是buildPartX(),它們用于創建復雜對象的各個部件;另一類方法是getResult(),它們用于返回復雜對象。Builder既可以是抽象類,也可以是接口。
  • ConcreteBuilder(具體建造者):它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義并明確它所創建的復雜對象,也可以提供一個方法返回創建好的復雜產品對象。
  • Product(產品角色):它是被構建的復雜對象,包含多個組成部件,具體建造者創建該產品的內部表示并定義它的裝配過程。
  • Director(指揮者):指揮者又稱為導演類,它負責安排復雜對象的建造次序,指揮者與抽象建造者之間存在關聯關系,可以在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成復雜對象的建造。客戶端一般只需要與指揮者進行交互,在客戶端確定具體建造者的類型,并實例化具體建造者對象(也可以通過配置文件和反射機制),然后通過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。

復雜對象是指那些包含多個成員屬性的對象,這些成員屬性也稱為部件或零件,如汽車包括方向盤、發動機、輪胎等部件,電子郵件包括發件人、收件人、主題、內容、附件等部件。

四、典型代碼

復雜對象類典型代碼如下:

public class Product {
    //定義部件,部件可以是任意類型,包括值類型和引用類型
    private  String partA;
    private  String partB;
    private  String partC;

    public void setPartA(String partA) {
        this.partA = partA;
    }

    public void setPartB(String partB) {
        this.partB = partB;
    }

    public void setPartC(String partC) {
        this.partC = partC;
    }
}

抽象建造者類定義了產品部件的創建方法和復雜產品返回方法,典型代碼如下:

public 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;
    }
}

在ConcreteBuilder中實現了buildPartX()方法,通過調用Product的setPartX()方法可以給產品對象的成員屬性設值。不同的具體建造者在實現buildPartX()方法時將有所區別,如setPartX()方法的參數可能不一樣,在有些具體建造者類中某些setPartX()方法無須實現(提供一個空實現)。而這些對于客戶端來說都無須關心,客戶端只需知道具體建造者類型即可。

public class ConcreteBuilder extends Builder {
    public void buildPartA() {
        product.setPartA("part A");
    }

    public void buildPartB() {
        product.setPartA("part B");
    }

    public void buildPartC() {
        product.setPartA("part C");
    }
}

建造者模式的結構中還有一個指揮者類Director,該類主要有兩個作用:一是它隔離了客戶與創建過程;二是它控制產品的創建過程,包括某個buildPartX()方法是否被調用以及多個buildPartX()方法調用的先后次序等。指揮者針對抽象建造者編程,客戶端只需要知道具體建造者的類型,即可通過指揮者類調用建造者的相關方法,返回一個完整的產品對象。在實際生活中也存在類似指揮者一樣的角色,如一個客戶去購買電腦,電腦銷售人員相當于指揮者,只要客戶確定電腦的類型,電腦銷售人員可以通知電腦組裝人員給客戶組裝一臺電腦。指揮者類典型代碼如下:

public class Director {
    private Builder builder;

    public Director(Builder builder){
        this.builder = builder;
    }
    public void setBuilder(Builder builder){
        this.builder = builder;
    }

    public Product construct(){
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
        return builder.getResult();
    }
}

客戶端:

public class Client {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Product product = director.construct();
    }
}

在客戶端代碼中,無須關心產品對象的具體組裝過程,只需指定具體建造者的類型即可。可以通過配置文件來存儲具體建造者類ConcreteBuilder的類名,使得更換新的建造者時無須修改源代碼,系統擴展更為方便。

五、代碼示例

一款視頻播放軟件,為了給用戶使用提供方便,不同場景下有不同界面,如完整界面、精簡界面等。不同的界面該播放器的組成元素有所差異,如在完整界面下將顯示菜單、播放列表、主窗口、控制條等,在精簡界面下只顯示主窗口和控制條。

image

復雜對象VideoPlayer:

public class VideoPlayer {
    private String type;
    private String menu;
    private String playerList;
    private String mainWindows;
    private String controlStrip;

    public void setType(String type) {
        this.type = type;
    }

    public void setMenu(String menu) {
        this.menu = menu;
    }

    public void setPlayerList(String playerList) {
        this.playerList = playerList;
    }

    public void setMainWindows(String mainWindows) {
        this.mainWindows = mainWindows;
    }

    public void setControlStrip(String controlStrip) {
        this.controlStrip = controlStrip;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("這是一個 ").append(type).append(" 視頻播放器\n\n");
        sb.append("該播放器擁有:\n");
        if (menu != null) sb.append(menu).append("\n");
        if (playerList != null) sb.append(playerList).append("\n");
        if (mainWindows != null) sb.append(mainWindows).append("\n");
        if (controlStrip != null) sb.append(controlStrip).append("\n");
        return sb.toString();
    }
}

抽象builder:

public abstract class PlayerBuilder {
    protected VideoPlayer player = new VideoPlayer();

    public abstract void buildType();
    public abstract void buildMenu();
    public abstract void buildPlayerList();
    public abstract void buildMainWindows();
    public abstract void buildControlStrip();

    public VideoPlayer getPlayer(){
        return player;
    }
}

具體builder:

public class FullPlayerBuilder extends PlayerBuilder {
    public void buildType() {
        player.setType("完整界面");
    }

    public void buildMenu() {
        player.setMenu("菜單");
    }

    public void buildPlayerList() {
        player.setPlayerList("播放列表");
    }

    public void buildMainWindows() {
        player.setMainWindows("主界面");
    }

    public void buildControlStrip() {
        player.setControlStrip("控制條");
    }
}

public class SimplePlayerBuilder extends PlayerBuilder {
    public void buildType() {
        player.setType("精簡界面");
    }

    public void buildMenu() {
    }

    public void buildPlayerList() {
    }

    public void buildMainWindows() {
        player.setMainWindows("主界面");
    }

    public void buildControlStrip() {
        player.setControlStrip("控制條");
    }
}

指揮者BuilderDirector:

public class PlayerDirector {
    private PlayerBuilder builder;

    public PlayerDirector(PlayerBuilder builder){
        this.builder = builder;
    }

    public void setBuilder(PlayerBuilder builder){
        this.builder = builder;
    }

    public VideoPlayer construct(){
        builder.buildType();
        builder.buildMenu();
        builder.buildPlayerList();
        builder.buildMainWindows();
        builder.buildControlStrip();
        return builder.getPlayer();
    }
}

客戶端測試:

public class Client {
    public static void main(String[] args) {
        PlayerBuilder builder = new FullPlayerBuilder();
        PlayerDirector director = new PlayerDirector(builder);
        VideoPlayer player = director.construct();

        System.out.println(player);
    }
}

六、Builder模式變種

6.1、省略Director

Director類在建造者模式中扮演十分重要的作用,它按一定的順序調用Builder的buildPartX()方法,像客戶端返回一個完整的產品。

但是在有些情況下,為了簡化系統,我們可以將Director和抽象建造者Builder進行合并。

如下:

public abstract class PlayerBuilder {
    protected VideoPlayer player = new VideoPlayer();

    public abstract void buildType();
    public abstract void buildMenu();
    public abstract void buildPlayerList();
    public abstract void buildMainWindows();
    public abstract void buildControlStrip();

    public VideoPlayer getPlayer(){
        buildType();
        buildMenu();
        buildPlayerList();
        buildMainWindows();
        buildControlStrip();
        return player;
    }
}

public class Client {
    public static void main(String[] args) {
        PlayerBuilder builder = new SimplePlayerBuilder();
        VideoPlayer player = builder.getPlayer();

        System.out.println(player);
    }
}

6.2、內部Builder

內部Builder用起來是鏈式調用的形式,在Effective Java中,第二條有提到遇到多個構造器參數時要考慮用構建器,應該還是比較熟悉的。

public class VideoPlayer {
    private final String type;
    private final String menu;
    private final String playerList;
    private final String mainWindows;
    private final String controlStrip;

    private VideoPlayer(Builder builder){
        type = builder.type;
        menu = builder.menu;
        playerList = builder.playerList;
        mainWindows = builder.mainWindows;
        controlStrip = builder.controlStrip;
    }

    public static class Builder{
        private String type;
        private String menu;
        private String playerList;
        private String mainWindows;
        private String controlStrip;

        public Builder setType(String type) {
            this.type = type;
            return this;
        }

        public Builder setMenu(String menu){
            this.menu = menu;
            return this;
        }

        public Builder setPlayerList(String playerList){
            this.playerList = playerList;
            return this;
        }

        public Builder setMainWindows(String mainWindows){
            this.mainWindows = mainWindows;
            return this;
        }

        public Builder setControlStrip(String controlStrip){
            this.controlStrip = controlStrip;
            return this;
        }

        public VideoPlayer build(){
            return new VideoPlayer(this);
        }
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("這是一個 ").append(type).append(" 視頻播放器\n\n");
        sb.append("該播放器擁有:\n");
        if (menu != null) sb.append(menu).append("\n");
        if (playerList != null) sb.append(playerList).append("\n");
        if (mainWindows != null) sb.append(mainWindows).append("\n");
        if (controlStrip != null) sb.append(controlStrip).append("\n");
        return sb.toString();
    }
}

客戶端:

public class Client {
    public static void main(String[] args) {
        VideoPlayer player = new VideoPlayer.Builder()
                .setType("簡單界面")
                .setMainWindows("主界面")
                .setControlStrip("控制條")
                .build();
        System.out.println(player);
    }
}

七、建造者模式與抽象工廠模式

建造者模式完成的事,好像都可以通過抽象工廠模式完成,那么區別是什么?

  • 與建造者模式相比,抽象工廠模式返回的是一系列相關的產品,這些產品位于不同的產品等級結構,構成產品族,而建造者模式返回的是一個組裝好的完整產品。抽象工廠模式更像一個汽車零件生產商,生產不同品牌汽車的各種零件。而建造者模式更像一個汽車裝配廠,通過一系列零件的組裝,最終生產出的是一個完整的汽車。
  • 抽象工廠模式中,客戶端需實例化工廠類,然后通過工廠類獲取所需的產品對象。而建造者模式更側重于將復雜的構造對象的方法交給建造者去做,而客戶端只需要通過指揮者就能創建一個完整的產品實例。

八、優點和缺點

8.1、優點

建造者模式的主要優點如下:

  • 在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象。
  • 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象。由于指揮者類針對抽象建造者編程,增加新的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合“開閉原則”。
  • 可以更加精細地控制產品的創建過程。將復雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程。

8.2、缺點

建造者模式的主要缺點如下:

  • 建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用范圍受到一定的限制。
  • 如果產品的內部變化復雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和運行成本。

九、適用環境

在以下情況下可以考慮使用建造者模式:

  • 需要生成的產品對象有復雜的內部結構,這些產品對象通常包含多個成員屬性。
  • 需要生成的產品對象的屬性相互依賴,需要指定其生成順序。
  • 對象的創建過程獨立于創建該對象的類。在建造者模式中通過引入了指揮者類,將創建過程封裝在指揮者類中,而不在建造者類和客戶類中。
  • 隔離復雜對象的創建和使用,并使得相同的創建過程可以創建不同的產品。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容