Android中的構建者(Builder)模式
最近在使用 Retrofit 和 OkHttp 框架的過程中發現創建相關對象時頻繁使用到了Builder模式,鏈式調用的方式讓代碼變得簡潔、易懂,但自己也只是知其然而不知其所以然,所以決定做個筆記加深下印象。
一、場景分析
在實際開發中,往往會遇到需要構建一個復雜的對象的代碼,像這樣的:
public class User {
private String name; // 必傳
private String cardID; // 必傳
private int age; // 可選
private String address; // 可選
}
于是我們起手就是擼了一串這樣的代碼,譬如:
通過構造函數的參數形式去寫一個實現類
User(String name);
User(String name, String cardID);
User(String name, String cardID,int age);
User(String name, String cardID,int age, String address);
又或者通過設置setter和getter方法的形式寫一個實現類
<!--more-->
public class User {
private String name; // 必傳
private String cardID; // 必傳
private int age; // 可選
private String address; // 可選
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
先說說這兩種方式的優劣:
第一種在參數不多的情況下,是比較方便快捷的,一旦參數多了,代碼可讀性大大降低,并且難以維護,對調用者來說也造成一定困惑;
第二種可讀性不錯,也易于維護,但是這樣子做對象會產生不一致的狀態,當你想要傳入全部參數的時候,你必需將所有的setXX方法調用完成之后才行。然而一部分的調用者看到了這個對象后,以為這個對象已經創建完畢,就直接使用了,其實User對象并沒有創建完成,另外,這個User對象也是可變的,不可變類所有好處都不復存在。
寫到這里真想為自己最近封裝的表單控件捏一把汗。。。所以有沒有更好地方式去實現它呢,那就是接下來要理解的Builder模式了。
二、定義
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的展示。
Builder模式屬于創建型,一步一步將一個復雜對象創建出來,允許用戶在不知道內部構建細節的情況下,可以更精細地控制對象的構造流程。
三、Builder模式變種-鏈式調用
代碼實現
public class User {
private final String name; //必選
private final String cardID; //必選
private final int age; //可選
private final String address; //可選
private final String phone; //可選
private User(UserBuilder userBuilder){
this.name=userBuilder.name;
this.cardID=userBuilder.cardID;
this.age=userBuilder.age;
this.address=userBuilder.address;
this.phone=userBuilder.phone;
}
public String getName() {
return name;
}
public String getCardID() {
return cardID;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public String getPhone() {
return phone;
}
public static class UserBuilder{
private final String name;
private final String cardID;
private int age;
private String address;
private String phone;
public UserBuilder(String name,String cardID){
this.name=name;
this.cardID=cardID;
}
public UserBuilder age(int age){
this.age=age;
return this;
}
public UserBuilder address(String address){
this.address=address;
return this;
}
public UserBuilder phone(String phone){
this.phone=phone;
return this;
}
public User build(){
return new User(this);
}
}
}
需要注意的點:
- User類的構造方法是私有的,調用者不能直接創建User對象。
- User類的屬性都是不可變的。所有的屬性都添加了final修飾符,并且在構造方法中設置了值。并且,對外只提供getters方法。
- Builder的內部類構造方法中只接收必傳的參數,并且該必傳的參數使用了final修飾符。
調用方式
new User.UserBuilder("Jack","10086")
.age(25)
.address("GuangZhou")
.phone("13800138000")
.build();
相比起前面通過構造函數和setter/getter方法兩種方式,可讀性更強。唯一可能存在的問題就是會產生多余的Builder對象,消耗內存。然而大多數情況下我們的Builder內部類使用的是靜態修飾的(static),所以這個問題也沒多大關系。
關于線程安全
Builder模式是非線程安全的,如果要在Builder內部類中檢查一個參數的合法性,必需要在對象創建完成之后再檢查,
正確示例:
public User build() {
User user = new user(this);
if (user.getAge() > 120) {
throw new IllegalStateException(“Age out of range”); // 線程安全
}
return user;
}
錯誤示例:
public User build() {
if (age > 120) {
throw new IllegalStateException(“Age out of range”); // 非線程安全
}
return new User(this);
}
四、經典Builder模式
UML類圖
來源自《Android源碼設計模式與解析實戰》
Product : 產品抽象類
Builder : 抽象Builder類,規范產品組建,一般是由子類實現具體的組建過程
ConcreteBuilder : 具體的Builder類
Director : 統一組裝過程
Product角色
/**
* 用戶抽象類
*/
public abstract class User {
protected String name;
protected String cardID;
protected int age;
protected String address;
public void setName(String name) {
this.name = name;
}
public abstract void setCardID();
public void setAge(int age) {
this.age = age;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [name ="+name+",cardID="+cardID+",age="+age+"," +
"address="+address+"]";
}
}
具體的Product類
/**
* 具體的Product角色 SysUser
*/
public class SysUser extends User {
public SysUser() {
}
@Override
public void setCardID() {
cardID="10086"; //設置默認ID
}
}
抽象Builder類
public abstract class Builder {
public abstract void buildName(String name);
public abstract void buildCardID();
public abstract void buildAge(int age);
public abstract void buildAddress(String address);
public abstract User create();
}
具體的Builder類
public class AccountBuilder extends Builder{
private User user=new SysUser();
@Override
public void buildName(String name) {
user.setName(name);
}
@Override
public void buildCardID() {
user.setCardID();
}
@Override
public void buildAge(int age) {
user.setAge(age);
}
@Override
public void buildAddress(String address) {
user.setAddress(address);
}
@Override
public User create() {
return user;
}
}
Director角色,負責構造User
public class Director {
Builder mBuilder =null;
public Director(Builder builder){
this.mBuilder =builder;
}
public void construct(String name,int age,String address){
mBuilder.buildName(name);
mBuilder.buildCardID();
mBuilder.buildAge(age);
mBuilder.buildAddress(address);
}
}
測試代碼
public class Test{
public static void main(String args){
//構建器
Builder builder=new AccountBuilder();
//Director
Director director=new Director(builder);
//封裝構建過程:Jack,10086,25,GuangZhou
director.construct("Jack",25,"GuangZhou");
//打印結果
System.out.println("Info :" +builder.create().toString());
}
}
輸出結果
System.out: Info :User [name =Jack,cardID=10086,age=25,address=GuangZhou]
六、用到Builder模式的例子
-
Android中的AlertDialog.Builder
private void showDialog(){
AlertDialog.Builder builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon);
builder.setTitle("Title");
builder.setMessage("Message");
builder.setPositiveButton("Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//TODO
}
});
builder.setNegativeButton("Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//TODO
}
});
builder.create().show();
}
-
OkHttp中OkHttpClient的創建
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(getCache())
.addInterceptor(new HttpCacheInterceptor())
.addInterceptor(new LogInterceptor())
.addNetworkInterceptor(new HttpRequestInterceptor())
.build();
-
Retrofit中Retrofit對象的創建
Retrofit retrofit = new Retrofit.Builder()
.client(createOkHttp())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
可見在實際使用中,均省略掉了Director角色,在很多框架源碼中,涉及到Builder模式時,大多都不是經典GOF的Builder模式,而是選擇了結構更加簡單的后者。
七、優缺點
優點:
- 良好的封裝性,使得客戶端不需要知道產品內部實現的細節
- 建造者獨立,擴展性強
缺點:
- 產生多余的Builder對象、Director對象,消耗內存
參考資料
來自:http://www.jianshu.com/p/0adc46f457be