嘻哈說:設計模式之建造者模式

番茄課堂_懶人發表於2018-10-10

1、嘻哈說

首先,請您欣賞建造者模式的原創歌曲

嘻哈說:建造者模式
作曲:懶人
作詞:懶人
Rapper:懶人

將一個複雜物件的構建與它的表示分離
使得同樣構建過程可以建立不同的表示
真是一步一步建立一個複雜物件的過程好似紳士
我在絞盡腦汁想著怎麼包餃子
先皮再陷的構建過程不會存在貓膩
但皮和陷各有不同需要各位老師傅
我就是導演者,負責構建的整個過程
老師傅呢,是具體建造者,建造每個部件幹活不磨蹭
餃子是產品角色,由一系列部件組成,不陌生
對了,記得還有抽象建造者
這樣才能說聲建造者模式可用
或者產品類中有個靜態內部類Builder
這種方式算是折中
複製程式碼

試聽請點選這裡

閒來無事聽聽曲,知識已填腦中去;

學習複習新方式,頭戴耳機不小覷。

番茄課堂,學習也要酷。

2、定義

無論是在現實世界中還是在軟體系統中,都存在一些複雜的物件,它們擁有多個組成部分,如電腦,它包括CPU、硬碟、記憶體等各種部件。

對於大多數使用者而言,無須知道這些部件的裝配細節,也幾乎不會使用單獨某個部件,而是完整的一臺電腦。

由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法確相對穩定。如何應對這種變化?

如何提供一種“封裝機制”來隔離出“複雜物件的各個部分”的變化,從而保持系統中的 “穩定構建演算法”不隨著需求改變而改變?

這就是要說的建造者模式。

使用建造者模式,使用者只需要指定複雜物件的型別就可以得到該物件,而無須知道其內部的具體構造細節。

我們來看一下建造者模式的定義。

將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

怎麼理解呢?

首先,是一個複雜的物件,並且將它的構造和表示分開。

這樣做的目的是可以單獨管理構建的過程,避免了呼叫者關心建立物件內部的邏輯或者順序。

而且還可以用一樣的構造過程建立不同的表示。

它既然是用來建立物件,那肯定就是建立型模式

3、場景

美食餃子

番茄餐廳的後廚。

廚師長:最近有不少客戶反饋,我們們的餃子上菜速度比較慢。我呢,重新規劃了一下流程,首先擀餃子皮,然後絞餃子餡,最後搞熟。

廚師A:好的。

廚師長:你們不用管這個流程,我來負責流程。你們就負責擀餃子皮,絞餃子餡,還有搞熟。我讓你們做哪步就做哪步。明白沒?

廚師A:明白了。

廚師長:明白了還不趕緊幹活去,這一天天的,什麼時候能像我一樣,求生欲這麼強,我們們上菜速度就上去了。

4、建造者模式

嘻哈說:設計模式之建造者模式

建造者模式的UML類圖。

package com.fanqiekt.builder;

/**
 * 餃子
 *
 * @Author: 番茄課堂-懶人
 */
public class Dumpling {
    private String wrapper; //餃子皮
    private String stuffing; //餃子餡

    public String getWrapper() {
        return wrapper;
    }

    public void setWrapper(String wrapper) {
        this.wrapper = wrapper;
    }

    public String getStuffing() {
        return stuffing;
    }

    public void setStuffing(String stuffing) {
        this.stuffing = stuffing;
    }

    @Override
    public String toString() {
        return "餃子皮是" + wrapper + ",餃子餡是" + stuffing;
    }
}
複製程式碼

Dumpling:它是產品(Product)角色,由一系列部件組成。

一般是一個較為複雜的物件,也就是說建立物件的過程比較複雜,一般會有比較多的程式碼量。

在本類圖中,產品類是一個具體的類,而非抽象類。

實際程式設計中,產品類可以是由一個抽象類與它的不同實現組成,也可以是由多個抽象類與他們的實現組成。

package com.fanqiekt.builder;

/**
 * 抽象的Builder類
 *
 * @Author: 番茄課堂-懶人
 */
public interface Builder {
    // 餃子皮
    void buildWrapper(String wrapper);
    // 餃子餡
    void buildStuffing(String stuffing);
    // 建立Dumpling
    Dumpling build();
}
複製程式碼

Builder:它是抽象建造者(Builder)角色,給出一個抽象介面,以規範產品物件的各個組成成分的建造。

一般而言,此介面獨立於應用程式的業務邏輯。

模式中直接建立產品物件的是具體建造者 (ConcreteBuilder)角色。

一般來說,產品所包含的零件數目與建造方法的數目相符。換言之,有多少零件,就有多少相應的建造方法。

引入抽象建造者的目的,是為了將建造的具體過程交與它的子類來實現,方便擴充套件其他的建造方式。

package com.fanqiekt.builder;

/**
 * 具體的 Builder類, 水餃的Builder
 *
 * @Author: 番茄課堂-懶人
 */
public class DumplingBuilder implements Builder{
    private Dumpling dumpling = new Dumpling();

    @Override
    public void buildWrapper(String wrapper) {
        System.out.println("製作餃子皮:" + wrapper);
        dumpling.setWrapper(wrapper);
    }

    @Override
    public void buildStuffing(String stuffing) {
        System.out.println("製作餃子陷:" + stuffing);
        dumpling.setStuffing(stuffing);
    }

    @Override
    public Dumpling build() {
        System.out.println("下鍋煮餃子");
        return dumpling;
    }
}
複製程式碼

DumplingBuilder:它是具體建造者(ConcreteBuilder)角色。

實現抽象類的所有未實現的方法,具體來說一般是兩項任務:組建產品;返回組建好的產品。

它就相當於場景中的廚師A。

package com.fanqiekt.builder;

/**
 * 導演者
 *
 * @Author: 番茄課堂-懶人
 */
public class Director {
    Builder builder;

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

    public void constructDumpling(String wrapper, String stuffing){
        builder.buildWrapper(wrapper);
        builder.buildStuffing(stuffing);
    }
}
複製程式碼

Director:它是導演者(Director)角色,負責呼叫適當的建造者來組建產品。

導演類一般不與產品類發生依賴關係,與導演類直接互動的是建造者類。

一般來說,導演類被用來封裝程式中易變、及其複雜的部分。

package com.fanqiekt.builder;

/**
 * 客戶端
 *
 * @Author: 番茄課堂-懶人
 */
public class Client {
    public static void main(String args[]){
        Builder builder = new DumplingBuilder();
        Director director = new Director(builder);
        director.constructDumpling("白麵餃子皮", "豬肉大蔥餡");
        Dumpling dumpling = builder.build();
        System.out.println("餃子出鍋嘍:" + dumpling.toString());
    }
}

複製程式碼

導演者角色是與客戶端打交道的角色。

導演者將客戶端建立產品的請求劃分為對各個零件的建造請求,再將這些請求委派給具體建造者角色。

具體建造者角色是做具體建造工作的,但是卻不為客戶端所知。

在實際應用中,很多會把該角色省略。

他就相當於場景中求生欲極強的廚師長。

我們可以執行下,看一下結果。

製作餃子皮:白麵餃子皮
製作餃子陷:豬肉大蔥餡
下鍋煮餃子
餃子出鍋嘍:餃子皮是白麵餃子皮,餃子餡是豬肉大蔥餡

複製程式碼

如果我現在不止想做水餃,我還想做個蒸餃怎麼辦?

很簡單,直接建立個新的Builder。

package com.fanqiekt.builder;

/**
 * 具體的 Builder類, 蒸餃的Builder
 *
 * @Author: 番茄課堂-懶人
 */
public class SteamedDumplingBuilder implements Builder{
    private Dumpling dumpling = new Dumpling();

    @Override
    public void buildWrapper(String wrapper) {
        System.out.println("製作餃子皮:" + wrapper);
        dumpling.setWrapper(wrapper);
    }

    @Override
    public void buildStuffing(String stuffing) {
        System.out.println("製作餃子陷:" + stuffing);
        dumpling.setStuffing(stuffing);
    }

    @Override
    public Dumpling build() {
        System.out.println("放在蒸籠上蒸餃子");
        return dumpling;
    }
}
複製程式碼

這就是抽象建造者的作用了。

5、建造者模式變種

您是不是感覺建造者角色太多,太麻煩了?

這個時候,我們建造者的變種就該登臺亮相了。

嘻哈說:設計模式之建造者模式

建造者模式變種的UML類圖。

package com.fanqiekt.builder.varietas;

/**
 * 餃子
 *
 * @Author: 番茄課堂-懶人
 */
public class Dumpling {
    Params params = new Params();

    private Dumpling(){
    }

    private String getWrapper() {
        return params.wrapper;
    }

    public String getStuffing() {
        return params.stuffing;
    }

    @Override
    public String toString() {
        return "餃子皮是" + getWrapper() + ",餃子餡是" + getStuffing();
    }

    /**
     * 靜態內部類 Builder
     */
    public static class Builder {
        Params params = new Params();

        public Builder setWrapper(String wrapper) {
            params.wrapper = wrapper;
            return this;
        }

        public Builder setStuffing(String stuffing) {
            params.stuffing = stuffing;
            return this;
        }

        public Dumpling build() {
            Dumpling dumpling = new Dumpling();
            params.apply(dumpling.params);
            System.out.println("下鍋煮餃子");
            return dumpling;
        }
    }

    private static class Params {
        private String wrapper; //餃子皮
        private String stuffing; //餃子餡

        private void apply(Params params){
            System.out.println("製作餃子皮:" + wrapper);
            params.wrapper = wrapper;
            System.out.println("製作餃子陷:" + stuffing);
            params.stuffing = stuffing;
        }
    }
}
複製程式碼

在變種當中,抽象建造者角色沒有了,導演者角色沒有了,只剩下產品角色和具體建造者角色了。

具體建造者變成了產品的私有靜態內部類。

增加了Params靜態內部類,它是用來封裝用到的屬性的。

為什麼要專門封裝這麼一個類呢?

如果沒有的話,則Dumpling會擁有wrapper、stuffing屬性。Builder中也要擁有wrapper、stuffing屬性。

為了屬性的統一性,就增加了Params靜態內部類。

6、用途

建造者模式是一步一步建立一個複雜的物件,它允許使用者只通過指定複雜物件的型別和內容就可以構建它們,使用者不需要知道內部的具體構建細節。

所以,它的作用主要體現在兩方面:複雜物件的構建、忽略具體的構建細節。

大概用途是:

(1)遇到多個構造器引數時。

(2)相同的方法,不同的執行順序,產生不同的事件結果時。

(3)需要生成的產品物件的屬性相互依賴,建造者模式可以強迫生成順序。

7、優點

良好的封裝性。

使用建造者模式可以使客戶端不必知道產品內部的組成細節。

良好的擴充套件性。

增加新的具體建造者無須修改原有類庫的程式碼,指揮者類針對抽象建造者類程式設計,系統擴充套件方便。

更加精細化。

可以更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不同的方法中,使得建立過程更加清晰,也更方便使用程式來控制建立過程。

建立不同的產品

將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。

使用者使用不同的具體建造者即可得到不同的產品物件。

8、缺點

系統龐大。

如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大。

使用侷限性。

建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制。

9、END

今天就先說到這裡,下次是工廠模式,感謝大家。

相關文章