Android中的構建者(Builder)模式

步行走天下 8年前發布 | 16K 次閱讀 Retrofit Android開發 移動開發

最近在使用 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

 

 本文由用戶 步行走天下 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!