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

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);
    }
}

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

 

 

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