重識設計模式-建造者模式(Builder Pattern)

本文已同步發表到個人技術微信公衆號,掃一掃文章底部的二維碼或在微信搜索 「程序員驛站」便可關注,不按期更新優質技術文章。同時,也歡迎加入QQ技術羣(羣號:650306310)一塊兒交流學習!java

定義

建造者模式(Builder Pattern): 將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。建造者模式是一種對象建立型模式。程序員

建造者模式將客戶端與包含多個組成部分(或部件)的複雜對象的建立過程分離,客戶端無須知道複雜對象的內部組成部分與裝配方式,只須要知道所需建造者的類型便可。它關注如何一步一步建立一個的複雜對象,不一樣的具體建造者定義了不一樣的建立過程,且具體建造者相互獨立,增長新的建造者很是方便,無須修改已有代碼,系統具備較好的擴展性。算法

建造者模式主要解決在軟件系統中,有時候面臨着"一個複雜對象"的建立工做,其一般由各個部分的子對象用必定的算法構成;因爲需求的變化,這個複雜對象的各個部分常常面臨着劇烈的變化,可是將它們組合在一塊兒的算法卻相對穩定。是在一些基本部件不會變,而其組合常常變化的時候使用。編程

角色

建造者模式一步一步建立一個複雜的對象,它容許用戶只經過指定複雜對象的類型和內容就能夠構建它們,用戶不須要知道內部的具體構建細節。建造者模式結構如圖所示:bash

建造者模式結構圖

在建造者模式結構圖中包含以下幾個角色:
●Builder(抽象建造者): 它爲建立一個產品Product對象的各個部件指定抽象接口,在該接口中通常聲明兩類方法,一類方法是buildPartX(),它們用於建立複雜對象的各個部件;另外一類方法是getResult(),它們用於返回複雜對象。Builder既能夠是抽象類,也能夠是接口。微信

●ConcreteBuilder(具體建造者): 它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜對象,也能夠提供一個方法返回建立好的複雜產品對象。網絡

●Product(產品角色): 它是被構建的複雜對象,包含多個組成部件,具體建造者建立該產品 的內部表示並定義它的裝配過程。函數

●Director(指揮者): 指揮者又稱爲導演類,它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,能夠在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端通常只須要與指揮者進行交互,在客戶端肯定具體建造者的類型,並實例化具體建造者對象(也能夠經過配置文件和反射機制),而後經過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。工具

在建造者模式的定義中提到了複雜對象,那麼什麼是複雜對象?簡單來講,複雜對象是指那些包含多個成員屬性的對象,這些成員屬性也稱爲部件或零件,如汽車包括方向盤、發動機、輪胎等部件,電子郵件包括髮件人、收件人、主題、內容、附件等部件,一個典型的複雜對象類代碼示例以下:學習

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 */
public class Product {
    private String partA; //定義部件,部件能夠是任意類型,包括值類型和引用
    private String partB;
    private String partC;
    //partA的Getter方法和Setter方法省略
    //partB的Getter方法和Setter方法省略
    //partC的Getter方法和Setter方法省略
}
複製代碼

在抽象建造者類中定義了產品的建立方法和返回方法,其典型代碼以下:

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 */
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;
    }
}
複製代碼

在抽象類Builder中聲明瞭一系列抽象的buildPartX()方法用於建立複雜產品的各個部件,具體建造過程在ConcreteBuilder中實現,此外還提供了工廠方法getResult(),用於返回一個建造好的完整產品。

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

在建造者模式的結構中還引入了一個指揮者類Director,該類主要有兩個做用:一方面它隔離了客戶與建立過程;另外一方面它控制產品的建立過程,包括某個buildPartX()方法是否被調用以及多個buildPartX()方法調用的前後次序等。指揮者針對抽象建造者編程,客戶端只須要知道具體建造者的類型,便可經過指揮者類調用建造者的相關方法,返回一個完整的產品對象。在實際生活中也存在相似指揮者同樣的角色,如一個客戶去購買電腦,電腦銷售人員至關於指揮者,只要客戶肯定電腦的類型,電腦銷售人員能夠通知電腦組裝人員給客戶組裝一臺電腦。指揮者類的代碼示例以下:

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * 指揮者類 */
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();
    }
}
複製代碼

在指揮者類中能夠注入一個抽象建造者類型的對象,其核心在於提供了一個建造方法construct(),在該方法中調用了builder對象的構造部件的方法,最後返回一個產品對象。

對於客戶端而言,只需關心具體的建造者便可,通常狀況下,客戶端類代碼片斷以下所示:

……
Builder builder = new ConcreteBuilder(); //可經過配置文件實現
Director director = new Director(builder);
Product product = director.construct();
……
複製代碼

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

建造者模式與抽象工廠模式有點類似,可是建造者模式返回一個完整的複雜產品,而抽象工廠模式返回一系列相關的產品;在抽象工廠模式中,客戶端經過選擇具體工廠來生成所需對象,而在建造者模式中,客戶端經過指定具體建造者類型並指導Director類如何去生成對象,側重於一步步構造一個複雜對象,而後將結果返回。若是將抽象工廠模式當作一個汽車配件生產廠,生成不一樣類型的汽車配件,那麼建造者模式就是一個汽車組裝廠,經過對配件進行組裝返回一輛完整的汽車。

案例回放

Sunny軟件公司遊戲開發小組決定開發一款名爲《Sunny羣俠傳》的網絡遊戲,該遊戲採用主流的RPG(Role Playing Game,角色扮演遊戲)模式,玩家能夠在遊戲中扮演虛擬世界中的一個特定角色,角色根據不一樣的遊戲情節和統計數據(如力量、魔法、技能等)具備不一樣的能力,角色也會隨着不斷升級而擁有更增強大的能力。

做爲RPG遊戲的一個重要組成部分,須要對遊戲角色進行設計,並且隨着該遊戲的升級將不斷增長新的角色。不一樣類型的遊戲角色,其性別、臉型、服裝、髮型等外部特性都有所差別,例如「天使」擁有美麗的面容和披肩的長髮,並身穿一襲白裙;而「惡魔」極其醜陋,留着光頭並穿一件刺眼的黑衣。Sunny公司決定開發一個小工具來建立遊戲角色,能夠建立不一樣類型的角色並能夠靈活增長新的角色。

Sunny公司的開發人員經過分析發現,遊戲角色是一個複雜對象,它包含性別、臉型等多個組成部分,不一樣的遊戲角色其組成部分有所差別,如圖所示:

幾種不一樣的遊戲角色造型

不管是何種造型的遊戲角色,它的建立步驟都大同小異,都須要逐步建立其組成部分,再將各組成部分裝配成一個完整的遊戲角色。如何一步步建立一個包含多個組成部分的複雜對象,建造者模式爲解決此類問題而誕生。

Sunny公司開發人員決定使用建造者模式來實現遊戲角色的建立,其基本結構如圖所示:

遊戲角色建立結構圖

在上圖中,ActorController充當指揮者,ActorBuilder充當抽象建造者,HeroBuilder、AngelBuilder和DevilBuilder充當具體建造者,Actor充當複雜產品。完整代碼以下所示:

Actor.java

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * Actor角色類:複雜產品,考慮到代碼的可讀性,只列出部分紅員屬性,且成員屬性的類型均 * 爲String,真實狀況下,有些成員屬性的類型需自定義 */
public class Actor {
    private String type; //角色類型
    private String sex; //性別

    private String face; //臉型
    private String costume; //服裝
    private String hairstyle; //髮型

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

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setFace(String face) {
        this.face = face;
    }

    public void setCostume(String costume) {
        this.costume = costume;
    }

    public void setHairstyle(String hairstyle) {
        this.hairstyle = hairstyle;
    }

    public String getType() {
        return (this.type);
    }

    public String getSex() {
        return (this.sex);
    }

    public String getFace() {
        return (this.face);
    }

    public String getCostume() {
        return (this.costume);
    }

    public String getHairstyle() {
        return (this.hairstyle);
    }
}
複製代碼

HeroBuilder.java

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * 英雄角色建造器:具體建造者 */
public class HeroBuilder 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("飄逸");
    }
}
複製代碼

AngelBuilder.java

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * 天使角色建造器:具體建造者 */
public class AngelBuilder 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("披肩長髮");
    }
}
複製代碼

DevilBuilder.java

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * 惡魔角色建造器:具體建造者 */
public 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("光頭");
    }
}
複製代碼

指揮者類ActorController定義了construct()方法,該方法擁有一個抽象建造者ActorBuilder類型的參數,在該方法內部實現了遊戲角色對象的逐步構建,代碼以下所示:

ActorController.java

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * 遊戲角色建立控制器:指揮者 */
public class ActorController {
    //逐步構建複雜產品對象
    public Actor construct(ActorBuilder ab) {
        Actor actor;
        ab.buildType();
        ab.buildSex();
        ab.buildFace();
        ab.buildCostume();
        ab.buildHairstyle();
        actor = ab.createActor();
        return actor;
    }
}
複製代碼

編寫以下客戶端測試代碼:

Client.java

/** * Created by Coding小僧 on 2019/3/17 * * @since 1.0 * 客戶端測試代碼 */
public class Client {
    public static void main(String args[]) {
        //針對抽象建造者編程
        ActorBuilder angelBuilder = new AngelBuilder();
        // ActorBuilder heroBuilder = new HeroBuilder();
        // ActorBuilder devilBuilder = new DevilBuilder();
        
        ActorController actorController = new ActorController();

        //經過指揮者建立完整的建造者對象
        Actor actor = actorController.construct(angelBuilder);
        System.out.println(actor.getType() + "的外觀:");
        System.out.println("性別:" + actor.getSex());
        System.out.println("面容:" + actor.getFace());
        System.out.println("服裝:" + actor.getCostume());
        System.out.println("髮型:" + actor.getHairstyle());
    }
}
複製代碼

編譯並運行程序,輸出結果以下:

天使的外觀:
性別:女
面容:漂亮
服裝:白裙
髮型:披肩長髮
複製代碼

在建造者模式中,客戶端只需實例化指揮者類,指揮者類針對抽象建造者編程,客戶端根據須要傳入具體的建造者類型,指揮者將指導具體建造者一步一步構造一個完整的產品(逐步調用具體建造者的buildX()方法),相同的構造過程能夠建立徹底不一樣的產品。在遊戲角色實例中,若是須要更換角色,只須要修改配置文件,更換具體角色建造者類便可;若是須要增長新角色,能夠增長一個新的具體角色建造者類做爲抽象角色建造者的子類,再修改配置文件便可,原有代碼無須修改,徹底符合「開閉原則」。

典型應用

Android中普遍使用的AlertDialog彈窗是建造者模式的應用之一,這裏爲了節省篇幅,就不在詳細分析AlertDialog在源碼中的詳細調用流程。

AlertDialog時序圖

優勢

1.在建造者模式中,客戶端沒必要知道產品內部組成的細節,將產品自己與產品的建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象。

2.每個具體建造者都相對獨立,而與其餘的具體建造者無關,所以能夠很方便地替換具體建造者或增長新的具體建造者,用戶使用不一樣的具體建造者便可獲得不一樣的產品對象。因爲指揮者類針對抽象建造者編程,增長新的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合「開閉原則」。

3.能夠更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不一樣的方法中,使得建立過程更加清晰,也更方便使用程序來控制建立過程。

缺點

1.建造者模式所建立的產品通常具備較多的共同點,其組成部分類似,若是產品之間的差別性很大,例如不少組成部分都不相同,不適合使用建造者模式,所以其使用範圍受到必定的限制。

2.若是產品的內部變化複雜,可能會致使須要定義不少具體建造者類來實現這種變化,致使系統變得很龐大,增長系統的理解難度和運行成本。

重點

建造者模式的核心在於如何一步步構建一個包含多個組成部件的完整對象,使用相同的構建過程構建不一樣的產品,在軟件開發中,若是咱們須要建立複雜對象並但願系統具有很好的靈活性和可擴展性能夠考慮使用建造者模式。

使用場景

在如下狀況下能夠考慮使用建造者模式:

1.須要生成的產品對象有複雜的內部結構,這些產品對象一般包含多個成員屬性。

2.須要生成的產品對象的屬性相互依賴,須要指定其生成順序。

3.對象的建立過程獨立於建立該對象的類。在建造者模式中經過引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類和客戶類中。

4.隔離複雜對象的建立和使用,並使得相同的建立過程能夠建立不一樣的產品。

關注個人技術公衆號"程序員驛站",不更新技術文章,微信掃一掃下方二維碼便可關注:

相關文章
相關標籤/搜索