Builder 模式

最近工作之余一直在斷斷續續的研究媒體選擇庫,在 GitHub 上搜了好多庫對比看了看,在學習研究過程中發現其中都運用了 Builder模式,今天就一起學習一下 Builder模式,順便看看它在 Android 源碼中的應用。

我們在實際開發中,必然會遇到一些需求需要構建一個十分復雜的對象,譬如本人最近開發的項目中就需要構建一個媒體庫選擇器,類似微信和眾多app中都有的圖片、視屏資源選擇器。這個選擇器(Selector)是相對比較復雜的,它需要很多屬性,比如:

  • 媒體資源類型: 圖片/視屏
  • 選擇模式: 單選/多選
  • 多選上限
  • 是否支持預覽
  • 是否支持裁剪
  • 選擇器UI風格樣式
  • ......

通常我們可以通過構造函數的參數形式去寫一個實現類

Selector(int type)

Selector(int type, int model)

Selector(int type, int model, int maxSize)

Selector(int type, int model, int maxsize, boolean isPreview)

......

再或者可以使用 getter 和 setter 方法去設置各個屬性

public class Selector {
    private int type; // 媒體資源類型: 圖片/視屏
    private int model; // 選擇模式: 單選/多選
    private int maxSize; // 多選上限
    private boolean isPreview; // 是否支持預覽
    
    private ...... // 更多屬性就不一一舉例了,大家腦部一下    

    public int getType() {
        return type;
    }

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

    public int getModel() {
        return model;
    }

    public void setModel(int model) {
        this.model = model;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public boolean isPreview() {
        return isPreview;
    }

    public void setPreview(boolean isPreview) {
        this.isPreview = isPreview;
    }

    .......
}

先分析一下這兩種構建對象的方式:

第一種方式通過重載構造方法實現,在參數不多的情況下,是比較方便快捷的,一旦參數多了,就會產生大量的構造方法,代碼可讀性大大降低,并且難以維護,對調用者來說也是一種災難;

第二種方法可讀性不錯,也易于維護,但是這樣子做對象會產生不確定性,當你構造 Selector 時想要傳入全部參數的時候,那你就必需將所有的 setter 方法調用完成之后才算創建完成。然而一部分的調用者看到了這個對象后,以為這個對象已經創建完畢,就直接使用了,其實 Selector 對象并沒有創建完成,另外,這個 Selector 對象也是可變的,不可變類的所有好處也將隨之消散。

寫到這里其實大家已經想到了,肯定有更好的辦法去解決這個問題,是的,你猜對了, 今天我們的主角 Builder模式 就是為解決這類問題而生的。下面我們一起看看 Builder模式 是如何優雅的處理這類尷尬的。

模式的定義

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

模式的使用場景

  1. 相同的方法,不同的執行順序,產生不同的事件結果時;
  2. 多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時;
  3. 產品類非常復雜,或者產品類中的調用順序不同產生了不同的效能,這個時候使用建造者模式非常合適;

化解上述尷尬的過程

Builder模式 屬于創建型,一步一步將一個復雜對象創建出來,允許用戶在不知道內部構建細節的情況下,可以更精細地控制對象的構造流程。還是上面同樣的需求,使用 Builder模式 實現如下:

public class Selector {
    private final int type; // 媒體資源類型: 圖片/視屏
    private final int model; // 選擇模式: 單選/多選
    private final int maxSize; // 多選上限
    private final boolean isPreview; // 是否支持預覽
    
    private final ...... // 更多屬性就不一一舉例了,大家腦部一下
    
    // 私有構造方法
    private Selector(SelectorBuilder selectorBuilder){
    this.type = selectorBuilder.type;
    this.model = selectorBuilder.model;
    this.maxSize = selectorBuilder.maxSize;
    this.isPreview = selectorBuilder.isPreview;
    ......

    /** 由于所有屬性都是 final 修飾的,所以只提供 getter 方法 **/

    public int getType() {
        return type;
    }

    public int getModel() {
        return model;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public boolean isPreview() {
        return isPreview;
    }

    .......
    
    // Builder 方法
    public static class SelectorBuilder{
        private int type; // 媒體資源類型: 圖片/視屏
        private int model; // 選擇模式: 單選/多選
        private int maxSize; // 多選上限
        private boolean isPreview; // 是否支持預覽
        
        private ...... // 更多屬性就不一一舉例了,大家腦部一下
        public SelectorBuilder() {
            // 設置各個屬性的默認值
            this.type = 1;
            this.model = 1;
            this.maxSize = 9;
            this.isPreview = true;
            ......
        }

        public SelectorBuilder setType(int type){
            this.type = type;
            return this;
        }

        public SelectorBuilder setModel(int model) {
            this.model = model;
            return this;
        }

        public SelectorBuilder setMaxSize(int maxSize) {
            this.maxSize = maxSize;
            return this;
        }

        ...... // 全部的setter方法就省略了

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

值得注意的是 Selector 的構造方法是私有的,并且所有屬性都是 final 修飾的,是不可變屬性,對調用者也只提供 getter 方法,SelectorBuilder 內部類可以根據調用者的具體需求隨意接收任意多個參數,應為我們再 SelectorBuilder 的構造方法中為每一個參數都設置了默認值,即使調用者調用時漏傳某個參數,也不會影響整個創建過程。當我們將我們需用的所有參數傳入后,隨后調用 build() 構造 Selector 對象,代碼如下:

new Selector.SelectorBuilder()
            .setType(2)
            .setModel(2)
            .setMaxSize(3)
            . ......
            .build();

是不是很簡潔?意不意外?驚不驚喜?你沒看錯,就是這么厲害!

Android源碼中的模式實現

在Android源碼中,我們最常用到的Builder模式就是AlertDialog.Builder, 使用該Builder來構建復雜的AlertDialog對象。簡單示例如下 :

// 顯示基本的AlertDialog  
private void showDialog(Context context) {  
    AlertDialog.Builder builder = new AlertDialog.Builder(context);  
    builder.setIcon(R.drawable.icon);  
    builder.setTitle("Title");  
    builder.setMessage("Message");  
    builder.setPositiveButton("Button1",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("點擊了對話框上的Button1");  
                }  
            });  
    builder.setNeutralButton("Button2",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("點擊了對話框上的Button2");  
                }  
            });  
    builder.setNegativeButton("Button3",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("點擊了對話框上的Button3");  
                }  
            });  
    builder.create().show();  // 構建AlertDialog, 并且顯示
} 

優缺點

當然在代碼世界,永遠沒有絕對的完美,我們只是在走向完美的道路上盡力去填補一個個坑而已。 Builder模式 有它的好處,給我們帶來了方便,但同時也會犧牲一些美好,這是不可避免的。

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

推薦閱讀更多精彩內容