本文基於《Effective Java》一書中的第二條;
前言一般我們寫引數如果寫個一兩個,那就可以了,如果寫七八個,那就有點難受了。如果寫十幾個?尼瑪,難受,我要去緩緩。
於是乎,一種新的方法策略運用而生。那就是builder模式,在構造方法的引數過多時,可以方便的進行建立一個類物件。所以本文的中心主旨一句話總結:當構造方法的引數過多時,推薦使用builder模式
既然推薦使用builder模式,那我們一個一個來,分析一下如果不使用builder模式有什麼缺點。
一、傳統方式的缺點1、可伸縮構造方法可伸縮構造方法就是我們平時書寫最常見的那種,請看下文程式碼;
public class Student { private int id; //必要 private String name;//必要 private int age; //可選 private int sclass; //可選 private int height;//可選 private float weight;//可選 private float score;//可選 //建構函式1:預設構造方法 public Student() {}; //構造方法2:必要欄位構造方法 public Student(int id, String name) { this.id = id; this.name = name; } //構造方法3:全部欄位構造方法 public Student(int id, String name, int age, int sclass, int height, float weight, float score) { super(); this.id = id; this.name = name; this.age = age; this.sclass = sclass; this.height = height; this.weight = weight; this.score = score; }}
下面如果我們要建立一個Student類,一般這樣建立,看下面程式碼:
缺點1:反轉欄位,編譯器不會報錯比如上面的欄位裡面有一個weight和一個score,都是float型別,如果在new一個Student類時,不小心寫反了,編譯器不會察覺。
缺點3:不想設定的引數,卻不得不設定值有時候我們的Student只想著設定ID、name和age欄位,其他的無關緊要,但是這種模式必須要設定所有的屬性值。
既然上面有這些缺點,我們可能還想到另外一種方式,那就是javaBean。
2、javaBean模式先看javaBean模式如何寫的。
public class Student { private int id; //必要 private String name;//必要 private int age; //可選 private int sclass; //可選 private int height;//可選 private float weight;//可選 private float score;//可選 //建構函式1:預設構造方法 public Student() {} //getter和setter方法 public int getId() {return id;} public void setId(int id) {this.id = id;} public String getName() {return name;} public void setName(String name) {this.name = name;} public int getAge() {return age;} public void setAge(int age) {this.age = age;} public int getSclass() {return sclass;} public void setSclass(int sclass) {this.sclass = sclass;} public int getHeight() {return height;} public void setHeight(int height) {this.height = height;} public float getWeight() {return weight;} public void setWeight(float weight) {this.weight = weight;} public float getScore() {return score;} public void setScore(float score) {this.score = score;};}
這種模式,看起來還比較舒服,只是設定了相應的getter和setter方法。再來看看如何使用這種方式去new一個Student類。
缺點1:構造過程中 JavaBean可能處於不一致的狀態JavaBeans 模式本身有嚴重的缺陷。由於構造方法在多次呼叫中被分割,所以在構造過程中 JavaBean 可能處於不一致的狀態。該類沒有透過檢查構造引數引數的有效性來執行一致性的選項。在不一致的狀態下嘗試使用物件可能會導致與包含 bug 的程式碼大相徑庭的錯誤,因此很難除錯。
說一下我對其的理解,在上面的例子中,我們的student1物件被多次呼叫了set方法,但是可能有時候在用到這個bean時,剩下的setter方法還沒有做完,於是再次呼叫時發現同一個javaBean呈現出了兩種狀態。於是處於一種不一致的狀態。
缺點2:無法保證javaBean的不可變性使用第一種模式可伸縮構造方法例項化之後不會更改可變性,所有的資料都是確定好了的。也可以保證執行緒安全。但是提供了setter方法,就不能保證了。比如:
既然前面兩種都存在各種各樣的問題。現在我們再來看今天的主題builder模式,
二、builder模式還是老樣子,我們先看看builder模式長得什麼樣子。再來分析一下他的優缺點。
public class Student { private int id; // 必要 private String name;// 必要 private int age; // 可選 private int sclass; // 可選 private int height;// 可選 private float weight;// 可選 private float score;// 可選 public Student(Builder builder) { this.id = builder.id; this.name = builder.name; this.age = builder.age; this.sclass = builder.sclass; this.height = builder.height; this.weight = builder.weight; this.score = builder.score; } public static class Builder { private int id; // 必要 private String name;// 必要 private int age; // 可選 private int sclass; // 可選 private int height;// 可選 private float weight;// 可選 private float score;// 可選 // 必要引數的構造方法 public Builder(int id, String name) { this.id = id; this.name = name; } public Builder setId(int id) { this.id = id; return this; } public Builder setName(String name) { this.name = name; return this; } public Builder setAge(int age) { this.age = age; return this; } public Builder setSclass(int sclass) { this.sclass = sclass; return this; } public Builder setHeight(int height) { this.height = height; return this; } public Builder setWeight(float weight) { this.weight = weight; return this; } public Builder setScore(float score) { this.score = score; return this; } // 對外提供的 public Student build() { return new Student(this); } }}
上面的程式碼是在內部構造了一個Builder類,然後我們看看如何去使用。
優點1:不存在反轉欄位的情況上面可以看出,每次新增新欄位值的時候是透過set方式進行的。具有javaBean的優點。
優點2:靈活構造引數我們把必要的欄位一寫,那些非必要的欄位我們可以自己選擇是不是要set。
優點3:不存在不一致狀態使用builder模式,物件的建立必須要等到build完成才可以。
優點4:使用靈活單個 builder 可以重複使用來構建多個物件。 builder 的引數可以在構建方法的呼叫之間進行調整,以改變建立的物件。 builder 可以在建立物件時自動填充一些屬性,例如每次建立物件時增加的序列號。
缺點:為了建立物件,首先必須建立它的 builder。雖然建立這個 builder 的成本在實踐中不太可能被注意到,但在效能關鍵的情況下可能會出現問題。而且,builder 模式比伸縮構造方法模式更冗長,因此只有在有足夠的引數時才值得使用它,比如四個或更多。
但是,如果從構造方法或靜態工廠開始,並切換到 builder,當類演化到引數數量失控的時候,過時的構造方法或靜態工廠就會面臨尷尬的處境。因此,所以,最好從一開始就建立一個 builder。
總結如果我們的引數比較多時,builder模式是一個不錯的選擇,如果比較少時,由於Builder本身也是個物件佔用一定的資源,所以還是使用可伸縮或者是javaBean的那種模式比較好。