JAVA編程思想(四)Builder模式經典範式以及和工廠模式如何選?

Java極客  |  做者  /  鏗然一葉
這是Java極客的第 66 篇原創文章

相關閱讀:java

JAVA編程思想(一)經過依賴注入增長擴展性
JAVA編程思想(二)如何面向接口編程
JAVA編程思想(三)去掉彆扭的if,自注冊策略模式優雅知足開閉原則
JAVA基礎(三)ClassLoader實現熱加載
Java併發編程入門(十一)限流場景和Spring限流器實現
HikariPool源碼(二)設計思想借鑑
人在職場(一)IT大廠生存法則編程


1. 建立對象實例的方式

Builder模式,工廠模式,new均可以用於建立對象實例,它們三者的應用場景和區別以下:設計模式

建立方式 應用場景
new 建立邏輯簡單,沒有影響實例生成的參數或條件
工廠模式 根據參數或條件生成不一樣實例
Builder模式 建立實例時能夠設置不一樣的參數組合,每種組合能夠表現不一樣的行爲

在選擇以上三種方式建立對象時,不要考慮A方式能不能替代B方式,由於若是它們只是替代關係,那就沒有體現它們各自的價值,只有當一個事物有不可替代性時,纔有其價值,因此應該考慮的是:我是否是不得不選擇它數組

若是A,B互相可替代,那麼就使用最簡單的方式,好比new能搞定,就不要硬整一個工廠模式,不要爲了用模式而用模式併發

1.1. 根據參數或條件生成不一樣實例

如在JAVA編程思想(三)去掉彆扭的if,自注冊策略模式優雅知足開閉原則 中的例子,稅策略工廠根據不一樣的稅類型生成了不一樣的稅策略實例去算稅。app


在這種場景下用new建立實例是不合適的, 不得不使用工廠模式來建立實例。

1.2. 不一樣的參數組合表現不一樣的行爲

Builder模式建立實例時能夠使用不一樣的參數,使得最後建立的實例有不一樣的行爲,舉個例子,IPhone有時針對不一樣的顏色區分不一樣的具體機型參數,例如(舉例用,不是徹底和實際一致):ide

顏色 攝像頭個數 MAX版 內存
紅色 2,3個攝像頭可選 普通版和MAX版 256G和512G
黑色 僅有2個攝像頭 普通版 128G,256G和512G

此時用工廠模式就不合適,由於組合參數有不少種,每種組合都寫一個建立方法很冗餘,更好的處理方式是在建立實例時能夠設置參數,由調用者自行設置須要的參數。post

下面舉例說明。測試

1.3. 建立者模式舉例

1.3.1. 定義合法參數

public enum Color {
    RED, BLACK
}

public enum Model {
    NORMAL, MAX
}

public enum CameraNum {
    TWO, THREE
}

public enum MemorySize {
    G128, G256, G512
}
複製代碼

1.3.2. IPhone類

public class IPhone {

    private Color color;
    private Model model;
    private CameraNum cameraNum;
    private MemorySize memorySize;

    public IPhone(Color color, Model model, CameraNum cameraNum, MemorySize memorySize) {
        this.color = color;
        this.model = model;
        this.cameraNum = cameraNum;
        this.memorySize = memorySize;
    }

    // 1.獨立的建立者,職責更清晰。2. 做爲內部類,功能更內聚 3.不須要訪問IPhone的成員變量-【靜態】內部類 
    public static final class Builder {
        private Color color;
        private Model model;
        private CameraNum cameraNum;
        private MemorySize memorySize;

        public Builder setColor(Color color) {
            this.color = color;
            return this;
        }

        public Builder setModel(Model model) {
            this.model = model;
            return this;
        }

        public Builder setCameraNum(CameraNum cameraNum) {
            this.cameraNum = cameraNum;
            return this;
        }

        public Builder setMemorySize(MemorySize memorySize) {
            this.memorySize = memorySize;
            return this;
        }

        public IPhone build() {
            IPhone iPhone = new IPhone(this.color, this.model, this.cameraNum, this.memorySize);
            return iPhone;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("color=").append(color.toString()).append(", ");
        builder.append("model=").append(model.toString()).append(", ");
        builder.append("cameraNum=").append(cameraNum.toString()).append(", ");
        builder.append("memorySize=").append(memorySize.toString());
        return builder.toString();
    }
}
複製代碼

1.3.3. 測試類

public class BuilderDemo {
    public static void main(String[] args) {
        IPhone.Builder builder = new IPhone.Builder();
        // 紅色
        IPhone redPhone = builder.setColor(Color.RED)
                .setCameraNum(CameraNum.THREE)
                .setMemorySize(MemorySize.G512)
                .setModel(Model.MAX).build();

        // 黑色
        IPhone blackPhone = builder.setColor(Color.BLACK)
                .setCameraNum(CameraNum.THREE)
                .setMemorySize(MemorySize.G128)
                .setModel(Model.NORMAL).build();

        System.out.println(redPhone);
        System.out.println(blackPhone);
    }
}
複製代碼

輸出結果:優化

color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=THREE, memorySize=G128
複製代碼

至此,建立者模式完成,通常的建立者設計模式舉例,包括大廠的代碼(例如Android中的JobInfo.Builder)也就到此爲止了,但不知細心的讀者是否發現了例子中存在的問題

例子中的錯誤爲:建立了一個錯誤的黑色IPhone,按照前面表格說明,並無3個攝像頭的黑色IPhone,但卻建立了一個這樣的黑色IPhone。

這種錯誤的解決方式有:

  1. 運行時拋出異常,這是最差的方式。
  2. 編譯時異常,在這個場景沒法作到。
  3. 增長API說明,讓開發者參考文檔避免錯誤。
  4. 經過不一樣的Builder建立對象實例,在編碼時避免錯誤。

下面,咱們就經過第4種方式來解決這個問題,儘量在早期就避免犯錯

1.4. 建立者模式優化,在編碼期避免建立錯誤

1.4.1. 新增適用紅色IPhone的參數定義

// 用於紅色IPhone,限定內存範圍
public enum RedMemorySize {
    G256 {
        @Override public MemorySize getMemorySize() {
            return MemorySize.G256;
        }
    },
    G512 {
        @Override public MemorySize getMemorySize() {
            return MemorySize.G512;
        }
    };

    public abstract MemorySize getMemorySize();
}
複製代碼

1.4.2. 定義不一樣的Builder建立不一樣的Iphone

public class IPhone {

    // 成員定義不變
    private Color color;
    private Model model;
    private CameraNum cameraNum;
    private MemorySize memorySize;

    // 構造器不變
    public IPhone(Color color, Model model, CameraNum cameraNum, MemorySize memorySize) {
        this.color = color;
        this.model = model;
        this.cameraNum = cameraNum;
        this.memorySize = memorySize;
    }

    // 黑色IPhone Builder
    public static final class BlackBuilder {
        private MemorySize memorySize;

        // 其餘參數沒得選,只需保留此方法
        public BlackBuilder setMemorySize(MemorySize memorySize) {
            this.memorySize = memorySize;
            return this;
        }

        public IPhone build() {
            // 限定的參數直接傳入
            IPhone iPhone = new IPhone(Color.BLACK, Model.NORMAL, CameraNum.TWO, this.memorySize);
            return iPhone;
        }
    }

    // 紅色IPhone Builder
    public static final class RedBuilder {
        private Model model;
        private CameraNum cameraNum;
        private MemorySize memorySize;

        public RedBuilder setModel(Model model) {
            this.model = model;
            return this;
        }

        public RedBuilder setCameraNum(CameraNum cameraNum) {
            this.cameraNum = cameraNum;
            return this;
        }

        // 注意這裏入參是RedMemorySize,用於限定內存範圍只能是256G和512G
        public RedBuilder setMemorySize(RedMemorySize memorySize) {
            this.memorySize = memorySize.getMemorySize();
            return this;
        }

        public IPhone build() {
            // 限定的參數直接傳入
            IPhone iPhone = new IPhone(Color.RED, this.model, this.cameraNum, this.memorySize);
            return iPhone;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("color=").append(color.toString()).append(", ");
        builder.append("model=").append(model.toString()).append(", ");
        builder.append("cameraNum=").append(cameraNum.toString()).append(", ");
        builder.append("memorySize=").append(memorySize.toString());
        return builder.toString();
    }
}
複製代碼

1.4.3. 測試代碼

public class BuilderDemo {
    public static void main(String[] args) {
        // 紅色專有Builder,在開發時便可避免建立錯誤對象
        IPhone.RedBuilder redBuilder = new IPhone.RedBuilder();
        IPhone redPhone = redBuilder.setCameraNum(CameraNum.THREE)
                .setMemorySize(RedMemorySize.G512)
                .setModel(Model.MAX).build();

        // 黑色專有Builder,在開發時便可避免建立錯誤對象
        IPhone.BlackBuilder blackBuilder = new IPhone.BlackBuilder();
        IPhone blackPhone = blackBuilder.setMemorySize(MemorySize.G128).build();

        System.out.println(redPhone);
        System.out.println(blackPhone);
    }
}
複製代碼

輸出:

color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=TWO, memorySize=G128
複製代碼

能夠看到,這時建立的黑色IPhone是正確的。

1.5. 建立者模式經典範式

至此,咱們能夠總結出建立者模式的經典範式:

  1. 建立者和要建立對象分離,這樣可解耦,職責更清晰
  2. 建立者可做爲建立對象的內部類(一般是靜態內部類),這樣更內聚,使得很容易找到建立者。
  3. 因爲不一樣的條件/參數組合建立出的對象實例有不一樣行爲,錯誤組合可能致使錯誤結果時,能夠經過不一樣的Builder來建立對象,在編碼時就避免犯錯。
  4. 經過鏈式建立方式使得代碼更簡潔

2. 總結

  1. 每一個設計模式有不能被替代的用途,若是發現能互相替代時,就用最簡單的那種。
  2. 儘早避免編碼犯錯能使代碼更健壯,糾錯成本更低,因此應儘量的在前期避免錯誤發生。
  3. 工廠模式和Builder模式的選擇在因而否有不少不一樣參數組合會影響被建立實例的行爲,若是有就使用Builder模式,不然會由於要窮舉各類參數組合致使工廠方法膨脹

end.


<--閱過留痕,左邊點贊!

相關文章
相關標籤/搜索