構建器(Builder)模式
一、引言
在日常的開發中,我們可能經常能看到如下的程式碼:
PrivateKeyDecryptParam param = new PrivateKeyDecryptParam.Builder() .uAppId(uAppId) .containerId(containerId) .cipher(cipher) .signFlag(signFlag) .build();
在Android中,也會看到下面建立AlertDialog程式碼:
new AlertDialog.Builder(this) .setTitle("標題") .setMessage("內容") .setCancelable(true) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { } }) .show();
觀察上面這兩段程式碼,都有一個共同的特點,就是都可以進行鏈式操作,這就是我們要學習的Builder模式,下面就來詳細學習一下。
二、Builer模式的使用場景
在《Effective Java第2版》書中有提到,當遇到多個構造器引數時,要考慮使用構建器(Builder模式)。
舉個例子,比如在專案中,我們需要新建一個Person類,假設該類有7個屬性(現實中遠不止這幾個引數),其中有2個是必要的引數需要初始化,分別是id和name。
1、使用JavaBean的setter方法來設定物件屬性
最常見的寫法應該是寫成JavaBean。程式碼如下:
public class Person { //必要引數 private int id; private String name; //可選引數 private int age; private String sex; private String phone; private String address; private String desc; public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setSex(String sex) { this.sex = sex; } public void setPhone(String phone) { this.phone = phone; } public void setAddress(String address) { this.address = address; } public void setDesc(String desc) { this.desc = desc; } }
在使用該Person類的時候,會寫出如下的程式碼:
Person person = new Person();
person.setId(1);
person.setName("張小毛");
person.setAge(22);
person.setSex("男");
person.setPhone("19999999999");
person.setAddress("beijing");
person.setDesc("JavaBeans模式");
這種JavaBean的方式也是常見的構造物件並賦值的方式,這種方式的好處是:
(1)、易於閱讀,並且可以只對有用的成員變數賦值;
但它的缺點是:
(1)、成員變數不可以是 final 型別,失去了不可變物件的很多好處;
(2)、物件狀態不連續,你必須呼叫7次setter方法才能得到一個具備7個屬性值得變數,在這期間使用者可能拿到不完整狀態的物件。如果有N個屬性,豈不是要person.setXXX呼叫N次?此種方式不優雅。
最重要的缺點是第二條:物件狀態不連續。什麼意思呢?
解釋一下:這種方式是 先建立物件、後賦值,使用者不知道什麼時候拿到的物件是完整的,構建完成的。很有可能你只setter了一兩個屬性就返回了,一些必要的屬性沒有被賦值。
2、使用重疊構造器
在這種模式下,需要提供一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個有兩個可選引數,依此類推,最後一個構造器包含所有的可選引數。程式碼如下:
public class Person {
//必要引數
private final int id;
private final String name;
//可選引數
private int age;
private String sex;
private String phone;
private String address;
private String desc;
public Person(int id, String name) {
this(id, name, 0);
}
public Person(int id, String name, int age) {
this(id, name, age, "");
}
public Person(int id, String name, int age, String sex) {
this(id, name, age, sex, "");
}
public Person(int id, String name, int age, String sex, String phone) {
this(id, name, age, sex, phone, "");
}
public Person(int id, String name, int age, String sex, String phone, String address) {
this(id, name, age, sex, phone, address, "");
}
public Person(int id, String name, int age, String sex, String phone, String address, String desc) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.phone = phone;
this.address = address;
this.desc = desc;
}
}
從上面的程式碼可以看出,當你想要建立例項的時候,就利用引數列表最短的構造器,但該列表中包含了要設定的所有引數,其餘都為預設值:
Person person = new Persion(1, "張小毛");
這個構造器呼叫,通常需要許多你本不想設定的引數,但還是不得不為它們傳遞值。
這種方式的優點就是:簡單!!!!(這是對開發者而言),但使用者在使用時,可得仔細瞭解你每個建構函式,否則一不小心填錯順序也不知道。而且如果有十幾個屬性,就歇菜了……(我也沒見過有十幾個引數的建構函式)
所以缺點是:
只適用於成員變數少的情況,太多了不容易理解、維護。
簡而言之:重疊構造器可行,但是當有許多引數的時候,建立使用程式碼會很難寫,並且較難以閱讀。
三、變種Builder模式
對於上述分析的兩種方法,都存在優點和缺點,為了解決上述兩種構建方式的不足,偉大的程式設計師們創造出了變種 Builder模式。直接看程式碼:
public class Person {
//必要引數
private final int id;
private final String name;
//可選引數
private int age;
private String sex;
private String phone;
private String address;
private String desc;
private Person(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.sex = builder.sex;
this.phone = builder.phone;
this.address = builder.address;
this.desc = builder.desc;
}
public static class Builder {
//必要引數
private final int id;
private final String name;
//可選引數
private int age;
private String sex;
private String phone;
private String address;
private String desc;
public Builder(int id, String name) {
this.id = id;
this.name = name;
}
public Builder age(int val) {
this.age = val;
return this;
}
public Builder sex(String val) {
this.sex = val;
return this;
}
public Builder phone(String val) {
this.phone = val;
return this;
}
public Builder address(String val) {
this.address = val;
return this;
}
public Builder desc(String val) {
this.desc = val;
return this;
}
public Person build() {
return new Person(this);
}
}
}
觀察上述程式碼,可以看到變種Builder模式包括以下內容:
(1)、在要構建類的內部,建立一個靜態內部類Builder;
(2)、靜態內部類的屬性要與構建類的屬性一致;
(3)、構建類的構造引數是靜態內部類,使用靜態內部類的變數為構建類逐一賦值;
(4)、靜態內部類提供引數的setter方法,並且返回值是當前Builder物件;
(5)、最終提供一個build方法new出來一個構建類的物件,引數是當前Builder物件;
呼叫程式碼如下:
public class Test {
public static void main(String[] args) {
Person person = new Person.Builder(1, "張小毛")
.age(22).sex("男").desc("使用builder模式").build();
System.out.println(person.toString());
}
}
對變種Builer模式的總結:
(1)、變種Builder模式目的在於:減少物件建立過程中引入的多個建構函式、可選引數以及多個setter過度使用導致的不必要的複雜性。
(2)、優點就是:看起來很整齊;先賦值,後建立物件,最終呼叫build()方法才建立了構建類的物件,保證了狀態的完整性。
(3)、缺點嘛,就是需要額外寫的程式碼多了點。
四、總結:
變種Builer模式相比於重疊構造器模式和JavaBeans模式,Builder模式實現的物件更利於使用。
對Builer模式使用方法的總結:
(1)、外部類的建構函式私有,且引數為靜態內部類;
(2)、靜態內部類擁有外部類相同的屬性;
(3)、為每一個屬性,寫一個方法,返回的是Builer;
(4)、最後一個方法是build方法,用於構建一個外部類;