無論是 static factory method 或是 constructor,在遇到傳入的參數很多時,都不容易處理。在開發過程中,常會使用 Telescoping Constructor Pattern 來解決。
public class PricingModel {
private final String name;
private final double smsFee;
private final int smsFreeTimes;
private final double phoneFee;
private final int phoneFreeMinutes;
private final double networkFee;
private final int networkFreeGB;
public PricingModel(String name, double smsFee, int smsFreeTimes) {
this(name, smsFee, smsFreeTimes, 0, 0, 0, 0);
}
public PricingModel(String name, double smsFee, int smsFreeTimes,
double phoneFee, int phoneFreeMinutes) {
this(name, smsFee, smsFreeTimes, phoneFee, phoneFreeMinutes, 0, 0);
}
public PricingModel(String name, double smsFee, int smsFreeTimes,
double phoneFee, int phoneFreeMinutes,
double networkFee, int networkFreeGB) {
this.name = name;
this.smsFee = smsFee;
this.smsFreeTimes = smsFreeTimes;
this.phoneFee = phoneFee;
this.phoneFreeMinutes = phoneFreeMinutes;
this.networkFee = networkFee;
this.networkFreeGB = networkFreeGB;
}
}
當需要傳入的參數越來越多時,上述的 Telescoping constructor pattern 同樣會讓開發者誤用及不易閱讀,那如果換成單純的 JavaBeans Pattern,開發者在使用上較簡單,程式碼也較容易閱讀。public class PricingModel {
private String name;
private double smsFee;
private int smsFreeOfTimes;
private double callFee;
private int callFreeOfMinute;
private double networkFee;
private int networkFreeOfGB;
public void setName(String name) { this.name = name; }
public void setSmsFee(double smsFee) { this.smsFee = smsFee; }
public void setSmsFreeOfTimes(int smsFreeOfTimes) { this.smsFreeOfTimes = smsFreeOfTimes; }
public void setCallFee(double callFee) { this.callFee = callFee; }
public void setCallFreeOfMinute(int callFreeOfMinute) { this.callFreeOfMinute = callFreeOfMinute; }
public void setNetworkFee(double networkFee) { this.networkFee = networkFee; }
public void setNetworkFreeOfGB(int networkFreeOfGB) { this.networkFreeOfGB = networkFreeOfGB; }
}
但是 setter 會讓 JavaBean 的不可變性打開後門,當 JavaBean 生成時,開發者有機會去修改其內容。範例如下,在拿到 PricingModel 後,利用 setter 修改 SMS 的免費次數。public class PricingModel {
private static final PricingModel GENERAL = ...;
public static PricingModel general() {
return GENERAL;
}
}
// use case
PricingModel general = PricingModel.general();
general.setSmsFreeOfTimes(Integer.MAX_VALUE);
最後介紹一種替代方案,可以避免掉上述的問題,Builder Pattern。它是由另一個 builder class 來處理,builder 中的 setter 回傳的是 builder 本身,最後調用 build() 才會生成實體。class PricingModelBuilder {
private String name;
private double smsFee;
private int smsFreeOfTimes;
private double callFee;
private int callFreeOfMinute;
private double networkFee;
private int networkFreeOfGB;
public PricingModelBuilder setName(String name) {
this.name = name;
return this;
}
public PricingModelBuilder setSmsFee(double smsFee) {
this.smsFee = smsFee;
return this;
}
public PricingModelBuilder setSmsFreeOfTimes(int smsFreeOfTimes) {
this.smsFreeOfTimes = smsFreeOfTimes;
return this;
}
// other setter...
public PricingModel build() {
return new PricingModel(name, smsFee, smsFreeOfTimes,
callFee, callFreeOfMinute, networkFee, networkFreeOfGB);
}
}
// use case
PricingModel general = new PricingModelBuilder().setName("general")
.setSmsFee(2.4).setSmsFreeOfTimes(5).build();
Builder Pattern 的另一項好處是,它可以多次調用某一方法,假設 PricingModel 有一 List,如果使用 setter,會是開發者準備好 List 後,由 setter 傳入,但在 builder 中可以有延伸,像是傳入參數的檢查,或是提供更簡潔的新增 element 方式。// constructor setter
public void setPromoteProjects(List<String> promoteProjects) {
this.promoteProjects = promoteProjects;
}
// builder method
public PricingModelBuilder addToPromoteProject(String project) {
this.promoteProjects.add(project);
return this;
}
// use case: constructor & builder
PricingModel general = new PricingModel();
List<String> projects = new ArrayList<>();
projects.add("12M");
projects.add("24M");
projects.add("30M");
general.setPromoteProjects(projects);
PricingModel general = new PricingModelBuilder()
.addToPromoteProject("12M")
.addToPromoteProject("24M")
.addToPromoteProject("30M")
.build();
轉載請註明原文網址 https://cookieandcoketw.blogspot.com/2020/06/effective-java-2-constructor-builder.html
沒有留言:
張貼留言