Builder 模式
在開發中,經常用到builder設計模式,但感覺最常見的應用場景就是構造物件引數較多的時候,本文將builder模式梳理總結一下。
定義
非要給builder模式一個定義,我就查看了《Android原始碼設計模式解析與實戰》,以下是其給出的定義:
將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
這樣的總結比較經典,但是感覺離徹底明白其中的含義還差點距離。前半句可以理解為將一個物件的建立過程分多步,後半句可以這樣理解,使用同樣的構建過程,傳遞不同的引數會產生不同的結果。
經典寫法

上圖為builder經典寫法的uml,其實在實際開發過程中,Director部分經常就被去掉了。 如下所示,定義了AbstractBuilder:
public abstract class AbstractBuilder { public abstract void buildPart1(int numOfWheel); public abstract void buildPart2(int numOfSeat); public abstract void buildPart3(int capacity); public abstract Vehicle build(); } 複製程式碼
demo的目標是將車輛Vehicle的構建分離,demo中的Product為Vehicle型別,可以看下Vehicle的定義:
public abstract class Vehicle { // 車輪的數量 protected int numOfWheel; // 座椅的數量 protected int numOfSeat; // 載重,car按照人數,truck按照噸數 protected int capacity; public void setNumOfWheel(int numOfWheel) { this.numOfWheel = numOfWheel; } public void setNumOfSeat(int numOfSeat) { this.numOfSeat = numOfSeat; } public abstract void setCapacity(int capacity); } 複製程式碼
demo中Vehicle的子類有Car(小轎車)和Truck(卡車),Car類的定義如下:
public class Car extends Vehicle { @Override public void setCapacity(int capacity) { // TODO Auto-generated method stub super.capacity = capacity; } @Override public String toString() { // TODO Auto-generated method stub return "car parameter:" + numOfWheel + "-" + numOfSeat + "-" + capacity; } } 複製程式碼
Truck的定義如下:
public class Truck extends Vehicle { @Override public void setCapacity(int capacity) { // TODO Auto-generated method stub super.capacity = capacity; } @Override public String toString() { // TODO Auto-generated method stub return "car parameter:" + numOfWheel + "-" + numOfSeat + "-" + capacity + " ton"; } } 複製程式碼
接下來就看下Car的builder CarBuilder:
public class CarBuilder extends AbstractBuilder { private Car car = new Car(); @Override public void buildPart1(int numOfWheel) { // TODO Auto-generated method stub car.setNumOfWheel(numOfWheel); } @Override public void buildPart2(int numOfSeat) { // TODO Auto-generated method stub car.setNumOfSeat(numOfSeat); } public void buildPart3(int capacity) { // TODO Auto-generated method stub car.setCapacity(capacity); } public Vehicle build() { return car; } } 複製程式碼
TruckBuilder定義如下:
public class TruckBuilder extends AbstractBuilder { private Truck truck = new Truck(); @Override public void buildPart1(int numOfWheel) { // TODO Auto-generated method stub truck.setNumOfWheel(numOfWheel); } @Override public void buildPart2(int numOfSeat) { // TODO Auto-generated method stub truck.setNumOfSeat(numOfSeat); } @Override public void buildPart3(int capacity) { // TODO Auto-generated method stub truck.setCapacity(capacity); } @Override public Vehicle build() { // TODO Auto-generated method stub return truck; } } 複製程式碼
以上定義了CarBuiler, TruckBuiler。雖然在實際開發中經常會省略掉Director部分,為了演示,demo也定義了Director
public class Director { private AbstractBuilder builder; public Director(AbstractBuilder builder) { this.builder = builder; } public void construct(int numOfWheel, int numOfSeat, int capacity) { if (builder != null) { builder.buildPart1(numOfWheel); builder.buildPart2(numOfSeat); builder.buildPart3(capacity); } } } 複製程式碼
OK,所有需要定義的部分已經完成,接下來就去呼叫一下:
public class Client { public static void main(String [] args) { AbstractBuilder builder = new CarBuilder(); Director director = new Director(builder); director.construct(4, 5, 5); Car car = (Car) builder.build(); System.out.println(car); AbstractBuilder builder2 = new TruckBuilder(); Director director2 = new Director(builder2); director2.construct(8, 2, 5); Truck truck = (Truck) builder2.build(); System.out.println(truck); } } 複製程式碼
程式輸入如下: car parameter:4-5-5 truck parameter:8-2-5 ton
demo演示部分將Vehicle的構造過程分3步,執行完3步構建後返回例項物件。
日常寫法
上面的demo是經典的寫法,但在實際開發中,很少寫的那麼標準或者那麼複雜,大多數情況下builder模式主要是為了防止在構建物件時傳遞太多的引數。檢視下以下demo:
public class Student { private String name; private String nickName; private String sex; private int age; private int weight; private int height; public Student(String name, String nickName, String sex, int age, int weight, int height) { // TODO Auto-generated constructor stub this.name = name; this.nickName = nickName; this.sex = sex; this.age = age; this.weight = weight; this.height = height; } @Override public String toString() { // TODO Auto-generated method stub return "student info:name=" + name + "\n" + "nickname=" + nickName + "\n" + "sex=" + sex + "\n" + "age=" + age + "\n" + "weight=" + weight + "\n" + "height=" + height; } public static class Builder { private String name; private String nickName; private String sex; private int age; private int weight; private int height; public Builder name(String name) { this.name = name; return this; } public Builder nickName(String nickName) { this.nickName = nickName; return this; } public Builder sex(String sex) { this.sex = sex; return this; } public Builder age(int age) { this.age = age; return this; } public Builder weight(int weight) { this.weight = weight; return this; } public Builder height(int height) { this.height = height; return this; } public Student build() { return new Student(name, nickName, sex, age, weight, height); } } } 複製程式碼
以下是測試程式:
public class Client { public static void main(String [] args) { Student student = new Student.Builder().name("rock") .nickName("store") .sex("boy") .age(12) .weight(60) .height(176).build(); System.out.println(student); } } 複製程式碼
程式執行結果如下:
student info:name=rock nickname=store sex=boy age=12 weight=60 height=176 複製程式碼
Android中Builder使用場景
在開發過程中,經常使用的builder模式其實就是上文所說的日常寫法,Android中最常見的builder模式就是AlertDialog的建立過程了,以下是AlertDialog建立過程的常見寫法。
AlertDialog.Builder builder = new AlertDialog.Builder(context) .setTitle(title) .setView(view) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setNegativeButton(android.R.string.cancel, null); builder.create().show(); 複製程式碼
感覺很熟悉,這就是我們最常用的AlertDialog的構建過程。扒一扒原始碼,由於AlertDialog.Builder的原始碼較多,就不全部貼出來,感興趣的同學可以自行看一下。
public static class Builder { private final AlertController.AlertParams P; public Builder(Context context) { this(context, resolveDialogTheme(context, ResourceId.ID_NULL)); } ...... public Builder setTitle(@StringRes int titleId) { P.mTitle = P.mContext.getText(titleId); return this; } public Builder setTitle(CharSequence title) { P.mTitle = title; return this; } public Builder setCustomTitle(View customTitleView) { P.mCustomTitleView = customTitleView; return this; } ...... public Builder setMessage(CharSequence message) { P.mMessage = message; return this; } public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) { P.mPositiveButtonText = P.mContext.getText(textId); P.mPositiveButtonListener = listener; return this; } ...... public AlertDialog create() { // Context has already been wrapped with the appropriate theme. final AlertDialog dialog = new AlertDialog(P.mContext, 0, false); 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; } } 複製程式碼
AlertDialog.Buidler類中定義各種set方法,執行完set方法之後再執行create方法便建立了一個AlertDialog。這應該是一個標準的builder模式了。可以發現AlertDialog.Builder執行set方法,其實就是將set引數複製給了物件P。物件P是什麼結構呢?
public static class AlertParams { public final Context mContext; public final LayoutInflater mInflater; public int mIconId = 0; public Drawable mIcon; public int mIconAttrId = 0; public CharSequence mTitle; public View mCustomTitleView; public CharSequence mMessage; public CharSequence mPositiveButtonText; public DialogInterface.OnClickListener mPositiveButtonListener; public CharSequence mNegativeButtonText; public DialogInterface.OnClickListener mNegativeButtonListener; public CharSequence mNeutralButtonText; public DialogInterface.OnClickListener mNeutralButtonListener; public boolean mCancelable; public DialogInterface.OnCancelListener mOnCancelListener; public DialogInterface.OnDismissListener mOnDismissListener; public DialogInterface.OnKeyListener mOnKeyListener; public CharSequence[] mItems; public ListAdapter mAdapter; public DialogInterface.OnClickListener mOnClickListener; public int mViewLayoutResId; public View mView; public int mViewSpacingLeft; public int mViewSpacingTop; public int mViewSpacingRight; public int mViewSpacingBottom; public boolean mViewSpacingSpecified = false; public boolean[] mCheckedItems; public boolean mIsMultiChoice; public boolean mIsSingleChoice; public int mCheckedItem = -1; public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener; public Cursor mCursor; public String mLabelColumn; public String mIsCheckedColumn; public boolean mForceInverseBackground; public AdapterView.OnItemSelectedListener mOnItemSelectedListener; public OnPrepareListViewListener mOnPrepareListViewListener; public boolean mRecycleOnMeasure = true; ...... } 複製程式碼
可以發現,AlertController.AlertParams型別的物件P其實就是存放了構建AlertDialog需要的各種引數。物件P中海油其他函式操作,感興趣的同學可以去看一下。
將引數儲存到P物件,然後執行create函式,建立新的AlertDialog物件,然後P中存放的屬性設定給新建的AlertDilaog物件,這樣,就完成了AlertDialog的構建。
總結
Builder模式的目標是將複雜物件的建立過程進行分解,使物件的構建與表示分離,使得同樣的構建過程可以建立不同的表示。在實際開發過程中,通常是在複雜物件內部申明靜態內部類Builder,在Builder中儲存複雜物件的屬性,然後使用create或者build函式將儲存的屬性設定給物件。
其實日常開發過程中使用builder模式還沒有讓我們領略到builer模式的強大,建議參考下這篇文章體會一下: www.cnblogs.com/happyhippy/…