設(shè)計模式之Builder模式

當(dāng)我第一次使用Picasso的時候烹骨,看見下面的官網(wǎng)示例時吨岭,我和我的小伙伴都驚呆了!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

如此簡潔明了的使用方式急灭,如此靈活多變的鏈?zhǔn)秸{(diào)用,讓我深深地迷住了畴嘶,然后我一直苦苦追求它,奈何天資愚笨蒋院,不知如何掀起它的神秘面紗铸屉,直到我在網(wǎng)上找到了這篇教程...

不好意思顷啼,中二病又犯了茵瀑,重來一遍。經(jīng)過不懈的努力,終于發(fā)現(xiàn)它就是傳說中的Builder(建造者)模式匙奴,并學(xué)會了如何與它親密相處。

Builder模式是怎么來的

不知道是哪位賢人曾經(jīng)說過,存在即為合理焊刹。Builder模式在眾多的框架以及android原生代碼中存在(比如AlertDialog),就一定有其價值非凌,用來解決某些需求颁糟。

考慮這樣一個場景棱貌,假如有一個類(****User****)勺像,里面有很多屬性吟宦,并且你希望這些類的屬性都是不可變的(final)袁波,就像下面的代碼:

public class User {

    private final String firstName;     // 必傳參數(shù)
    private final String lastName;      // 必傳參數(shù)
    private final int age;              // 可選參數(shù)
    private final String phone;         // 可選參數(shù)
    private final String address;       // 可選參數(shù)
}

在這個類中娃磺,有些參數(shù)是必要的偷卧,而有些參數(shù)是非必要的蚕泽。就好比在注冊用戶時仔蝌,用戶的姓和名是必填的绰更,而年齡执俩、手機(jī)號和家庭地址等是非必需的宋税。那么問題就來了矮台,如何創(chuàng)建這個類的對象呢蛤迎?

一種可行的方案就是實用構(gòu)造方法辆童。第一個構(gòu)造方法只包含兩個必需的參數(shù),第二個構(gòu)造方法中,增加一個可選參數(shù),第三個構(gòu)造方法中再增加一個可選參數(shù)使套,依次類推奉呛,直到構(gòu)造方法中包含了所有的參數(shù)咆槽。

    public User(String firstName, String lastName) {
        this(firstName, lastName, 0);
    }

    public User(String firstName, String lastName, int age) {
        this(firstName, lastName, age, "");
    }

    public User(String firstName, String lastName, int age, String phone) {
        this(firstName, lastName, age, phone, "");
    }

    public User(String firstName, String lastName, int age, String phone, String address) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.phone = phone;
        this.address = address;
    }

這樣做的好處只有一個:可以成功運(yùn)行。但是弊端很明顯:

  • 參數(shù)較少的時候問題還不大,一旦參數(shù)多了,代碼可讀性就很差谬擦,并且難以維護(hù)惨远。
  • 對調(diào)用者來說也很麻煩。如果我只想多傳一個address參數(shù)蔑水,還必需給age歇父、phone設(shè)置默認(rèn)值垂睬。而且調(diào)用者還會有這樣的困惑:我怎么知道第四個String類型的參數(shù)該傳address還是phone檐春?

第二種解決辦法就出現(xiàn)了俐巴,我們同樣可以根據(jù)JavaBean的習(xí)慣缘圈,設(shè)置一個空參數(shù)的構(gòu)造方法遣疯,然后為每一個屬性設(shè)置settersgetters方法文判。就像下面一樣:

public class User {

    private String firstName;     // 必傳參數(shù)
    private String lastName;      // 必傳參數(shù)
    private int age;              // 可選參數(shù)
    private String phone;         // 可選參數(shù)
    private String address;       // 可選參數(shù)

    public User() {
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }
}

這種方法看起來可讀性不錯赏殃,而且易于維護(hù)抗蠢。作為調(diào)用者,創(chuàng)建一個空的對象,然后只需傳入我感興趣的參數(shù)笔宿。那么缺點(diǎn)呢捧存?也有兩點(diǎn):

  • 對象會產(chǎn)生不一致的狀態(tài)。當(dāng)你想要傳入5個參數(shù)的時候,你必需將所有的setXX方法調(diào)用完成之后才行闲孤。然而一部分的調(diào)用者看到了這個對象后舆绎,以為這個對象已經(jīng)創(chuàng)建完畢波岛,就直接食用了,其實User對象并沒有創(chuàng)建完成曹鸠。
  • ****User****類是可變的了眠屎,不可變類所有好處都不復(fù)存在橄镜。

終于輪到主角上場的時候了,利用Builder模式舔亭,我們可以解決上面的問題肢预,代碼如下:

public class User {

    private final String firstName;     // 必傳參數(shù)
    private final String lastName;      // 必傳參數(shù)
    private final int age;              // 可選參數(shù)
    private final String phone;         // 可選參數(shù)
    private final String address;       // 可選參數(shù)

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }

    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }

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

有幾個重要的地方需要強(qiáng)調(diào)一下:

  • ****User****類的構(gòu)造方法是私有的辫红。也就是說調(diào)用者不能直接創(chuàng)建User對象厉熟。
  • ****User****類的屬性都是不可變的滤馍。所有的屬性都添加了final修飾符阁苞,并且在構(gòu)造方法中設(shè)置了值。并且蜻展,對外只提供getters方法纵顾。
  • Builder模式使用了鏈?zhǔn)秸{(diào)用《绊铮可讀性更佳施逾。
  • Builder的內(nèi)部類構(gòu)造方法中只接收必傳的參數(shù),并且該必傳的參數(shù)適用了final修飾符。

相比于前面兩種方法汉额,Builder模式擁有其所有的優(yōu)點(diǎn)曹仗,而沒有上述方法中的缺點(diǎn)∪渌眩客戶端的代碼更容易寫怎茫,并且更重要的是,可讀性非常好妓灌。唯一可能存在的問題就是會產(chǎn)生多余的Builder對象轨蛤,消耗內(nèi)存。然而大多數(shù)情況下我們的Builder內(nèi)部類使用的是靜態(tài)修飾的(static)虫埂,所以這個問題也沒多大關(guān)系祥山。

現(xiàn)在,讓我們看看如何創(chuàng)建一個User對象呢掉伏?

new User.UserBuilder("王", "小二")
                .age(20)
                .phone("123456789")
                .address("亞特蘭蒂斯大陸")
                .build();

相當(dāng)整潔缝呕,不是嗎?你甚至可以用一行代碼完成對象的創(chuàng)建斧散。

關(guān)于Builder的一點(diǎn)說明

線程安全問題

由于Builder是非線程安全的供常,所以如果要在Builder內(nèi)部類中檢查一個參數(shù)的合法性,必需要在對象創(chuàng)建完成之后再檢查颅湘。

public User build() {
  User user = new user(this);
  if (user.getAge() > 120) {
    throw new IllegalStateException(“Age out of range”); // 線程安全
  }
  return user;
}

上面的寫法是正確的话侧,而下面的代碼是非線程安全的:

public User build() {
  if (age > 120) {
    throw new IllegalStateException(“Age out of range”); // 非線程安全
  }
  return new User(this);
}

經(jīng)典的Builder模式

上面介紹的Builder模式當(dāng)然不是“原生態(tài)”的啦,經(jīng)典的Builder模式的類圖如下:

builder

其中:

  • Product 產(chǎn)品抽象類闯参。
  • Builder 抽象的Builder類瞻鹏。
  • ConcretBuilder 具體的Builder類。
  • Director 同一組裝過程鹿寨。

當(dāng)然新博,之前實例中的Builder模式,是省略掉了Director的脚草,這樣結(jié)構(gòu)更加簡單赫悄。所以在很多框架源碼中,涉及到Builder模式時馏慨,大多都不是經(jīng)典GOF的Builder模式埂淮,而是省略后的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末写隶,一起剝皮案震驚了整個濱河市倔撞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慕趴,老刑警劉巖痪蝇,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鄙陡,死亡現(xiàn)場離奇詭異,居然都是意外死亡躏啰,警方通過查閱死者的電腦和手機(jī)趁矾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來给僵,“玉大人毫捣,你說我怎么就攤上這事∠爰剩” “怎么了培漏?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胡本。 經(jīng)常有香客問我牌柄,道長,這世上最難降的妖魔是什么侧甫? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任珊佣,我火速辦了婚禮,結(jié)果婚禮上披粟,老公的妹妹穿的比我還像新娘咒锻。我一直安慰自己,他們只是感情好守屉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布惑艇。 她就那樣靜靜地躺著,像睡著了一般拇泛。 火紅的嫁衣襯著肌膚如雪滨巴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天俺叭,我揣著相機(jī)與錄音恭取,去河邊找鬼。 笑死熄守,一個胖子當(dāng)著我的面吹牛蜈垮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裕照,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼攒发,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晋南?” 一聲冷哼從身側(cè)響起晨继,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搬俊,沒想到半個月后紊扬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唉擂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年餐屎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玩祟。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡腹缩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出空扎,到底是詐尸還是另有隱情藏鹊,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布转锈,位于F島的核電站盘寡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏撮慨。R本人自食惡果不足惜竿痰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砌溺。 院中可真熱鬧影涉,春花似錦、人聲如沸规伐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猖闪。三九已至鲜棠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萧朝,已是汗流浹背岔留。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留检柬,地道東北人献联。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像何址,于是被迫代替她去往敵國和親里逆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 前言 : Android中的AlertDialog用的就是builder設(shè)計模式用爪,圖片加載控件Universal-...
    正陽Android閱讀 2,759評論 0 0
  • 引言 繼續(xù)走我的設(shè)計模式模式的進(jìn)度條原押,今天輪到了Builder模式,這個模式吧我不知道該說些什么偎血,它使用的還是挺廣...
    蟲兒飛ZLEI閱讀 1,076評論 1 0
  • 一诸衔、定義 講一個復(fù)雜對象的構(gòu)建與他的表示分離盯漂,使用同樣的構(gòu)建過程實現(xiàn)不同的表示; 分類:創(chuàng)建型模式 二笨农、使用場景 ...
    夢語少年閱讀 413評論 2 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理就缆,服務(wù)發(fā)現(xiàn),斷路器谒亦,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 有懷常不釋竭宰,一語一心酸。 此地暫胡馬份招,終身只宋民切揭。 南宋被滅,文天祥兵敗被捕锁摔,忽必烈的鐵騎踏盡江南廓旬。其間,以身殉國...
    故城酒兮閱讀 477評論 0 4