設計模式(七)----建立型模式之建造者模式

2023-02-10 06:01:22

1、概述

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

  • 分離了部件的構造(由Builder來負責)和裝配(由Director負責)。 從而可以構造出複雜的物件。這個模式適用於:某個物件的構建過程複雜的情況。

  • 由於實現了構建和裝配的解耦。不同的構建器,相同的裝配,也可以做出不同的物件;相同的構建器,不同的裝配順序也可以做出不同的物件。也就是實現了構建演演算法、裝配演演算法的解耦,實現了更好的複用。

  • 建造者模式可以將部件和其組裝過程分開,一步一步建立一個複雜的物件。使用者只需要指定複雜物件的型別就可以得到該物件,而無須知道其內部的具體構造細節。

2、結構

建造者(Builder)模式包含如下角色:

  • 抽象建造者類(Builder):這個介面規定要實現複雜物件的那些部分的建立,並不涉及具體的部件物件的建立。

  • 具體建造者類(ConcreteBuilder):實現 Builder 介面,完成複雜產品的各個部件的具體建立方法。在構造過程完成後,提供產品的範例。

  • 產品類(Product):要建立的複雜物件。

  • 指揮者類(Director):呼叫具體建造者來建立複雜物件的各個部分,在指導者中不涉及具體產品的資訊,只負責保證物件各部分完整建立或按某種順序建立。

類圖如下:

3、範例

建立共用單車

生產自行車是一個複雜的過程,它包含了車架,車座等元件的生產。而車架又有碳纖維,鋁合金等材質的,車座有橡膠,真皮等材質。對於自行車的生產就可以使用建造者模式。

這裡Bike是產品,包含車架,車座等元件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具體的建造者;Director是指揮者。類圖如下:

具體的程式碼如下:

//自行車類
public class Bike {
    private String frame;
    private String seat;
​
    public String getFrame() {
        return frame;
    }
​
    public void setFrame(String frame) {
        this.frame = frame;
    }
​
    public String getSeat() {
        return seat;
    }
​
    public void setSeat(String seat) {
        this.seat = seat;
    }
}
​
// 抽象 builder 類
public abstract class Builder {
​
    protected Bike mBike = new Bike();
​
    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
}
​
//摩拜單車Builder類
public class MobikeBuilder extends Builder {
​
    @Override
    public void buildFrame() {
        mBike.setFrame("鋁合金車架");
    }
​
    @Override
    public void buildSeat() {
        mBike.setSeat("真皮車座");
    }
​
    @Override
    public Bike createBike() {
        return mBike;
    }
}
​
//ofo單車Builder類
public class OfoBuilder extends Builder {
​
    @Override
    public void buildFrame() {
        mBike.setFrame("碳纖維車架");
    }
​
    @Override
    public void buildSeat() {
        mBike.setSeat("橡膠車座");
    }
​
    @Override
    public Bike createBike() {
        return mBike;
    }
}
​
//指揮者類
public class Director {
    private Builder mBuilder;
​
    public Director(Builder builder) {
        mBuilder = builder;
    }
​
    public Bike construct() {
        mBuilder.buildFrame();
        mBuilder.buildSeat();
        return mBuilder.createBike();
    }
}
​
//測試類
public class Client {
    public static void main(String[] args) {
        showBike(new OfoBuilder());
        showBike(new MobikeBuilder());
    }
    private static void showBike(Builder builder) {
        Director director = new Director(builder);
        Bike bike = director.construct();
        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

注意:

上面範例是 Builder模式的常規用法,指揮者類 Director 在建造者模式中具有很重要的作用,它用於指導具體構建者如何構建產品,控制呼叫先後次序,並向呼叫者返回完整的產品類,但是有些情況下需要簡化系統結構,可以把指揮者類和抽象建造者進行結合

// 抽象 builder 類
public abstract class Builder {
​
    protected Bike mBike = new Bike();
​
    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
    
    public Bike construct() {
        this.buildFrame();
        this.BuildSeat();
        return this.createBike();
    }
}

說明:

這樣做確實簡化了系統結構,但同時也加重了抽象建造者類的職責,也不是太符合單一職責原則,如果construct() 過於複雜,建議還是封裝到 Director 中。

4、優缺點

優點:

  • 建造者模式的封裝性很好。使用建造者模式可以有效的封裝變化,在使用建造者模式的場景中,一般產品類和建造者類是比較穩定的,因此,將主要的業務邏輯封裝在指揮者類中對整體而言可以取得比較好的穩定性。

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

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

  • 建造者模式很容易進行擴充套件。如果有新的需求,通過實現一個新的建造者類就可以完成,基本上不用修改之前已經測試通過的程式碼,因此也就不會對原有功能引入風險。符合開閉原則。

缺點:

建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制。例如上面生產自行車的例子,現在如果想生產一臺電腦的話,兩者之間的差異較大,這個時候就不適合使用建造者模式了。

5、使用場景

建造者(Builder)模式建立的是複雜物件,其產品的各個部分經常面臨著劇烈的變化,但將它們組合在一起的演演算法卻相對穩定,所以它通常在以下場合使用。

  • 建立的物件較複雜,由多個部件構成,各部件面臨著複雜的變化,但構件間的建造順序是穩定的。

  • 建立複雜物件的演演算法獨立於該物件的組成部分以及它們的裝配方式,即產品的構建過程和最終的表示是獨立的。

6、模式擴充套件

建造者模式除了上面的用途外,在開發中還有一個常用的使用方式,就是當一個類構造器需要傳入很多引數時,如果建立這個類的範例,程式碼可讀性會非常差,而且很容易引入錯誤,此時就可以利用建造者模式進行重構。

重構前程式碼如下:

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
​
    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }
​
    public String getCpu() {
        return cpu;
    }
​
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
​
    public String getScreen() {
        return screen;
    }
​
    public void setScreen(String screen) {
        this.screen = screen;
    }
​
    public String getMemory() {
        return memory;
    }
​
    public void setMemory(String memory) {
        this.memory = memory;
    }
​
    public String getMainboard() {
        return mainboard;
    }
​
    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }
​
    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}
​
public class Client {
    public static void main(String[] args) {
        //構建Phone物件
        Phone phone = new Phone("intel","三星螢幕","金士頓","華碩");
        System.out.println(phone);
    }
}

上面在使用者端程式碼中構建Phone物件,傳遞了四個引數,如果引數更多呢?程式碼的可讀性及使用的成本就是比較高。

重構後程式碼:

public class Phone {
​
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
​
    private Phone(Builder builder) {
        cpu = builder.cpu;
        screen = builder.screen;
        memory = builder.memory;
        mainboard = builder.mainboard;
    }
​
    public static final class Builder {
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;
​
        public Builder() {}
​
        public Builder cpu(String val) {
            cpu = val;
            return this;
        }
        public Builder screen(String val) {
            screen = val;
            return this;
        }
        public Builder memory(String val) {
            memory = val;
            return this;
        }
        public Builder mainboard(String val) {
            mainboard = val;
            return this;
        }
        public Phone build() {
            return new Phone(this);}
    }
    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}
​
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone.Builder()
                .cpu("intel")
                .mainboard("華碩")
                .memory("金士頓")
                .screen("三星")
                .build();
        System.out.println(phone);
    }
}

重構後的程式碼在使用起來更方便,某種程度上也可以提高開發效率。從軟體設計上,對程式設計師的要求比較高。