當在實體化物件時,我們可能因為不同需求,需要給的參數數量不同,舉例來說,有一個負責營養成分的class,有calories、weight、carbohydrate、sodium、fat和sugar這六個property,如果是透過constructor指定所有的資訊,必須一口氣所有參數給constructor。
public class NutritionFacts {
private final int calories; //required
private final int weight; //required
private final int carbohydrate; //optional
private final int sodium; //optional
private final int fat; //optional
private final int sugar; //optional
public NutritionFacts(int calories, int weight, int carbohydrate, int sodium, int fat, int sugar) {
this.calories = calories;
this.weight = weight;
this.carbohydrate = carbohydrate;
this.sodium = sodium;
this.fat = fat;
this.sugar = sugar;
}
}
但並不是每種食物都有carbohydrate、sodium、fat和sugar這四種成分,或非常少可以忽略不計,這時候再給constructor參數時,都得固定填0或某個數字或字串。假設需要定義rice、carbonatedWater和hamburger這三種食物的營養成分,因為這三種食物都不需要填寫一些成分,使用者必須填很多額外的參數,可讀性不佳也有很多重工。
class main {
public static void main(String[] args) {
NutritionFacts rice = new NutritionFacts(130, 100, 28, 0, 0, 0);
NutritionFacts carbonatedWater = new NutritionFacts(50, 1000, 1, 1, 0, 0);
NutritionFacts hamburger = new NutritionFacts(600, 200, 50, 5, 30, 0);
}
}
當然,也可以選擇宣告多個constructor method,但使用者就必需花費很多時間去理解每個食物適合使用的constructor method,一樣會影響可讀性,而且這兩種方式,使用者都得小心地去檢查參數放的位置對不對。
class main {
public static void main(String[] args) {
NutritionFacts rice = new NutritionFacts(130, 100, 28);
NutritionFacts carbonatedWater = new NutritionFacts(50, 1000, 1, 1);
NutritionFacts hamburger = new NutritionFacts(600, 200, 50, 5, 30);
System.out.println(rice);
}
}
public class NutritionFacts {
private final int calories; //required
private final int weight; //required
private final int carbohydrate; //optional
private final int sodium; //optional
private final int fat; //optional
private final int sugar; //optional
public NutritionFacts(int calories, int weight, int carbohydrate, int sodium, int fat, int sugar) {
this.calories = calories;
this.weight = weight;
this.carbohydrate = carbohydrate;
this.sodium = sodium;
this.fat = fat;
this.sugar = sugar;
}
public NutritionFacts(int calories, int weight, int carbohydrate) { //for rice
this(calories, weight, carbohydrate, 0, 0);
}
//for Carbonated water
public NutritionFacts(int calories, int weight, int carbohydrate, int sodium) {
this(calories, weight, carbohydrate, sodium, 0);
}
//for hamburger
public NutritionFacts(int calories, int weight, int carbohydrate, int sodium, int fat) {
this(calories, weight, carbohydrate, sodium, fat, 0);
}
}
如果真的不使用constructor傳遞參數,其實也可以改用JavaBeans pattern,使用setter method去assign值。
public class NutritionFacts {
private int calories = 0; //required
private int weight = 0; //required
private int carbohydrate = 0; //optional
private int sodium = 0; //optional
private int fat = 0; //optional
private int sugar = 0; //optional
public NutritionFacts() {}
public void setCalories(int calories) { this.calories = calories; }
public void setWeight(int weight) { this.weight = weight; }
public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; }
public void setSodium(int sodium) { this.sodium = sodium; }
public void setFat(int fat) { this.fat = fat; }
public void setSugar(int sugar) { this.sugar = sugar; }
}
JavaBeans pattern最大的好處是,可以針對需要填值的欄位,透過setter method去設值,但是這也有一個很大的缺點,不是一開始就指定欄位的值,所以欄位不能是final,而且使用者可以在任何時候設定值,這會讓debug的難度變高,有些錯誤不會在compile的時候被發現,而是在runtime的時候才出現。
class main {
public static void main(String[] args) {
NutritionFacts hamburger = new NutritionFacts();
hamburger.setCalories(600);
hamburger.setWeight(200);
hamburger.setCarbohydrate(50);
hamburger.setSodium(5);
hamburger.setFat(30);
}
}
舉例來說,如果NutritionFacts多了一個name的欄位且型別是string。
public class NutritionFacts {
private int calories = 0; //required
private int weight = 0; //required
private int carbohydrate = 0; //optional
private int sodium = 0; //optional
private int fat = 0; //optional
private int sugar = 0; //optional
private String name = "";
public NutritionFacts() {}
public void setCalories(int calories) { this.calories = calories; }
public void setWeight(int weight) { this.weight = weight; }
public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; }
public void setSodium(int sodium) { this.sodium = sodium; }
public void setFat(int fat) { this.fat = fat; }
public void setSugar(int sugar) { this.sugar = sugar; }
public void setName(String name) { this.name = name; }
public String getName() { return this.name; }
}
如果使用setName指定name的值是null,之後使用getter拿出name,並要呼叫String底下的method時,在runtime會出現java.lang.NullPointerException ,但在compile的時候並不會檢查出這個錯誤,增加debug的難度。
class main {
public static void main(String[] args) {
NutritionFacts hamburger = new NutritionFacts();
hamburger.setCalories(600);
hamburger.setWeight(200);
hamburger.setCarbohydrate(50);
hamburger.setSodium(5);
hamburger.setFat(30);
hamburger.setName(null);
System.out.println(hamburger.getName().contentEquals("hamburger"));
}
}