重構:遇到多個引數初始化時可考慮建造者模式
技術標籤:程式碼重構
前面我們提到使用靜態工廠方法代替構造方法,因為靜態工廠相對於構造方法有諸多個優勢。但靜態工廠和構造方法有一個共同的侷限性:它們都不能很好地擴充套件到大量的可選引數。比如我們有一個用於描述欄位屬性的類,一個欄位通常包含:欄位名、欄位標題、欄位是否可見、欄位是否必填等等屬性,其中欄位名、欄位標題是必填的,其它都是非必填。
對於這樣的類,我們應該怎麼設計它。通常來說,大部分的開發者都會想到使用過載的方式來提供幾個可能的構造方法保證客戶端可以快速的創建出這樣的一個欄位物件出來。比如這樣:
public class Field {
private String name;
private String title;
private boolean visible = true;
private boolean allowBlank = true;
public Field(String name, String title) {
this(name, title, true);
}
public Field(String name, String title, boolean visible) {
this(name, title, visible, true);
}
public Field(String name, String title, boolean visible, boolean allowBlank) {
this.name = name;
this.title = title;
this.visible = visible;
this.allowBlank = allowBlank;
}
}
當我們想要建立一個可見且非必填的欄位時,就可以呼叫最短的構造方法建立物件;當我們想要建立一個不可見且必填的欄位時,那我們就得呼叫最長的構造方法。
我這裡只是簡單的舉了個只包含四個屬性的例子,這麼看來這種方式還不算很糟糕。但問題是隨著屬性的增加,它很快就失去了控制。最後我們建立一個物件就不得不這樣:
Field field = new Field("text", "單行文字");
field.setVisible(false);
field.setAllowBlank(false);
field.setDefaultValue(xxx);
field.setReadOnly(false);
...
類似上述的寫法,我們稱之為JavaBeans
模式。這種模式下先建立一個最簡單的物件,然後通過呼叫setter
方法來設定每個引數。這種方式雖然建立物件很容易,寫出來的程式碼閱讀起來也很容易。但是它最大的缺點是:該模式使得把類做成不可變類的可能性不復存在。
說了這麼多,我們現在就正式進入本篇的主題——建造者模式。建造者模式可以保證像過載的構造方法那樣安全,也能像JavaBeans
模式那樣可讀。通常來說,建造者模式不直接生成最終物件,而是讓客戶端得到一個builder
物件,然後在builder
物件上呼叫類似於setter
的方法來設定每個相關的可選引數。最後呼叫無參的build
方法來生成通常不可變的物件。如下:
public class Field {
private String name;
private String title;
private boolean visible = true;
private boolean allowBlank = true;
private Field(Builder builder) {
this.name = builder.name;
this.title = builder.title;
this.visible = builder.visible;
this.allowBlank = builder.allowBlank;
}
public static class Builder {
private String name;
private String title;
private boolean visible = true;
private boolean allowBlank = true;
public Builder(String name, String title) {
this.name = name;
this.title = title;
}
public Builder visible(boolean visible) {
this.visible = visible;
return this;
}
public Builder allowBlank(boolean allowBlank) {
this.allowBlank = allowBlank;
return this;
}
public Field build() {
return new Field(this);
}
}
}
builder
的設值方法返回builder
本身,以便把呼叫連結起來,得到一個流式的API。客戶端程式碼:
Field field = new Builder("text", "單行文字").visible(false).allowBlank(false).build();
總結,如果類的構造方法或者靜態工廠中具有多個引數,設計這種類時,Builder
模式就是一種不錯的選擇,特別適當大多數引數都是可選或者型別相同的時候。