最近工作之余一直在斷斷續(xù)續(xù)的研究媒體選擇庫靖榕,在 GitHub 上搜了好多庫對比看了看矗愧,在學習研究過程中發(fā)現其中都運用了 Builder模式峻贮,今天就一起學習一下 Builder模式,順便看看它在 Android 源碼中的應用哩都。
我們在實際開發(fā)中,必然會遇到一些需求需要構建一個十分復雜的對象婉徘,譬如本人最近開發(fā)的項目中就需要構建一個媒體庫選擇器,類似微信和眾多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 方法調用完成之后才算創(chuàng)建完成。然而一部分的調用者看到了這個對象后混槐,以為這個對象已經創(chuàng)建完畢编兄,就直接使用了,其實 Selector 對象并沒有創(chuàng)建完成声登,另外狠鸳,這個 Selector 對象也是可變的,不可變類的所有好處也將隨之消散捌刮。
寫到這里其實大家已經想到了碰煌,肯定有更好的辦法去解決這個問題,是的绅作,你猜對了芦圾, 今天我們的主角 Builder模式 就是為解決這類問題而生的。下面我們一起看看 Builder模式 是如何優(yōu)雅的處理這類尷尬的俄认。
模式的定義
將一個復雜對象的構建與它的表示分離个少,使得同樣的構建過程可以創(chuàng)建不同的表示洪乍。
模式的使用場景
- 相同的方法,不同的執(zhí)行順序夜焦,產生不同的事件結果時壳澳;
- 多個部件或零件,都可以裝配到一個對象中茫经,但是產生的運行結果又不相同時巷波;
- 產品類非常復雜,或者產品類中的調用順序不同產生了不同的效能卸伞,這個時候使用建造者模式非常合適抹镊;
化解上述尷尬的過程
Builder模式 屬于創(chuàng)建型,一步一步將一個復雜對象創(chuàng)建出來荤傲,允許用戶在不知道內部構建細節(jié)的情況下垮耳,可以更精細地控制對象的構造流程。還是上面同樣的需求遂黍,使用 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 的構造方法中為每一個參數都設置了默認值唬党,即使調用者調用時漏傳某個參數鹃共,也不會影響整個創(chuàng)建過程。當我們將我們需用的所有參數傳入后驶拱,隨后調用 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慕嚷, 并且顯示
}
優(yōu)缺點
當然在代碼世界,永遠沒有絕對的完美,我們只是在走向完美的道路上盡力去填補一個個坑而已喝检。 Builder模式 有它的好處嗅辣,給我們帶來了方便,但同時也會犧牲一些美好挠说,這是不可避免的澡谭。
優(yōu)點
- 良好的封裝性, 使用建造者模式可以使客戶端不必知道產品內部組成的細節(jié)损俭;
- 建造者獨立蛙奖,容易擴展;
- 在對象創(chuàng)建過程中會使用到系統中的一些其它對象杆兵,這些對象在產品對象的創(chuàng)建過程中不易得到外永。
缺點
- 會產生多余的Builder對象,消耗內存拧咳;
- 對象的構建過程暴露。