首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Effective Java 學習筆記:Builder 模式中「對象處於不一致狀態」的深度解析

     編輯:程式設計疑難解答

原文語境

《Effective Java》第 2 條「遇到多個構造器參數時要考慮用構建器」中提到:

「因為構造過程被分到了幾個調用之中,在構造過程中 JavaBean 可能處於不一致的狀態。」

什麼是「不一致狀態」?

指對象已部分初始化,但尚未滿足類的所有不變約束。例如:

public class NutritionFacts {
    private final int servingSize;  // 必填
    private final int servings;     // 必填
    private final int calories;     // 選填
    private final int fat;          // 選填
    private final int sodium;       // 選填
    private final int carbohydrate; // 選填

    // 傳統重疊構造器
    public NutritionFacts(int servingSize, int servings) { ... }
    public NutritionFacts(int servingSize, int servings, int calories) { ... }
    public NutritionFacts(int servingSize, int servings, int calories, int fat) { ... }
    // ...
}

調用者可能寫錯參數順序:`new NutritionFacts(100, 200, 0, 10, 20)` → `calories=0, fat=10, sodium=20, carbohydrate=0`(預設),語義錯誤且編譯不報錯。

Builder 模式如何解決?

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    private NutritionFacts(Builder builder) {
        this.servingSize  = builder.servingSize;
        this.servings     = builder.servings;
        this.calories     = builder.calories;
        this.fat          = builder.fat;
        this.sodium       = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }

    public static class Builder {
        // 必填參數
        private final int servingSize;
        private final int servings;
        // 選填參數 - 預設值
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val)  { calories = val;      return this; }
        public Builder fat(int val)       { fat = val;           return this; }
        public Builder sodium(int val)    { sodium = val;        return this; }
        public Builder carbohydrate(int val){ carbohydrate = val; return this; }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
}

// 使用:語義清晰、順序無關、必填強制
NutritionFacts facts = new NutritionFacts.Builder(240, 8)
    .calories(100).sodium(35).carbohydrate(27).build();

多執行緒下的「不一致」風險

書中提到的「幾個調用的先後順序不同」在多執行緒場景尤為明顯:

  • 執行緒 A 呼叫 `builder.calories(100)`,執行緒 B 同時呼叫 `builder.sodium(35)`。
  • 若 `Builder` 非執行緒安全,內部欄位可能被部分寫入、部分舊值,構建出的對象混合了不同執行緒的設定值 → 不一致狀態。
  • 解決:`Builder` 設計為不可變/執行緒封閉,或使用 `ThreadLocal`/同步構建。

JavaBean 慣例的缺陷

模式缺陷
無參構造 + Setter對象可變、構造中間態對外可見、難以保證不變約束、非執行緒安全
重疊構造器參數順序易混、擴展性差、閱讀性低
Builder語義明確、不可變對象、支援驗證、執行緒安全易實現、可讀性高

總結

「構造過程分到幾個調用中 → 可能處於不一致狀態」精確描述了分步初始化(Setter/重疊構造器)無法原子性保證對象不變約束的本質缺陷。Builder 模式將「配置階段」與「構建階段」分離,在 `build()` 瞬間原子性完成所有欄位賦值並可驗證,徹底消除中間態洩露,是構建複雜不可變對象的黃金標準。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved