Java 設計模式情景分析——建造者模式
當我們遇到類似汽車的裝配,需要車輪、方向盤、發動機,還有各種小零件時,為了在構建過程中隱藏實現細節,就可以使用建造者模式 (Builder模式) 將部件和組裝過程分離,使得構建過程和部件都可以自由擴充套件,兩者之間的耦合也降到最低。接下來我們看一下定義,建造者模式是將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
1.建造者模式的使用情景
1)相同的方法,不同的執行順序,產生不同的事件結果時;
2)多個部件,都可以裝配到一個物件中,但是產生的執行結果又不相同時;
3)產品類非常複雜,或者產品類中的呼叫順序不同產生了不同的作用時;
4)當初始化一個物件特別複雜時;
2.程式中使用建造者模式的優缺點
- | 建造者模式 |
---|---|
優點 | 良好的封裝性,使用建造者模式可以使客戶端不必知道產品內部組成的細節;建造者獨立,擴充套件性好。 |
缺點 | 會產生多餘的 Builder 物件,消耗記憶體。 |
3.建造者模式的UML類圖
1.經典模式
- Product產品類:產品的抽象類
- Builder:抽象Builder類,規範產品的組建
- ConcreteBuilder:具體的Builder類
- Director:統一組裝過程
值得注意的是,在 Android 開發中,Director 物件經常會被省略,而直接使用一個 Builder 來進行物件的組裝,這個 Builder 通常為鏈式呼叫,下面我們看一下鏈式呼叫的類圖。
2.簡化模式 鏈式呼叫
- Product產品類:產品的抽象類
- Builder:抽象Builder類,規範產品的組建
- ConcreteBuilder:具體的Builder類,鏈式呼叫
下面我們給出的實現也基於鏈式呼叫的。
4.建造者模式的實現——鏈式呼叫
1.定義Product產品類(以組裝計算機為例):
public abstract class Computer {
protected String board;
protected String display;
protected String os;
protected Computer () {
}
// 設定主機板
public void setmBoard(String board) {
this.board = board;
}
// 設定顯示器
public void setmDisplay(String display) {
this.display = display;
}
// 設定作業系統
public abstract void setmOS();
@Override
public String toString() {
return "Computer{" + "board='" + board + '\'' + ", display='" + display + '\'' +
", os='" + os + '\'' + '}';
}
}
具體的 Computer 類:
public class MacBook extends Computer {
protected MacBook() {
}
@Override
public void setmOS() {
os = "mac OS Sierra";
}
}
2.定義抽象Builder類,規範產品的組建:
public abstract class Builder {
// 設定主機板
public abstract Builder setBoard(String board);
// 設定顯示器
public abstract Builder setDisplay(String display);
// 設定作業系統
public abstract Builder setOS();
// 建立 Computer
public abstract Computer create();
}
具體的Builder類:
public class MacBookBuilder extends Builder {
private Computer mComputer = new MacBook();
@Override
public Builder setBoard(String board) {
mComputer.setmBoard(board);
return this;
}
@Override
public Builder setDisplay(String display) {
mComputer.setmDisplay(display);
return this;
}
@Override
public Builder setOS() {
mComputer.setmOS();
return this;
}
@Override
public Computer create() {
return mComputer;
}
}
鏈式呼叫的關鍵點是每個 setter 方法都返回自身,也就是 return this; 這樣就使得 setter 方法可以為鏈式呼叫。
3.測試程式碼:
@Test
public void demo() {
// 鏈式呼叫
Computer computer = new MacBookBuilder().setBoard("Intel").setDisplay("Retina").setOS().create();
System.out.println("Computer info:" + computer.toString());
}
列印結果:Computer info:Computer{board=’Intel’, display=’Retina’, os=’mac OS Sierra’}
5.Android系統原始碼中的應用情景
在Android系統原始碼中,最常用到建造者模式的就是 AlertDialog.Builder,使用該 Builder 構建複雜的 AlertDialog 物件,組裝 Dialog 的各個部分,達到構建和表示分離的效果,其使用的也是鏈式呼叫,下面來看一個簡單的應用:
new android.support.v7.app.AlertDialog.Builder(context)
.setTitle("AlertDialog")
.setMessage("Something important.")
.setCancelable(false) // 設定點選Dialog以外的介面不消失,按返回鍵也不起作用
.setPositiveButton("OK", new android.content.DialogInterface.OnClickListener() {
@Override
public void onClick(android.content.DialogInterface dialogInterface, int i) {
}
})
.setNegativeButton("Cancel", null)
.show();
我們看一下 android.support.v7.app.AlertDialog 的相關原始碼:
public class AlertDialog extends AppCompatDialog implements DialogInterface {
// AlertController 接收 Builder 成員變數 P 中的各個引數
final AlertController mAlert;
// 建構函式
protected AlertDialog(Context context) {
this(context, 0);
}
protected AlertDialog(Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
// 程式碼省略
// 實際上呼叫 mAlert 的 setTitle 方法
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
// 程式碼省略
public static class Builder {
// 1.儲存 AlertDialog 的各個引數,例如 title、message
private final AlertController.AlertParams P;
// 程式碼省略
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
@NonNull
public Context getContext() {
return P.mContext;
}
// 程式碼省略
// 2.設定各種引數
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
// 程式碼省略
// 3.構建 AlertDialog,傳遞引數
public AlertDialog create() {
// 4.呼叫 new AlertDialog 構造物件,並且將引數傳遞給個體 AlertDialog
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
// 5.將 P 中的引數應用到 dialog 中的 mAlert 物件中
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
}
上述程式碼中,Builder 類可以設定 AlertDialog 中的各個引數,這些引數都儲存在型別為 AlertController.AlertParams 的成員變數 P 中,在呼叫 create() 方法時會建立 AlertDialog,並且將 P 中儲存的引數應用到 AlertDialog 的 mAlert 物件中,即 P.apply(dialog.mAlert); 程式碼段。我們再看 apply 方法的實現:
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
// 如果設定了 mItems,則表示是單選或者多選列表,此時建立一個 ListView
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
// 將 mView 設定給 Dialog
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
}
在 apply() 方法中,只是將 AlertParams 引數設定到 AlertController 中,然後當我們獲取到 AlertDialog 物件後,再通過 show() 方法就可以顯示這個對話方塊了。
建造者模式在 Android 開發中 也較為常用,通常作為配置類的構造器將配置的構建和表示分離開來,同時也是將配置從目標類中隔離出來,避免過多的 setter 方法。