iT邦幫忙

2024 iThome 鐵人賽

DAY 3
0
Software Development

深入淺出Java 30天系列 第 3

Day 3: 當constructor需要很多參數時,考慮使用builder(上)

  • 分享至 

  • xImage
  •  

當在實體化物件時,我們可能因為不同需求,需要給的參數數量不同,舉例來說,有一個負責營養成分的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"));
    }
}


上一篇
Day 2: 盡量使用static factory method,而不是constructor
下一篇
Day 4: 當constructor需要很多參數時,考慮使用builder(下)
系列文
深入淺出Java 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言