設(shè)計模式03_建造者模式_builder

1.Builder模式是怎么來的

考慮這樣一個場景宝与,假如有一個類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ù)是非必要的氓拼。就好比在注冊用戶時盛正,用戶的姓和名是必填的吓坚,而年齡巍沙、手機號和家庭地址等是非必需的冗茸。那么問題就來了灸眼,如何創(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;
    }

這樣做的好處只有一個:可以成功運行突雪。但是弊端很明顯:

參數(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è)置setters和getters方法。就像下面一樣:

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ù)段标。那么缺點呢涯冠?也有兩點:

  • 對象會產(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);
        }
    }
}

有幾個重要的地方需要強調(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)點爽室,而沒有上述方法中的缺點汁讼。客戶端的代碼更容易寫阔墩,并且更重要的是嘿架,可讀性非常好。唯一可能存在的問題就是會產(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();

2.關(guān)于Builder的一點說明

線程安全問題
由于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);
}

3. 經(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模式尽纽,而是省略后的咐蚯。

轉(zhuǎn)載自
作者:湫水長天
鏈接:http://www.reibang.com/p/e2a2fe3555b9

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弄贿,隨后出現(xiàn)的幾起案子春锋,更是在濱河造成了極大的恐慌,老刑警劉巖差凹,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件期奔,死亡現(xiàn)場離奇詭異,居然都是意外死亡危尿,警方通過查閱死者的電腦和手機呐萌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谊娇,“玉大人肺孤,你說我怎么就攤上這事〖没叮” “怎么了赠堵?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長法褥。 經(jīng)常有香客問我茫叭,道長,這世上最難降的妖魔是什么挖胃? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任杂靶,我火速辦了婚禮梆惯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吗垮。我一直安慰自己垛吗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布烁登。 她就那樣靜靜地躺著怯屉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饵沧。 梳的紋絲不亂的頭發(fā)上锨络,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音狼牺,去河邊找鬼羡儿。 笑死,一個胖子當(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
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年宋舷,在試婚紗的時候發(fā)現(xiàn)自己被綠了绪撵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡祝蝠,死狀恐怖音诈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绎狭,我是刑警寧澤细溅,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站儡嘶,受9級特大地震影響喇聊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹦狂,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一誓篱、第九天 我趴在偏房一處隱蔽的房頂上張望朋贬。 院中可真熱鬧,春花似錦窜骄、人聲如沸锦募。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糠亩。三九已至,卻和暖如春准验,著一層夾襖步出監(jiān)牢的瞬間赎线,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工糊饱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垂寥,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓济似,卻偏偏與公主長得像矫废,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子砰蠢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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