標籤:比較 活性 rgs 優勢 不難 測試案例 表示 優點 方式
一、引言
在Android開發中,採用Builder模式的代碼隨處可見,比如說Android系統對話方塊AlertDialog的使用或者是Android中的通知欄(Notification)的使用,又比如說在一些常用的第三方庫中也隨處可見其蹤跡,比如說一些常用的網路請求庫如OkHttp或者是retrofit,又或者是圖片載入庫Glide中也不缺乏它的應用。
為什麼Builder模式在Android或是Java開發中這麼火呢?因為它相較於建構函式或者是Get/Set方法,它的靈活性和封裝性上都比較有優勢。下面就通過具體的例子說明下Builder模式的具體使用方式,直接結合代碼會更加直觀。
二、類執行個體初始化需求的實現
首先提出我們的需求:
現在我們想要執行個體化一個類,如下所示,其中有些屬性是必選的,而有些屬性是可選的。如下所示:
public class User { private final String mFirstName; //必選 private final String mLastName; //必選 private final String mGender; //可選 private final int mAge; //可選 private final String mPhoneNo; //可選}
方法一:
** * 定義多個重載的建構函式實現 * Created by DB on 2017/6/23. */public class User3 { private final String mFirstName; private final String mLastName; private final String mGender; private final int mAge; private final String mPhoneNo; public User3(String mFirstName,String mLastName){ this(mFirstName,mLastName,""); } public User3(String mFirstName,String mLastName,String mGender){ this(mFirstName,mLastName,mGender,0); } public User3(String mFirstName,String mLastName,String mGender,int mAge){ this(mFirstName,mLastName,mGender,mAge,""); } public User3(String mFirstName, String mLastName, String mGender,int mAge,String mPhoneNo) { this.mFirstName = mFirstName; this.mLastName = mLastName; this.mGender = mGender; this.mAge = mAge; this.mPhoneNo = mPhoneNo; }}
在方法一中,我們使用了多個重載的建構函式來實現初始化不同屬性值的需求,而可想而知,如果想要實現所有可能性的屬性需求,會產生大量重載版本的建構函式,使得代碼冗長且不好用。
方法二:
/** * 採用get/set * Created by DB on 2017/6/23. */public class User4 { private String mFirstName; private String mLastName; private String mGender; private int mAge; private String mPhoneNo; public String getmFirstName() { return mFirstName; } public void setmFirstName(String mFirstName) { this.mFirstName = mFirstName; } public String getmLastName() { return mLastName; } public void setmLastName(String mLastName) { this.mLastName = mLastName; } public String getmGender() { return mGender; } public void setmGender(String mGender) { this.mGender = mGender; } public int getmAge() { return mAge; } public void setmAge(int mAge) { this.mAge = mAge; } public String getmPhoneNo() { return mPhoneNo; } public void setmPhoneNo(String mPhoneNo) { this.mPhoneNo = mPhoneNo; }}
在方法2中,我們使用了get和set方法來完成對屬性值的設定和讀取,如果是這樣的話,就需要將屬性值的final關鍵字去掉,這也就意味著這個類的執行個體不再是不可變的,與原先的需求有所出入,並且這個類的執行個體狀態不聯絡,比如說如果你想建立一個同時具有幾個屬性值的類執行個體時,只有當最後那個執行個體屬性的set函數被調用時,該執行個體才具有完整聯絡的狀態,這意味著調用者可能會看到該類執行個體的不連續狀態。
方法3:
終於輪到了我們的Builder模式大展身手了,這裡我們使用的Builder模式中的變種模式,具體代碼如下:
/** * Created by DB on 2017/6/23. */public class User { private final String mFirstName; //必選 private final String mLastName; //必選 private final String mGender; //可選 private final int mAge; //可選 private final String mPhoneNo; //可選 //建構函式為私人的,調用者不能直接執行個體化該類,需要通過builder去實現執行個體化 private User(UserBuilder builder){ mFirstName=builder.firstName; mLastName=builder.lastName; mGender=builder.gender; mAge=builder.age; mPhoneNo=builder.phoneNo; } //僅提供get方法,而不提供set方法,確保User類的不可變性 public String getmFirstName() { return mFirstName; } public String getmLastName() { return mLastName; } public String getmGender() { return mGender; } public int getmAge() { return mAge; } public String getmPhoneNo() { return mPhoneNo; } //建構函式只接受必選的屬性值作為參數,並且只有必選的屬性值設定為final,以確保其在建構函式中設定 public static class UserBuilder { private final String firstName; private final String lastName; private String gender; private int age; private String phoneNo; public UserBuilder(String firstName,String lastName){ this.firstName=firstName; this.lastName=lastName; } public UserBuilder gender(String gender){ this.gender=gender; return this; } public UserBuilder age(int age){ this.age=age; return this; } public UserBuilder phoneNo(String phoneNo){ this.phoneNo=phoneNo; return this; } public User build(){ return new User(this); } } public static void main(String[] args){ User user = new UserBuilder("ZHB","Pignet") .gender("male") .age(25) .phoneNo("1234456") .build(); System.out.println(user.getmFirstName()+" "+user.getmLastName()); System.out.println(user.getmAge()+" "+user.getmGender()); }}
在這裡我們加入了一個測試案例,其結果如下所示:
從上述代碼可以看到我們熟悉的鏈式編程,這邊我們將必選的屬性值作為參數傳到Builder的建構函式中,並且為final,其他的屬性值則通過各自的方法實現設定,最後在build方法中建立一個類的執行個體對象,並返回。
三、Builder模式的優缺點
優點:將類執行個體的初始化實現分為構建和表示兩部分,同時,也將類執行個體的初始化從目標類中隔離出來(集中到了一個內部類中),避免了過度的Set方法,同時採用調用鏈式的實現,使得代碼更加簡潔明了。
缺點:需要編寫很多的樣板代碼,我們需要在內部類UserBuilder中重複外部類User的屬性定義
四、使用外掛程式自動產生代碼
為了規避掉Builder模式中的缺點,我們可以在Android Studio中(Intelligent IDEA)通過安裝名為InnerBuilder的外掛程式來簡化Builder模式的建立過程。
來看看它的產生效果
/** * Created by DB on 2017/6/23. */public class User2 { private final String mFirstName; private final String mLastName; private final String mGender; private final int mAge; private final String mPhoneNo; private User2(Builder builder) { mFirstName = builder.mFirstName; mLastName = builder.mLastName; mGender = builder.mGender; mAge = builder.mAge; mPhoneNo = builder.mPhoneNo; } public static final class Builder { private String mFirstName; private String mLastName; private String mGender; private int mAge; private String mPhoneNo; public Builder() { } public Builder mFirstName(String val) { mFirstName = val; return this; } public Builder mLastName(String val) { mLastName = val; return this; } public Builder mGender(String val) { mGender = val; return this; } public Builder mAge(int val) { mAge = val; return this; } public Builder mPhoneNo(String val) { mPhoneNo = val; return this; } public User2 build() { return new User2(this); } }}
對比前面的例子,我們不難發現這一段自動產生的程式碼和我們前面寫的代碼有一點點不同,我們可以在此基礎上根據實際的需求進行修改(如前面的必選項採用建構函式來初始化部分)
Builder模式詳解及其在Android開發中的應用