【Effective Java】條2:多構造器參數考慮用構造器

構造函數和靜態工廠方法1都不能很好的處理多參數問題。

譬如有一個類表示包裝食品外面顯示的營養成分標籤,這些標籤包含必須的成分:每份的含量,每罐的含量以及每份的卡路里,還有一些其他非必須的顯示,譬如總脂肪量、飽和脂肪量、轉化脂肪、膽固醇、鈉等等。

public class NutritionFacts {

  /** 必須參數*/
  private int servingSize;

  /** 必須參數*/
  private int servings;

  /** 非必須*/
  private int calories;

  /** 非必須*/
  private int fat;

  /** 非必須*/
  private int sodium;

  /** 非必須*/
  private int carbohydrate;

}

試想一下,構建這個類的構造函數,採用構造器和靜態工廠方法都不是很好理解。由於參數太多,你可能會分幾個構造函數來構造(重疊構造器),又或者採用JavaBean來構建,又或者採用本文所說的構造器模式。

重疊構造器模式

首先提供個必須參數構成的構造函數,然後每增加一個非必須參數構成一個構造函數。實現如下:

public class NutritionFacts {

  /** 必須參數*/
  private int servingSize;

  /** 必須參數*/
  private int servings;

  /** 非必須*/
  private int calories;

  /** 非必須*/
  private int fat;

  /** 非必須*/
  private int sodium;

  /** 非必須*/
  private int carbohydrate;

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

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

  public NutritionFacts(int servingSize, int servings, int calories, int fat) {
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
    this.fat = fat;
  }

  public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
    this.fat = fat;
    this.sodium = sodium;
  }

  public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium,
      int carbohydrate) {
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
    this.fat = fat;
    this.sodium = sodium;
    this.carbohydrate = carbohydrate;
  }
}

缺點也顯而易見,當參數足夠多的時候,調用方可能需要參照構造函數將參數一一對應。萬一構造函數改變了參數的順序,調用方也不得不進行修改。

JavaBean構造

提供默認的構造函數,然後採用set方法進行設置。代碼如下:

public class NutritionFacts {

  /** 必須參數*/
  private int servingSize;

  /** 必須參數*/
  private int servings;

  /** 非必須*/
  private int calories;

  /** 非必須*/
  private int fat;

  /** 非必須*/
  private int sodium;

  /** 非必須*/
  private int carbohydrate;

  //Getter & Setter
}

public class NutritionFactsDemo {

  public static void main(String[] args) {
    NutritionFacts nutritionFacts = new NutritionFacts();
    nutritionFacts.setServings(100);
    nutritionFacts.setServingSize(10);
  }

}

JavaBean自身有嚴重的缺點。其一,由於對象的構造是分步進行的,導致Bean可能不處於一致性狀態;其二,在多線程環境中,開發者需要確保構造對象的線程安全。

構造模式(推薦)

不像前兩種方式,構造模式先用必須的參數構建builder對象,然後在builder對象爲非必須參數調用類似setter的方法,最後調用無參的build方法來生成對象。

public class NutritionFacts {

  /** 必須參數*/
  private int servingSize;

  /** 必須參數*/
  private int servings;

  /** 非必須*/
  private int calories;

  /** 非必須*/
  private int fat;

  /** 非必須*/
  private int sodium;

  /** 非必須*/
  private int carbohydrate;

  private NutritionFacts(Builder builder) {
    this.servingSize = builder.getServingSize();
    this.servings = builder.getServings();
    this.calories = builder.getCalories();
    this.fat = builder.getFat();
    this.sodium = builder.getSodium();
    this.carbohydrate = builder.getCarbohydrate();
  }


  public static final class Builder {

    /** 必須參數*/
    private int servingSize;

    /** 必須參數*/
    private int servings;

    private int calories;
    private int fat;
    private int sodium;
    private int carbohydrate;

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

    public static Builder newInstance(int servingSize, int servings) {
      return new Builder(servingSize, servings);
    }

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

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

    public Builder calories(int calories) {
      this.calories = calories;
      return this;
    }

    public Builder fat(int fat) {
      this.fat = fat;
      return this;
    }

    public Builder sodium(int sodium) {
      this.sodium = sodium;
      return this;
    }

    public Builder carbohydrate(int carbohydrate) {
      this.carbohydrate = carbohydrate;
      return this;
    }

    // Getter & Setter

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

構造器模式不是沒有缺點,缺點就是多了很多代碼。但是對於多參數的構造,特別是實際開發中,還是強烈建議使用構造器模式。

多囉嗦一句,在Intellij idea中可以在插件2中搜索builder,使用此插件可以直接生成構造器模式的代碼。

擴展閱讀

發佈了98 篇原創文章 · 獲贊 180 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章