Java 解決構造方法引數過多-builder模式(effect java 學習筆記2)
一、前景:
一般情況我們不會遇到這樣的情況,使用靜態工廠方法,或者構造方法就足夠。但是它們也有一個限制就是,它們不能很好的擴充套件到很多可選引數的場景。隨著我們業務的深入,某些java bean 中的引數將會越來越多,我們新增的構造方法也相應的增加。想想一個10個引數的構造方法,我胃痛。
既然想要用builder模式,我們首先需要知道傳統方法的不足。
二、可伸縮構造方法 VS builder模式
我們都用程式碼例項來說話
1、可伸縮構造方法
public class Student {
private final String name; // required
private final String sex; // required
private final int weight; // optional
private final int height; // optional
private final int age; // optional
public Student(String name,String sex) {
this(name,sex,0);
}
public Student(String name,String sex,int w) {
this(name,w,int w,int h) {
this(name,h,0);
}
public Student(String name,int h,int a) {
this.name = name;
this.sex = sex;
this.weight = w;
this.height = h;
this.age = a;
}
}
複製程式碼
當我們想建立一個Student例項的時候,可以設定所有引數的最短的夠著方法如下:
Student student = new Student("小明","男",50,150,16);
通常情況下,這個構造方法可能需要許多你不想設定的引數(是不是有點不爽),隨著引數的不斷增加,它很快會失控的。(你也會發瘋的)。首先我們很難讀懂這個方法,其次如果反轉了兩個引數,編譯器是不會報錯的。比如身高和體重填反了,糟心。。。。。
2、JavaBean模式
當在構造方法中遇到許多可選引數時,另一種選擇是 JavaBeans 模式,在這種模式中,呼叫一個無引數的構造函 數來建立物件,然後呼叫 setter 方法來設定每個必需的引數和可選引數:
public class Student {
private String name; // required
private String sex; // required
private int weight; // optional
private int height; // optional
private int age; // optional
public Student() {}
public void setName(String name) {
this.name = name;
}
public void set Sex(String sex) {
this.sex = sex;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setHeight(int height) {
this.height = height;
}
public void setAge(int age) {
this.age = age;
}
}
複製程式碼
這種模式沒有伸縮構造方法的缺點。有點冗長,但建立例項容易,並且容易閱讀
Student student = new Student();
student.setName("小明");
student.setSex("男");
student.setAge(16);
student.setWeight(50);
student.setHeight(150);
複製程式碼
JavaBeans 模式本身有嚴重的缺陷。由於構造方法在多次呼叫中被分割,所以在構造過程中 JavaBean 可能處於不一致的狀態。該類沒有通過檢查構造引數引數的有效性來執行一致性的選項。在不一致的狀態下嘗試使用 物件可能會導致與包含 bug 的程式碼大相徑庭的錯誤,因此很難除錯。一個相關的缺點是,JavaBeans 模式排除了讓類 不可變的可能性,並且需要在程式設計師的部分增加工作以確保執行緒安全
3、builder模式
builder 模式結合了可伸縮構造方法模式的安全性和JavaBean模式的可讀性。
public class Student {
private final String name;
private final String sex;
private final int weight;
private final int height;
private final int age;
private Student(Builder builder) {
this.name = builder.name;
this.sex = builder.sex;
this.weight = builder.weight;
this.height = builder.height;
this.age = builder.age;
}
public static class Builder {
private final String name; // required
private final String sex; // required
private int weight; // optional
private int height; // optional
private int age; // optional
public Builder(String name,String sex) {
this.name = name;
this.sex = sex;
}
public Builder setWeight(int weight) {
this.weight = weight;
return this;
}
public Builder setHeight(int height) {
this.height = height;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Student build() {
return new Student(this);
}
}
}
複製程式碼
我首先來噴一下,程式碼量翻倍了。但是Student 類是不可變的,所有的引數預設值都在一個地方。builder 的 setter 方法返回 builder 本身, 這樣呼叫就可以被連結起來,從而生成一個流暢的 API。例項如下:
Student student = new Student.Builder("小明","男").setWeight(50)
.setHeight(150).setAge(16).build();
複製程式碼
程式碼很容易編寫,更重要的是易於閱讀。 Builder 模式模擬 Python 和 Scala 中的命名可選引數。 這裡沒有涉及到有效性的檢查
總結:
builder 對構造方法的一個微小的優勢是,builder 可以有多個可變引數,因為每個引數都是在它自己的方法中指 定的。
Builder 模式非常靈活。 單個 builder 可以重複使用來構建多個物件。 builder 的引數可以在構建方法的呼叫之間 進行調整,以改變建立的物件。 builder 可以在建立物件時自動填充一些屬性,例如每次建立物件時增加的序列號。
Builder 模式也有缺點。為了建立物件,首先必須建立它的 builder。雖然建立這個 builder 的成本在實踐中不太可 能被注意到,但在效能關鍵的情況下可能會出現問題。而且,builder 模式比伸縮構造方法模式更冗長,因此只有在 有足夠的引數時才值得使用它,比如四個或更多。但是請記住,如果希望在將來新增更多的引數。但是,如果從構造 方法或靜態工廠開始,並切換到 builder,當類演化到引數數量失控的時候,過時的構造方法或靜態工廠就會面臨尷 尬的處境。因此,所以,最好從一開始就建立一個 builder。
總而言之,當設計類的構造方法或靜態工廠的引數超過幾個時,Builder 模式是一個不錯的選擇,特別是如果許 多引數是可選的或相同型別的。客戶端程式碼比使用伸縮構造方法(telescoping constructors)更容易讀寫,並且 builder 比 JavaBeans 更安全。
ps:如果有微信讀書的書友,可以來微信讀書群傳送門找組織。