從實際場景來看設計模式1:Builder構建器模式

問題摘要

本文從代碼編寫中遇見的常見問題:如何優雅的設計需要大量參數的類?來由淺入深的學習構建器設計模式。

引經據典

遇到多個構造器參數時要考慮用構建器。

——《Effective Java》第二版

實際場景

設計一個表示食品營養成分的類,該類有必要的參數:每份含量、每份所含卡路里,以及其他非必須的參數:總脂肪量、飽和脂肪量、膽固醇及鈉的含量。

在設計類時因爲參數衆多,所以有編碼經驗的同學可以立即想到使用重疊的構造函數來設計:先寫一個包含必須參數的構造函數,然後寫重載函數幷包含一個非必須參數並調用包含必須參數的構造函數,然後再寫一個包含兩個非必須參數的構造函數…

這樣就得到了一個可以在創建時根據需要傳入非必須參數的類,優點是思路清晰,缺點也很明顯,如果這個類包含20個、30個參數時,將極大的增加編碼量,而且在客戶端實例化時,也並非可以簡潔清晰的創建所需的類。

結論:重疊的構造器可行,但是當參數很多時,客戶端代碼會很難編寫,並且難以閱讀。

嘗試使用JavaBean模式 1

聽起來高大上,其實就是通過無參構造器實例化之後按需調用setter來設置屬性值。

這種方式優點是比較直觀,無論是類的編寫還是客戶端實例化都可以很方便的實現,但是帶來很嚴重的缺陷就是,創建的類會處於中間狀態而帶來安全問題,當使用對象時,無法確保這個對象的參數是否全部設置好了。

在構建的過程中JavaBean可能處於不一致的狀態。 類無法通過檢驗構造器參數來保證一致性,在高併發場景下,如果使用了處於不一致狀態的對象,會帶來運行結果的錯誤,而且無法重新和排查。

構建器模式

通過上面的解決方案我們發現,構建過程的複雜性和代碼的可讀性、可維護性是最大的疑難點。我們可以試想,如果將構建過程抽離出來,提供簡潔的方法供使用者構建,然後返回構建的結果,如果採用這種實現方式,類的一致性和構建過程的簡潔性就得到了保證。

既能保證像重疊構造器模式的安全性,又能保證JavaBean模式的可讀性,這就是Builder模式

不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器(或者靜態工廠),得到一個builder對象。然後客戶端通過類似setter的調用將可選參數設置進去,最後通過build來生成最終對象。

我們先直接來看實現後的實例化過程

 NutritionFacts cocacola = new NurtritionFacts.Builder(240,16)
 											.calories(25)
 											.carbohydrate(27)
 											.build();

整個代碼簡潔清爽,這就是設計模式的魅力。

代碼實現

/**
 * @Author SunHongmin
 * @Description 當一個類實例化過程中需要許多參數,如果使用重載的構造方法來實現,客戶端代碼將很難直觀的編寫
 * 使用構建者模式將接收參數過程交由靜態內部類來實現,最後返回實例成功的最終類,既保證了簡潔易用,又可以避免安全隱患
 * <p>
 * 靜態內部類的好處也顯而易見  外部類可以直接調用內部類的私有成員並且 內部類可以使用外部類的私有構造方法
 * @Date 2020/5/17 21:40
 */
public class Builder {
    public static void main(String[] args) {
        Person person = new Person.Builder("張三", "201120")
                .age(15)
                .height(178.5)
                .weight(80)
                .build();

        System.out.println(person);
    }
}

// 實際需要實例化的類,私有化構造參數,不直接去實例化
class Person {
    private String name;
    private String sno;

    private int age;
    private double height;
    private double weight;

    private Person(Builder builder) {
        this.name = builder.name;
        this.sno = builder.sno;
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
    }

    // 利用靜態內部類來提供定製化參數設置  統一實例化入口
    public static class Builder {
        private String name;
        private String sno;

        private int age;
        private double height;
        private double weight;

        // 必須參數使用構造參數傳入
        public Builder(String name, String sno) {
            this.name = name;
            this.sno = sno;
        }

        // 非必須參數提供公有方法單獨設置
        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder height(double height) {
            this.height = height;
            return this;
        }

        public Builder weight(double weight) {
            this.weight = weight;
            return this;
        }

        // build 方法實例化person方法
        public Person build() {
            return new Person(this);
        }
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", sno='" + sno + '\'' +
                ", age=" + age +
                ", height=" + height +
                ", weight=" + weight +
                '}';
    }
}

實際應用中的注意事項

如果類的構造器或者靜態工廠中需要多個參數(通常4個及以上可認爲參數比較多),設計這種類時,構建器模式是一種不錯的選擇。

Builder構建器模式也存在不足,爲了創建對象先要創建它的構造器,如果十分注重性能,可能會帶來影響。

構建器模式的代碼較重疊構造器更加冗長,因此只有當需要很多參數時才使用。

注意:如果類最初設計使用重疊構造器或者靜態工廠實現,那麼等到類需要多個參數時才添加構造器,將會無法控制,那些過時的構造器或者靜態工廠將會顯得不協調,因此最好一開始就使用構造器模式。


  1. 注意區分 Builder生成器模式: 生成器模式主要用於構建複雜對象 ↩︎

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