設計模式之建造者模式

序言

在看Retrofit源碼時,可以看到里面用到了大量的設計模式,如果我們非常了解設計模式對理解是很有幫助的,在Rerofit里有用到建造者模式。

定義

將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以生成不同的表示。建造者模式也是對象創建行模式。

說明: 單看這個定義初學者也是很蒙, 大概意思是不需要關心復雜對象創建過程,只需要知道建造者類型即可,建造者才關心這個復雜對象是如何一步一步創建一個復雜對象的。 根據不同類型的建造者生成不同的復雜的對象。也是有具有良好的擴展性。

建造者模式類圖

建造者模式類圖

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

  • Builder(抽象的建造者) : 它是創建Product對象各個部分的抽象接口。 在該接口一般有兩類接口 ,一種是biuldPartX()方法,用于創建該復雜對象各個部分; 另一種方法是getResult, 它用于返回這個復雜對象。 Build可以是一個接口,也可以是抽象類。
  • ConcreteBuilder(具體的建造者): 它實現了Builder接口,具體實現該復雜對象的各個部分,然后返回具體的復雜對象。
  • Product(產品角色): 它是復雜對象,里面包含該對象有哪些部分組成。
  • Director(指揮者):它負責對象的建造次序。指揮者和抽象的建造者存在關聯關系,可以在construct中完成復雜對象的創建過程。

上面有提到復雜對象,那什么是復雜對象呢,簡單來說就是包含多個成員屬性的對象。代碼示例如下:

//產品
class Product{
    private String partA;//定義產品的屬性,屬性可以是其它類型, 值類型和引用類型都行。
    private String partB;
    private String partC;
    //Setter和Getter方法省略就不寫了
}

在抽象建造者定義產品的創建方法和獲取產品對象的方法。代碼示例如下:

//抽象建造者
interface Builder{
    void buildPartA();//創建產品的屬性
    void buildPartB();
    void buildPartC();
    Product getResult(); //返回產品對象

}

抽象建造者中申明了一系列的buildPartX()方法,但是具體的實現還是需要具體的建造者。 不同的建造者實現的buildPartX有所區別。代碼示例如下:

//具體的建造者 A
public class AConcreteBuilder implements Builder {
Product productA=new Product();
@Override
public void buildPartA() {
    productA.setPartA("PartA from AConcreteBuilder");
}

@Override
public void buildPartB() {
    productA.setPartA("PartB from AConcreteBuilder");
}

@Override
public void buildPartC() {
    productA.setPartA("PartC from AConcreteBuilder");
}

@Override
public Product getResult() {
    return productA;
}
}

//具體的建造者 B
public class BConcreteBuilder implements Build {
Product productB=new Product();
@Override
public void buildPartA() {
    productB.setPartA("PartA from BConcreteBuilder");
}

@Override
public void buildPartB() {
    productB.setPartA("PartB from BConcreteBuilder");
}

@Override
public void buildPartC() {
    productB.setPartA("PartC from BConcreteBuilder");
}

@Override
public Product getResult() {
    return productB;
}
}

我們可以看到每個建造者實現的東西是不同的。但是返回的產品還是一類的,只是產品的屬性不一樣。

在建造者模式還有個Director角色(導演者或指揮者),該類主要有兩個作用: 其一就是控制產品的創建過程,包括biuldPartX方法是否被調用以及調用的順序; 其二 就是隔離了客戶與創建的過程, Director只需要指揮抽象的建造者(Build),客戶端需要具體的建造者然后調用指揮者的相關方法,返回一個完整的對象(Product)。 代碼示例如下

//指揮者
public class Director {
private Builder mBuilder; //抽象的建造者

public Director(Builder build) {
    this.mBuilder = build;
}
public Product construct(){
    mBuilder.buildPartA();
    mBuilder.buildPartB();
    mBuilder.buildPartC();
    return  mBuilder.getResult();
}
}

//客戶端   代碼片段如下
Builder Abuilder=new AConcreteBuilder();//創建具體的建造者
Director director=new Director(Abuilder);//創建指揮者
Product productA=director.construct();//返回產品A

Builder Bbuilder=new BConcreteBuilder();//創建具體的建造者
Director director=new Director(Bbuilder);//創建指揮者
Product productA=director.construct();//返回產品B

例子

如某個公司要開發個APP(Product),就需要開發者(Builder), 如果有多端的話就需要不同的開發者(ConcreteBuilder),技術經理(Director)就充當指揮者來指示不同的開發者來開發。

APP (Product)

public class APP {
    String code;//代碼
    String note;//注釋及文檔

    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getNote() {
        return note;
    }
    public void setNote(String note) {
        this.note = note;
    }

    @Override
    public String toString() {
        return "APP = {" +
                "code='" + code + '\'' +
                ", note='" + note + '\'' +
                '}';
    }
}

開發者 (Builder)

public interface Developer {
    void writeCode();//寫代碼

    void writeNote();//寫文檔及注釋

    APP getAPP();//返回APP
}

Andoird開發者(ConcreteBuilder)

public class AndroidDeveloper implements Developer {

    APP app = new APP();

    @Override
    public void writeCode() {
        app.setCode("Android code");
    }
    @Override
    public void writeNote() {
        app.setNote("Android note ");
    }
    @Override
    public APP getAPP() {
        return app;
    }
}

技術經理 (指揮者)

public class TechnicalManager {

    Developer mDeveloper;

    public void setDeveloper(Developer mDeveloper) {
        this.mDeveloper = mDeveloper;
    }

    public APP getAPP() {
        mDeveloper.writeCode();
        mDeveloper.writeNote();
        return mDeveloper.getAPP();
    }
}

測試:

    public static void main(String[] args) {
        TechnicalManager manager = new TechnicalManager();//創建技術經理對象(指揮者)
        AndroidDeveloper javaDeveloper = new AndroidDeveloper();//創建Android開發者(具體的建造者)
        manager.setDeveloper(javaDeveloper);//技術經理指揮Android程序員去干活
        APP app = manager.getAPP(); //完成 Android端 APP
        System.out.println(app);
    }

運行結果:

APP = {code='Android code', note='Android note'}

如果公司想在蘋果商店也要上款APP,那就需要ios開發,這樣我們就不用修改源代碼,只需要另創建一個ios的建造者就OK。

ios 開發者(ConcreteBuilder)

public class IosDeveloper implements Developer {
    APP app = new APP();

    @Override
    public void writeCode() {
    app.setCode("ios code");
    }

    @Override
    public void writeNote() {
        app.setNote("ios note");
    }

    @Override
    public APP getAPP() {
        return app;
    }
}

測試:

  public static void main(String[] args) {
    TechnicalManager manager = new TechnicalManager();//創建技術經理對象(指揮者)
    IosDeveloper iosDeveloper = new IosDeveloper();//創建ios開發者(具體的建造者)
    manager.setDeveloper(iosDeveloper);//技術經理指揮ios程序員去干活
    APP app = manager.getAPP(); //完成 ios端 APP
    System.out.println(app);
}

運行結果:

APP {code='ios code', note='ios note'}

如果需要其他端的APP,只需另創建個建造者就可以完成APP。

但是我們看大多數的源碼,里面的建造者模式是沒有指揮者的(Director),這個又是怎樣實現的呢。 在Android中的彈框(dialog)的實現就用了建造者模式,我們就大致實現下它是怎么創建的。 源碼中還是很復雜的,這里只是簡單實現。

在Android中創建一個dialog的代碼

   AlertDialog.Builder builder = new AlertDialog.Builder(this)
    .setTitle("提示")
    .setMessage("消息")
    .setIcon(R.drawable.icon);
    AlertDialog alertDialog=builder.create(); 

這是Android創建dialog最簡單的方式。 現在我們就實現這樣的寫法。

// Product
public class AlertDialog{
    String msg;//消息
    String tilte;//標題
    int icon; //圖標

    @Override
    public String toString() {
        return "AlertDialog{" +
                "msg='" + msg + '\'' +
                ", tilte='" + tilte + '\'' +
                ", icon=" + icon +
                '}';
}
//建造者  (Builder)
    static class Builder{
        private AlertDialog mDialog=new AlertDialog();

        public Builder setIcon(int icon){
            mDialog.icon=icon;
            return this;
        }

        public  Builder setTitle(String title){
        mDialog.tilte=title;
            return this;
        }

        public  Builder setMsg(String msg){
            mDialog.msg=msg;
            return this;
        }

        public AlertDialog create(){
            return  mDialog;
        }
    }
}

測試 :

public static void main(String args[]) {
    //因為Builder是AlertDialog的成員內部類,所以這樣調用AlertDialog.Builder  
    AlertDialog.Builder builder = new AlertDialog.Builder();
    builder.setIcon(1)
            .setMsg("消息")
            .setTitle("提示");
    AlertDialog dialog = builder.create();
    System.out.println(dialog);
}

運行結果:

AlertDialog{msg='消息', tilte='提示', icon=1}

可以看到我們也按照Android源碼這樣的調用方法創建了自己的dialog。 Android中的dialog實現比我這復雜,dialog屬性更多,邏輯更復雜。

我們可以再看看Retrofit中使用的建造者模式

   Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

這是創建Retrofit對象。可以看到也是用了建造者模式。 這里就不說了,下次分析源碼時再說。

總結:建造者模式只要明白四個角色Product、Builder、ConcreteBuilder、Director的作用,就差不多明白建造者模式是怎么回事了。

優點:

  • 將產品的本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象。
  • 每一個具體建造者都是相對獨立的,不依賴其他的具體建造者,可以方便地替換和增加新的具體建造者,用戶使用不同的具體建造者就能得到不同的產品對象。 由于指揮者(Director)是針對抽象的建造者(Builder)編程,增加新的建造者無需修改原來的建造者,符合‘開閉原則’。

缺點:

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

推薦閱讀更多精彩內容