設(shè)計(jì)模式:Builder模式(譯)

Builder模式是一種創(chuàng)建模式 - 換句話說(shuō),它用于創(chuàng)建和初始化對(duì)象。

問(wèn)題

假設(shè)我們是一個(gè)Java團(tuán)隊(duì),為一家銀行開(kāi)發(fā)軟件。 我們需要一個(gè)類(lèi)來(lái)表示銀行賬戶(hù)。 以下是BankAccount類(lèi)的第一個(gè)版本(請(qǐng)注意,使用double作為貨幣的數(shù)據(jù)類(lèi)型是一個(gè)壞主意 )。

public class BankAccount {
    private long accountNumber;
    private String owner;
    private double balance;
    public BankAccount(long accountNumber, String owner, double balance) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = balance;
    }
    //Getters and setters omitted for brevity.
}

非常簡(jiǎn)單 - 我們可以按如下方式使用它。

BankAccount account = new BankAccount(123L, "Bart", 100.00);

很遺憾,我們要不停添加新功能。 一個(gè)新需求是,我們要跟蹤每個(gè)帳戶(hù)的利率,開(kāi)戶(hù)日期,以及可選的開(kāi)設(shè)分行。 這聽(tīng)起來(lái)很容易,所以我們做出了BankAccount類(lèi)的2.0版本。

public class BankAccount {
    private long accountNumber;
    private String owner;
    private String branch;
    private double balance;
    private double interestRate;
    public BankAccount(long accountNumber, String owner, String branch, double balance, double interestRate) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.branch = branch;
        this.balance = balance;
        this.interestRate = interestRate;
   }
    //Getters and setters omitted for brevity.
}

于是我們使用以下方式來(lái)創(chuàng)建對(duì)象

BankAccount account = new BankAccount(456L, "Marge", "Springfield", 100.00, 2.5);
BankAccount anotherAccount = new BankAccount(789L, "Homer", null, 2.5, 100.00);  //Oops!

這段代碼可以編譯通過(guò),但是實(shí)際上是錯(cuò)誤的,Homer的錢(qián)每個(gè)月都會(huì)增加一倍。 提示:密切關(guān)注傳遞給構(gòu)造函數(shù)的參數(shù)的順序。

如果我們有多個(gè)相同類(lèi)型的連續(xù)參數(shù),很容易出現(xiàn)類(lèi)似的錯(cuò)誤, 并且這樣的錯(cuò)誤很難被發(fā)現(xiàn)。 另外,太多的構(gòu)造函數(shù)參數(shù)會(huì)導(dǎo)致代碼變得難以閱讀。 如果我們有10個(gè)不同的參數(shù),根本無(wú)法快速理解每個(gè)參數(shù)的含義。 更糟糕的是,其中一些值可能是可選的,這意味著我們需要?jiǎng)?chuàng)建多個(gè)不同的構(gòu)造函數(shù)來(lái)處理所有可能的組合,否則我們必須將null傳遞給構(gòu)造函數(shù)(丑陋!)。

你可能認(rèn)為我們可以不寫(xiě)構(gòu)造函數(shù)然后通過(guò)setter方法設(shè)置參數(shù)。 但是,這將帶來(lái)另外的問(wèn)題 - 如果開(kāi)發(fā)人員忘記調(diào)用必須要調(diào)用的setter方法怎么辦? 我們最終可能會(huì)得到一個(gè)只是部分初始化的對(duì)象,同樣,這樣的問(wèn)題編譯階段無(wú)法發(fā)現(xiàn),很難被追蹤。

因此,我們需要解決兩個(gè)問(wèn)題:

  • 構(gòu)造函數(shù)參數(shù)太多。
  • 對(duì)象狀態(tài)不正確。

這是Builder模式發(fā)揮作用的地方。

Builder模式

Builder模式允許我們編寫(xiě)可讀,易懂的代碼來(lái)初始化復(fù)雜的對(duì)象。你可能已經(jīng)在Apache Camel或Hamcrest等工具中看到過(guò)類(lèi)似的用法 。

首先我們需要?jiǎng)?chuàng)建一個(gè)新類(lèi):構(gòu)建器類(lèi),它將包含BankAccount類(lèi)中所有字段。 我們用構(gòu)造器類(lèi)來(lái)配置每個(gè)參數(shù),然后使用這些參數(shù)來(lái)返回BankAccount類(lèi)的對(duì)象。 同時(shí),我們將從BankAccount類(lèi)中刪除公共構(gòu)造函數(shù),并將其替換為私有構(gòu)造函數(shù),以便只能通過(guò)構(gòu)建器創(chuàng)建BankAccount對(duì)象。

如下:

public class BankAccount {
    public static class Builder {
        private long accountNumber; //This is important, so we'll pass it to the constructor.
        private String owner;
        private String branch;
        private double balance;
        private double interestRate;
        public Builder(long accountNumber) {
            this.accountNumber = accountNumber;
        }
        public Builder withOwner(String owner){
            this.owner = owner;
            return this;  //By returning the builder each time, we can create a fluent interface.
        }
        public Builder atBranch(String branch){
            this.branch = branch;
            return this;
        }
        public Builder openingBalance(double balance){
            this.balance = balance;
            return this;
        }
        public Builder atRate(double interestRate){
            this.interestRate = interestRate;
            return this;
        }
        public BankAccount build(){
            //Here we create the actual bank account object, which is always in a fully initialised state when it's returned.
            BankAccount account = new BankAccount();  //Since the builder is in the BankAccount class, we can invoke its private constructor.
            account.accountNumber = this.accountNumber;
            account.owner = this.owner;
            account.branch = this.branch;
            account.balance = this.balance;
            account.interestRate = this.interestRate;
            return account;
        }
    }
    //Fields omitted for brevity.
    private BankAccount() {
        //Constructor is now private.
    }
    //Getters and setters omitted for brevity.
}

我們現(xiàn)在可以創(chuàng)建新帳戶(hù),如下所示。

BankAccount account = new BankAccount.Builder(1234L)
            .withOwner("Marge")
            .atBranch("Springfield")
            .openingBalance(100)
            .atRate(2.5)
            .build();
BankAccount anotherAccount = new BankAccount.Builder(4567L)
            .withOwner("Homer")
            .atBranch("Springfield")
            .openingBalance(100)
            .atRate(2.5)
            .build();

這樣的代碼大大提高了可讀性。

總結(jié)

我們通過(guò)一個(gè)示例開(kāi)始,代碼從簡(jiǎn)單開(kāi)始,然后變得越來(lái)越復(fù)雜。 然后,我們使用Builder模式來(lái)解決我們發(fā)現(xiàn)的問(wèn)題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,666評(píng)論 1 32
  • 第3章 基本概念 3.1 語(yǔ)法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類(lèi)型 5種簡(jiǎn)單數(shù)據(jù)類(lèi)型:Unde...
    RickCole閱讀 5,527評(píng)論 0 21
  • 天天大爺躺 曬著太陽(yáng)盯路人 閑言碎語(yǔ)迷糊蛋 總是惹得仙人樂(lè) 眾生宜遠(yuǎn)不宜近
    縱情嬉戲天地間閱讀 207評(píng)論 0 0
  • 之前在一本書(shū)上看到過(guò)一句話:人類(lèi)唯一能從歷史中學(xué)到的東西就是,什么也學(xué)不會(huì)。當(dāng)時(shí)看到這句話覺(jué)得很震驚,這寫(xiě)的是什么...
    漢智平閱讀 116評(píng)論 0 0
  • 曾無(wú)意中聽(tīng)到高中的政治老師說(shuō)過(guò):相見(jiàn)不如懷念! 很多時(shí)候我都拿只句話告訴我自己:分別也不一定是壞事,有時(shí)候相見(jiàn)還不...
    熬夜的犀牛閱讀 539評(píng)論 0 0

友情鏈接更多精彩內(nèi)容