Java 大白話講解設計模式之 -- 建造者(Builder)模式

聲明:原創作品,轉載請註明出處https://www.jianshu.com/p/afe090b2e19c

建造者模式是日常開發中比較常見的設計模式,它的主要作用就是將複雜事物創建的過程抽象出來,該抽象的不同實現方式不同,創建出的對象也不同。通俗的講,創建一個對象一般都會有一個固定的步驟,這個固定的步驟我們把它抽象出來,每個抽象步驟都會有不同的實現方式,不同的實現方式創建出的對象也將不同。舉個常見的例子,想必大家都買過電腦,電腦的生產或者組裝其實就是屬於建造者模式,我們知道,電腦的生產都需要安裝CPU、內存條、硬盤等元器件。我們可以把這個安裝步驟抽象出來,至於到底裝哪種CPU,比如i5還是i7就是對該抽象安裝步驟的具體實現。

建造者模式分爲兩種,一種爲經典建造者模式,另一種爲變種建造者模式。我們來挨個看下:

經典Builder模式

我們先來看下經典Builder模式,先上UML類圖:

對UML類圖不瞭解可以看我之前的這篇文章Java 大白話講解設計模式之 -- UML類圖

從上圖可以看到,經典Buider模式中有四個角色:

  1. 要建造的產品Product -- 組裝的電腦
  2. 抽象的Builder -- 裝CPU、內存條、硬盤等抽象的步驟
  3. Builder的具體實現ConcreteBuilder -- 對上述抽象步驟的實現,比如裝i5CPU、8G內存條、1T硬盤
  4. 使用者Director -- 電腦裝機人員

接下來我們來看下用代碼如何實現上述組裝電腦的過程:

1.首先我們先來創建一個Computer類:

public class Computer {
    /*CPU*/
    private String CPU;
    /*內存*/
    private String memory;
    /*硬盤*/
    private String hardDisk;
    /*鍵盤*/
    private String keyboard;
    /*鼠標*/
    private String mouse;

    public String getCPU() {
        return CPU;
    }

    public void setCPU(String CPU) {
        this.CPU = CPU;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public String getMouse() {
        return mouse;
    }

    public void setMouse(String mouse) {
        this.mouse = mouse;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "CPU='" + CPU + '\'' +
                ", memory='" + memory + '\'' +
                ", hardDisk='" + hardDisk + '\'' +
                ", keyboard='" + keyboard + '\'' +
                ", mouse='" + mouse + '\'' +
                '}';
    }
}

很簡單,可以看到這個Computer類中有五個基本屬性CPU、內存條、硬盤、鍵盤和鼠標,然後還有一個toString方法,用於之後方便打印信息用。

2.接下來我們來創建一個抽象的電腦組裝過程的Builder類:

public interface ComputerConfigBuilder {
    void setCPU();
    void setMemery();
    void setHardDisk();
    void setKeyboard();
    void setMouse();
    Computer getComputer();
}

電腦組裝一般都需要安裝CPU、內存條、硬盤、鍵盤鼠標等,我們把這一安裝過程給抽象出來,也就是這裏的ComputerConfigBuilder ,至於具體安裝什麼需要其實現類來實現,另外其中還定義了一個獲取Conputer的方法。

3.好了,有了抽象的組裝過程,接下來我們就需要創建具體的實現類。我們知道電腦一般都有低配版和高配版,不同配置,組裝成的電腦自然就不一樣。接下我們首先來創建一個低配版的套餐LowConfigBuilder ,讓其實現ComputerConfigBuilder:

public class LowConfigBuilder implements ComputerConfigBuilder {

    private Computer mComputer;

    public LowConfigBuilder(){
        this.mComputer = new Computer();
    }

    @Override
    public void setCPU() {
        mComputer.setCPU("i5");
    }

    @Override
    public void setMemery() {
        mComputer.setMemory("8G");
    }

    @Override
    public void setHardDisk() {
        mComputer.setHardDisk("500G");
    }

    @Override
    public void setKeyboard() {
        mComputer.setKeyboard("薄膜鍵盤");
    }

    @Override
    public void setMouse() {
        mComputer.setMouse("有線鼠標");
    }

    @Override
    public Computer getComputer() {
        return mComputer;
    }
}

可以看到這個低配版的配置爲:i5的CPU、8G內存、500G硬盤、薄膜鍵盤和有線鼠標。

接着我們再創建一個高配版的套餐:

public class HighConfigBuider implements ComputerConfigBuilder {

    private Computer mComputer;

    public HighConfigBuider(){
        this.mComputer = new Computer();
    }

    @Override
    public void setCPU() {
        mComputer.setCPU("i7");
    }

    @Override
    public void setMemery() {
        mComputer.setMemory("16G");
    }

    @Override
    public void setHardDisk() {
        mComputer.setHardDisk("1T");
    }

    @Override
    public void setKeyboard() {
        mComputer.setKeyboard("機械鍵盤");
    }

    @Override
    public void setMouse() {
        mComputer.setMouse("無線鼠標");
    }

    @Override
    public Computer getComputer() {
        return mComputer;
    }
}

可以看到這個高配版的配置爲:i7的CPU、16G內存、1T硬盤、機械鍵盤和無線鼠標。

4.上面我們已經定義好了兩種配置方案,接下我們還需要一名裝機人員Director:

public class Director {
    private ComputerConfigBuilder mBuilder;
    public void setBuilder(ComputerConfigBuilder builder){
        this.mBuilder = builder;
    }
    public void createComputer(){
        mBuilder.setCPU();
        mBuilder.setMemery();
        mBuilder.setHardDisk();
        mBuilder.setKeyboard();
        mBuilder.setMouse();
    }
    public Computer getComputer(){
        return mBuilder.getComputer();
    }
}

我們需要通過setBuilder來告訴他電腦需要什麼配置,然後就可以通過createComputer來一步步組裝電腦,組裝完之後就可以調用getComputer方法來獲取我們需要的電腦啦。
5.演示
接下來我們就來創建一臺電腦試下,首先我們先創建一個
低配版的:

Director director = new Director();//創建裝機人員
director.setBuilder(new LowConfigBuilder()); //告訴裝機人員電腦配置,這裏爲低配版
director.createComputer(); //裝機人員開始組裝
Computer computer = director.getComputer(); //從裝機人員獲取組裝好的電腦
System.out.print("電腦配置:" + computer.toString());  //查看電腦配置
--------------------------------------
輸出結果:
電腦配置:Computer{CPU='i5', memory='8G', hardDisk='500G', keyboard='薄膜鍵盤', mouse='有線鼠標'}
--------------------------------------

高配版的:

director.setBuilder(new HighConfigBuider());
director.createComputer();
Computer computer = director.getComputer();
System.out.print("電腦配置:" + computer.toString());
--------------------------------------
輸出結果:
電腦配置:Computer{CPU='i7', memory='16G', hardDisk='1T', keyboard='機械鍵盤', mouse='無線鼠標'}
--------------------------------------

好了這就是經典的Builder模式,到這裏相信你對文章開頭Builder模式的定義有了更好的理解。不過在我們日常開發中經典Builder模式一般不常用,用的比較多的還是變種的Builder模式,接下來我們來看下變種的Builder模式。

變種Builder模式

今天Boss突然跑過來扔了一個需求給你:需要創建一個不可變的Person對象,這個Person可以擁有以下幾個屬性:名字、性別、年齡、職業、車、鞋子、衣服、錢、房子。其中名字和性別是必須有的。

聽完後,你稍微想了下,很快給出一個符合要求的Person類:

public class Person {
    /*名字(必須)*/
    private final String name;
    /*性別(必須)*/
    private final String gender;
    /*年齡(非必須)*/
    private final String age;
    /*鞋子(非必須)*/
    private final String shoes;
    /*衣服(非必須)*/
    private final String clothes;
    /*錢(非必須)*/
    private final String money;
    /*房子(非必須)*/
    private final String house;
    /*汽車(非必須)*/
    private final String car;
    /*職業(非必須)*/
    private final String career;

    public Person(String name,String gender,String age,String shoes,String clothes,String money,String house,String car,String career){
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.shoes = shoes;
        this.clothes = clothes;
        this.money = money;
        this.house = house;
        this.car = car;
        this.career = career;
    }

    public Person(String name, String gender){
        this(name,gender,null,null,null,null,null,null,null);
    }

}

由於要創建出的Person對象是不可變的,所以你將類中的屬性都聲明爲final的,然後定義了一個參數爲所有屬性的構造方法,又因爲name和gender爲必須項,所以你爲了調用者方便又單獨定義了一個參數爲name和gender的構造方法。這樣Person類就好了,你信心滿滿的把這個類提交給了Boss,Boss看了,還是很滿意的,不過一段時間後,Boss向你反饋了一個問題,就是如果需要傳入非必須屬性的時候,這個構造方法調用起來不是很方便,因爲這個構造方法參數太多了,很容易傳錯。你試了下,發現確實有這個問題,看來不能把參數全都都放在構造方法中,很快你想到了用set方法設置:

public class Person {
    /*名字(必須)*/
    private String name;
    /*性別(必須)*/
    private String gender;
    /*年齡(非必須)*/
    private String age;
    /*鞋子(非必須)*/
    private String shoes;
    /*衣服(非必須)*/
    private String clothes;
    /*錢(非必須)*/
    private String money;
    /*房子(非必須)*/
    private String house;
    /*汽車(非必須)*/
    private String car;
    /*職業(非必須)*/
    private String career;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getShoes() {
        return shoes;
    }

    public void setShoes(String shoes) {
        this.shoes = shoes;
    }

    ......

}

如果要創建對象的話只用如下操作就行了:

        Person person = new Person();
        person.setName("張三");
        person.setAge("22");
        person.setGender("男");
        person.setCareer("程序員");
        ......

這樣看上去比較清晰了,只要創建一個對象,想要賦什麼值set上去就可以了,不過你細細看了下,還是發現了不少的問題的,首先用這個set方法,違背了剛開始這個對象不可變的需求,其次用這種set方法一條一條賦值,逼格不夠高,另外用這種方式很可能會得到一個不完整的Person對象,因爲當你建完了Person對象,可能出於各方面的原因有些信息忘記set了,那麼你得到的Person對象就不是你預期的對象。這時,你有點困惑了,只好打開谷歌一頓搜索,沒想到還真的找到了解決辦法,就是用下面這種變種的Builder模式:

public class Person {
    /*名字(必須)*/
    private final String name;
    /*性別(必須)*/
    private final String gender;
    /*年齡(非必須)*/
    private final String age;
    /*鞋子(非必須)*/
    private final String shoes;
    /*衣服(非必須)*/
    private final String clothes;
    /*錢(非必須)*/
    private final String money;
    /*房子(非必須)*/
    private final String house;
    /*汽車(非必須)*/
    private final String car;
    /*職業(非必須)*/
    private final String career;


    private Person(Builder builder) {
        this.name = builder.name;
        this.gender = builder.gender;
        this.age = builder.age;
        this.shoes = builder.shoes;
        this.clothes = builder.clothes;
        this.money = builder.money;
        this.house = builder.house;
        this.car = builder.car;
        this.career = builder.career;
    }

    public static class Builder {
        private final String name;
        private final String gender;
        private String age;
        private String shoes;
        private String clothes;
        private String money;
        private String house;
        private String car;
        private String career;

        public Builder(String name,String gender) {
            this.name = name;
            this.gender = gender;
        }

        public Builder age(String age) {
            this.age = age;
            return this;
        }

        public Builder car(String car) {
            this.car = car;
            return this;
        }

        public Builder shoes(String shoes) {
            this.shoes = shoes;
            return this;
        }

        public Builder clothes(String clothes) {
            this.clothes = clothes;
            return this;
        }

        public Builder money(String money) {
            this.money = money;
            return this;
        }

        public Builder house(String house) {
            this.house = house;
            return this;
        }

        public Builder career(String career) {
            this.career = career;
            return this;
        }

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


由於這個Person對象是不可變的,所以毫無疑問我們給他的所有屬性都加了final修飾,當然如果沒有不可變的需求也是可以不加的,然後在Person類中定義一個內部類Builder,這個Builder內部類中的屬性要和Person中的相同,並且必須有的屬性要用final修飾,防止這些屬性沒有被賦值,其他非必須的屬性不能用final,因爲如果加了final,就必須對其進行初始化,這樣這些非必須的屬性又變成必須的。然後內部類中定義了一個構造方法,傳入必須有的屬性。其他非必須的屬性都通過方法設置,每個方法都返回Builder對象自身。最後定義了一個build方法,將Builder對象傳入Person的私有構造方法,最終返回一個對象。

接下來我們來看下Person的創建:

        Person person = new Person.Builder("張三","男")
                .age("12")
                .money("1000000")
                .car("寶馬")
                .build();

是不是看上去逼格瞬間提高了,非必須的屬性可以根據需要任意設置,非常靈活,而且這樣先設置屬性再創建對象,最終獲取的對象一定是你預期的完整對象,不會像用之前set的方法創建的對象可能還沒有設置完全。好了,寫完之後,你迫不及待的把這個Person類提交給了Boss,果然Boss對這種對象創建方式非常滿意。

好了兩種建造者模式到這也分析的差不多了,相信你對建造者模式有了更好的理解。

設計模式持續更新中...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章