最近工作之余一直在斷斷續續的研究媒體選擇庫,在 GitHub 上搜了好多庫對比看了看,在學習研究過程中發現其中都運用了 Builder模式,今天就一起學習一下 Builder模式,順便看看它在 Android 源碼中的應用。
我們在實際開發中,必然會遇到一些需求需要構建一個十分復雜的對象,譬如本人最近開發的項目中就需要構建一個媒體庫選擇器,類似微信和眾多app中都有的圖片、視屏資源選擇器。這個選擇器(Selector)是相對比較復雜的,它需要很多屬性,比如:
- 媒體資源類型: 圖片/視屏
- 選擇模式: 單選/多選
- 多選上限
- 是否支持預覽
- 是否支持裁剪
- 選擇器UI風格樣式
- ......
通常我們可以通過構造函數的參數形式去寫一個實現類
Selector(int type)
Selector(int type, int model)
Selector(int type, int model, int maxSize)
Selector(int type, int model, int maxsize, boolean isPreview)
......
再或者可以使用 getter 和 setter 方法去設置各個屬性
public class Selector {
private int type; // 媒體資源類型: 圖片/視屏
private int model; // 選擇模式: 單選/多選
private int maxSize; // 多選上限
private boolean isPreview; // 是否支持預覽
private ...... // 更多屬性就不一一舉例了,大家腦部一下
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getModel() {
return model;
}
public void setModel(int model) {
this.model = model;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public boolean isPreview() {
return isPreview;
}
public void setPreview(boolean isPreview) {
this.isPreview = isPreview;
}
.......
}
先分析一下這兩種構建對象的方式:
第一種方式通過重載構造方法實現,在參數不多的情況下,是比較方便快捷的,一旦參數多了,就會產生大量的構造方法,代碼可讀性大大降低,并且難以維護,對調用者來說也是一種災難;
第二種方法可讀性不錯,也易于維護,但是這樣子做對象會產生不確定性,當你構造 Selector 時想要傳入全部參數的時候,那你就必需將所有的 setter 方法調用完成之后才算創建完成。然而一部分的調用者看到了這個對象后,以為這個對象已經創建完畢,就直接使用了,其實 Selector 對象并沒有創建完成,另外,這個 Selector 對象也是可變的,不可變類的所有好處也將隨之消散。
寫到這里其實大家已經想到了,肯定有更好的辦法去解決這個問題,是的,你猜對了, 今天我們的主角 Builder模式 就是為解決這類問題而生的。下面我們一起看看 Builder模式 是如何優雅的處理這類尷尬的。
模式的定義
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
模式的使用場景
- 相同的方法,不同的執行順序,產生不同的事件結果時;
- 多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時;
- 產品類非常復雜,或者產品類中的調用順序不同產生了不同的效能,這個時候使用建造者模式非常合適;
化解上述尷尬的過程
Builder模式 屬于創建型,一步一步將一個復雜對象創建出來,允許用戶在不知道內部構建細節的情況下,可以更精細地控制對象的構造流程。還是上面同樣的需求,使用 Builder模式 實現如下:
public class Selector {
private final int type; // 媒體資源類型: 圖片/視屏
private final int model; // 選擇模式: 單選/多選
private final int maxSize; // 多選上限
private final boolean isPreview; // 是否支持預覽
private final ...... // 更多屬性就不一一舉例了,大家腦部一下
// 私有構造方法
private Selector(SelectorBuilder selectorBuilder){
this.type = selectorBuilder.type;
this.model = selectorBuilder.model;
this.maxSize = selectorBuilder.maxSize;
this.isPreview = selectorBuilder.isPreview;
......
/** 由于所有屬性都是 final 修飾的,所以只提供 getter 方法 **/
public int getType() {
return type;
}
public int getModel() {
return model;
}
public int getMaxSize() {
return maxSize;
}
public boolean isPreview() {
return isPreview;
}
.......
// Builder 方法
public static class SelectorBuilder{
private int type; // 媒體資源類型: 圖片/視屏
private int model; // 選擇模式: 單選/多選
private int maxSize; // 多選上限
private boolean isPreview; // 是否支持預覽
private ...... // 更多屬性就不一一舉例了,大家腦部一下
public SelectorBuilder() {
// 設置各個屬性的默認值
this.type = 1;
this.model = 1;
this.maxSize = 9;
this.isPreview = true;
......
}
public SelectorBuilder setType(int type){
this.type = type;
return this;
}
public SelectorBuilder setModel(int model) {
this.model = model;
return this;
}
public SelectorBuilder setMaxSize(int maxSize) {
this.maxSize = maxSize;
return this;
}
...... // 全部的setter方法就省略了
public Selector build() {
return new Selector(this);
}
}
}
值得注意的是 Selector 的構造方法是私有的,并且所有屬性都是 final 修飾的,是不可變屬性,對調用者也只提供 getter 方法,SelectorBuilder 內部類可以根據調用者的具體需求隨意接收任意多個參數,應為我們再 SelectorBuilder 的構造方法中為每一個參數都設置了默認值,即使調用者調用時漏傳某個參數,也不會影響整個創建過程。當我們將我們需用的所有參數傳入后,隨后調用 build() 構造 Selector 對象,代碼如下:
new Selector.SelectorBuilder()
.setType(2)
.setModel(2)
.setMaxSize(3)
. ......
.build();
是不是很簡潔?意不意外?驚不驚喜?你沒看錯,就是這么厲害!
Android源碼中的模式實現
在Android源碼中,我們最常用到的Builder模式就是AlertDialog.Builder, 使用該Builder來構建復雜的AlertDialog對象。簡單示例如下 :
// 顯示基本的AlertDialog
private void showDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon);
builder.setTitle("Title");
builder.setMessage("Message");
builder.setPositiveButton("Button1",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("點擊了對話框上的Button1");
}
});
builder.setNeutralButton("Button2",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("點擊了對話框上的Button2");
}
});
builder.setNegativeButton("Button3",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("點擊了對話框上的Button3");
}
});
builder.create().show(); // 構建AlertDialog, 并且顯示
}
優缺點
當然在代碼世界,永遠沒有絕對的完美,我們只是在走向完美的道路上盡力去填補一個個坑而已。 Builder模式 有它的好處,給我們帶來了方便,但同時也會犧牲一些美好,這是不可避免的。
優點
- 良好的封裝性, 使用建造者模式可以使客戶端不必知道產品內部組成的細節;
- 建造者獨立,容易擴展;
- 在對象創建過程中會使用到系統中的一些其它對象,這些對象在產品對象的創建過程中不易得到。
缺點
- 會產生多余的Builder對象,消耗內存;
- 對象的構建過程暴露。