設計模式之建造者模式(BuilderPattern)

敲程式碼的小小酥發表於2021-03-14

一.意義

將一個複雜的物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

說明:複雜物件的構建,比如一個物件有幾十個成員屬性,那麼我們在建立這個物件,並給成員屬性賦值時,就會很麻煩。採用建造者模式,就是把建立物件並給成員屬性賦值的工作,分離出來,由建造者角色來完成,業務程式設計師直接呼叫導演類,獲得複雜物件即可,無需再進行物件的建立工作了。

二.角色

建造者模式涉及到一下幾種角色:

Builder(抽象建造者):它為建立一個產品Product物件的各個部件指定抽象介面,在該介面中一般宣告兩類方法,一類方法是buildPartX(),它們用於建立複雜物件的各個部件;另一類方法是getResult(),它們用於返回複雜物件。Builder既可以是抽象類,也可以是介面。由此可見,抽象建造者就類似於抽象工廠,只不過,抽象工廠是排程的產品的整體部件,而抽象建造者,是構造產品的內部部件。抽象建造者定義了建造規範,不同的建造者遵循著同樣的建造規範,構建不同的部件。

ConcreteBuilder(具體建造者):它實現了Builder介面,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜物件,也可以提供一個方法返回建立好的複雜產品物件。具體建造者就相當於工廠實現類。一個產品,對應一個具體建造者,構造多樣的部件,並返回對應的產品物件。

Product(產品角色):它是被構建的複雜物件,包含多個組成部件,具體建造者建立該產品的內部表示並定義它的裝配過程。在建造者模式中,產品大多是一個類,產品的部件就是類的成員屬性。

Director(指揮者):指揮者又稱為導演類,它負責安排複雜物件的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其construct()建造方法中呼叫建造者物件的部件構造與裝配方法,完成複雜物件的建造。客戶端一般只需要與指揮者進行互動,在客戶端確定具體建造者的型別,並例項化具體建造者物件(也可以通過配置檔案和反射機制),然後通過指揮者類的建構函式或者Setter方法將該物件傳入指揮者類中。

個人感覺,這裡的導演類,完全可以融合到建造者角色中,這樣的話,對於呼叫者而言,選擇具體建造者的時候,通過建造者構造部件,也可以獲得產品。這種形式,就是把產品的構建和產品的獲得,都放到了一個類裡,違反了單一責任原則。所以,導演類的存在,是為了滿足單一責任原則。那為什麼在工廠模式中,沒有導演這個角色呢,那是因為在工廠模式中,工廠的作用就是生產產品,直接通過工廠,就獲得了產品。而建造者模式,建造者是在構建產品,它體現的是一個過程,而不是最終的結果。

 

三、程式碼實現

我們來構建上述的角色,實現建造者模式:

首先,先定義產品,我們以電腦產品為例,建造者模式的產品以類為主,注重的是內部的部件(成員屬性):

/**
 * 產品:電腦
 */
public class Computer {
    /**
     * 內部部件:
     */
    private String brand;
    private String cpu;
    private String mainBoard;
    private String hardDisk;
    private String displayCard;
    private String power;
    private String memory;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getMainBoard() {
        return mainBoard;
    }

    public void setMainBoard(String mainBoard) {
        this.mainBoard = mainBoard;
    }

    public String getHardDisk() {
        return hardDisk;
    }

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

    public String getDisplayCard() {
        return displayCard;
    }

    public void setDisplayCard(String displayCard) {
        this.displayCard = displayCard;
    }

    public String getPower() {
        return power;
    }

    public void setPower(String power) {
        this.power = power;
    }

    public String getMemory() {
        return memory;
    }

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

 

然後,我們定義抽象建立者,主要分為兩部分:buildX構建部件,和getResult獲取構建產品,程式碼如下:

**
 * 建造者角色,其實就是代替複雜物件的建構函式等構造方式,在這裡對複雜物件進行構建
 */
public abstract class ComputerBuilder {
    //建造者中,建立複雜產品物件,構建出複雜物件後,進行輸出。
    protected Computer computer = new Computer();

    /**
     * 建造產品部件的方法,這裡用抽象方法,不同的建造者可以構建出不同的產品部件來。
     */
    public abstract void buildBrand();
    public abstract void buildCPU();
    public abstract void buildMainBoard();
    public abstract void buildHardDisk();
    public abstract void buildDisplayCard();
    public abstract void buildPower();
    public abstract void buildMemory();

    /**
     * 建造者最後輸出複雜產品。
     * @return
     */
    public Computer createComputer() {
        return computer;
    }
}

 

然後建立具體建造者類,如下程式碼:

/**
 * 具體建造者,可以構建不同的產品部件。構建者有統一的構建介面,這樣可以規範不同構建者。
 */
public class ASUSComputerBuilder extends ComputerBuilder{
    @Override
    public void buildBrand() {
        computer.setBrand("華碩電腦");
    }

    @Override
    public void buildCPU() {
        computer.setCpu("Intel 第8代 酷睿");
    }

    @Override
    public void buildMainBoard() {
        computer.setMainBoard("華碩主機板");
    }

    @Override
    public void buildHardDisk() {
        computer.setHardDisk("256GB SSD");
    }

    @Override
    public void buildDisplayCard() {
        computer.setDisplayCard("MX150 獨立2GB");
    }

    @Override
    public void buildPower() {
        computer.setPower("3芯 鋰離子電池 65W AC介面卡");
    }

    @Override
    public void buildMemory() {
        computer.setMemory("1 x SO-DIMM  8GB");
    }
}
/**
 * 具體建造者,可以構建不同的產品部件。構建者有統一的構建介面,這樣可以規範不同構建者。
 */
public class DellComputerBuilder extends ComputerBuilder {
    @Override
    public void buildBrand() {
        computer.setBrand("戴爾電腦");
    }

    @Override
    public void buildCPU() {
        computer.setCpu("i5-8300H 四核");
    }

    @Override
    public void buildMainBoard() {
        computer.setMainBoard("戴爾主機板");

    }

    @Override
    public void buildHardDisk() {
        computer.setHardDisk("1T + 128GB SSD");
    }

    @Override
    public void buildDisplayCard() {
        computer.setDisplayCard("GTX1060 獨立6GB");

    }

    @Override
    public void buildPower() {
        computer.setPower("4芯 鋰離子電池 180W AC介面卡");
    }

    @Override
    public void buildMemory() {
        computer.setMemory("4G + 4G");
    }
}

 

然後,我們定義導演類,來排程建造者,輸出複雜產品:

/**
 * 導演類,指揮構建過程,呼叫建造者類。
 */
public class ComputerDirector {
    /**
     * 導演類的建造方法一般都用construct命名。傳入具體建造者類,輸出產品
     * @param builder
     * @return
     */
    public Computer construct(ComputerBuilder builder){
        Computer computer;
        builder.buildBrand();
        builder.buildCPU();
        builder.buildDisplayCard();
        builder.buildHardDisk();
        builder.buildMainBoard();
        builder.buildMemory();
        builder.buildPower();
        computer = builder.createComputer();
        return computer;

    }
}

 

這樣,對於業務程式設計師而言,就可以通過導演類,來獲取複雜物件了,如下:

public class Main {
    public static void main(String[] args) {
        //使用者直接使用的是導演類
       ComputerDirector director=new ComputerDirector();
       //選擇相應的構建者,來構建不同的部件
        ComputerBuilder dellBuilder = new DellComputerBuilder();
        //返回構建出來的產品
        Computer dellComputer=director.construct(dellBuilder);


    }
    
}

 

由此我們可以看到,業務程式設計師需要選擇具體的建造類,傳入導演類中,獲得產品。在我們實際專案中,通常我們在xml裡定義需要的具體建造者類,然後spring等其他框架讀取我們在xml裡寫的具體建造者類,呼叫導演類,來獲得程式設計師想要的產品。所以對於業務程式設計師而言,需要做的就是在xml裡定義具體建造者,導演類都是框架在做。 

四、優點和缺點

優點:

  • 在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。

  • 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,使用者使用不同的具體建造者即可得到不同的產品物件。由於指揮者類針對抽象建造者程式設計,增加新的具體建造者無須修改原有類庫的程式碼,系統擴充套件方便,符合 “開閉原則”。

  • 可以更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不同的方法中,使得建立過程更加清晰,也更方便使用程式來控制建立過程。

缺點:

  • 建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用範圍受到一定的限制。

  • 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和執行成本。從這句話我們可以看出,在我們應用設計模式時,也要考慮系統的複雜程度,即使一個業務很適合應用某種設計模式,但是應用起來,很笨重很複雜,那麼我們也不建議生搬硬套設計模式。

五、建造者模式的本質

建造者模式的本質,就是建造物件。我們在閱讀原始碼中,看到Builder字樣時,只要知道其實在創造產品即可。具體其怎麼走流程,怎麼建立這個物件的,我們不用太過關注,因為有些建造者模式,結構和設計特別複雜。

 

相關文章