閱讀677 返回首頁    go 阿裏雲 go 技術社區[雲棲]


設計模式之二:Builder模式

設計模式之二:Builder模式
目錄介紹

  • 0.關於Builder模式案例下載
  • 1.Builder模式介紹
  • 2.Builder模式使用場景
  • 3.Builder模式簡單案例
  • 3.1 Builder模式UML圖(摘自網絡)
  • 3.2 在《Android源碼設計模式》這本書上,介紹經典Builder模式中,包括
  • 3.3 Product角色
  • 3.4 Builder : 抽象Builder類,規範產品組建,一般是由子類實現具體的組建過程
  • 3.5 ConcreteBuilder : 具體的Builder類
  • 3.6 Director : 統一組裝過程
  • 4.Builder模式實際案例Demo
  • 4.1 實際開發中,有時會遇到複雜的對象的代碼
  • 4.2 通過構造函數的參數形式去寫一個實現類,或者通過set,get去實現
  • 4.3 分析
  • 4.4 將上麵例子改成builder模式如下所示
  • 4.5 最後運用,代碼如下
  • 4.6 關於線程安全問題
  • 5.Android源碼中的Builder模式實現
  • 5.1 首先看看AlertDialog.Builder源代碼,隻是摘自部分代碼
  • 5.2 接下來看看build源代碼
  • 5.3 接下來看看show的代碼
  • 5.4 為什麼AlertDialog要使用builder模式呢?
  • 6.Builder模式總結
  • 6.1 builder模式優點
  • 6.2 builder模式缺點
  • 7.關於其他說明
  • 7.1 參考案例
  • 8.關於其他更多內容

0.本人寫的綜合案例

  • 案例
  • 說明及截圖
  • 模塊:新聞,音樂,視頻,圖片,唐詩宋詞,快遞,天氣,記事本,閱讀器等等
  • 接口:七牛,阿裏雲,天行,幹貨集中營,極速數據,追書神器等等
  • #####關於Builder設計模式,具體實際案例可以參考:https://github.com/yangchong211/YCDialog

1.Builder模式介紹

1.1 定義: 將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的展示。
1.2 Builder模式屬於創建型,一步一步將一個複雜對象創建出來,允許用戶在不知道內部構建細節的情況下,可以更精細地控製對象的構造流程。

2.Builder模式使用場景

相同方法,不同執行順序,產生不同事件結果
初始化對象特別複雜,參數眾多,且很多參數都具有默認值時

3.Builder模式簡單案例

3.1 Builder模式UML圖(摘自網絡)

  • Image.png

3.2 在《Android源碼設計模式》這本書上,介紹經典Builder模式中,包括

  • Product : 產品抽象類
  • Builder : 抽象Builder類,規範產品組建,一般是由子類實現具體的組建過程
  • ConcreteBuilder : 具體的Builder類
  • Director : 統一組裝過程

3.3 Product角色

//設置抽象類
public abstract class BuilderUser {··
    protected String name;
    protected String cardID;
    protected int age;
    protected String address;
    public BuilderUser() {}
    //設置名字
    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 "BuilderUser{" +
                "name='" + name + ''' +
                ", cardID='" + cardID + ''' +
                ", age=" + age +
                ", address='" + address + ''' +
                '}';
    }
}
/**
 * 具體的Product角色 UserCard
 */
public class UserCard extends BuilderUser {

    public UserCard() {}

    @Override
    public void setCardID() {
        cardID="10086";             //設置默認ID
    }
}

3.4 Builder : 抽象Builder類,規範產品組建,一般是由子類實現具體的組建過程

/**
 * 抽象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 BuilderUser create();
}

3.5 ConcreteBuilder : 具體的Builder類

/**
* 具體的Builder類
*/
public class AccountBuilder extends Builder{

    private BuilderUser user = new UserCard();

    @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 BuilderUser create() {
        return user;
    }
}

3.6 Director : 統一組裝過程

/**
* 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);
    }
}

4.Builder模式實際案例Demo

4.1 實際開發中,有時會遇到複雜的對象的代碼,比如

public class BuilderDemo {
    private final String name;         //必選
    private final String cardID;       //必選
    private final int age;             //可選
    private final String address;      //可選
    private final String phone;        //可選
}

4.2 通過構造函數的參數形式去寫一個實現類,或者通過set,get去實現

BuilderDemo(String name);
BuilderDemo(String name, String cardID);
BuilderDemo(String name, String cardID,int age);
BuilderDemo(String name, String cardID,int age, String address);
BuilderDemo(String name, String cardID,int age, String address,String phone);

4.3 分析

  • 第一種在參數不多的情況下,是比較方便快捷的,一旦參數多了,代碼可讀性大大降低,並且難以維護,對調用者來說也造成一定困惑;
  • 第二種可讀性不錯,也易於維護,但是這樣子做對象會產生不一致的狀態,當你想要傳入全部參數的時候,你必需將所有的setXX方法調用完成之後才行。然而一部分的調用者看到了這個對象後,以為這個對象已經創建完畢,就直接使用了,其實User對象並沒有創建完成,另外,這個User對象也是可變的,不可變類所有好處都不複存在。
  • Builder模式同時滿足上麵兩種情況,易於維護,並且創建方便

4.4 將上麵例子改成builder模式如下所示

public class BuilderDemo {

    private final String name;         //必選
    private final String cardID;       //必選
    private final int age;             //可選
    private final String address;      //可選
    private final String phone;        //可選

    public BuilderDemo(UserBuilder userBuilder){
        this.name=userBuilder.name;
        this.cardID=userBuilder.cardID;
        this.age=userBuilder.age;
        this.address=userBuilder.address;
        this.phone=userBuilder.phone;
    }

    public static class UserBuilder{
        private final String name;
        private final String cardID;
        private int age;
        private String address;
        private String phone;

        public BuilderDemo build(){
            return new BuilderDemo(this);
        }

        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;
        }
    }
}

4.5 最後運用,代碼如下

new BuilderDemo.UserBuilder("yc","10086")
        .age(24)
        .address("beijing")
        .phone("13667225184")
        .build();

4.6 關於線程安全問題

// 線程安全
public BuilderDemo build(){
    BuilderDemo builderDemo = new BuilderDemo(this);
    if(builderDemo.age > 100){
        throw new IllegalStateException("Age out of range");
    }
    return builderDemo;
}

// 線程不安全,不要這樣寫
public BuilderDemo build(){
    if(age > 100){
        throw new IllegalStateException("Age out of range");
    }
    return new BuilderDemo(this);
}

5.Android源碼中的Builder模式實現

5.1 首先看看AlertDialog.Builder源代碼,隻是摘自部分代碼

public class AlertDialog extends AppCompatDialog implements DialogInterface {

    //接收builder成員變量p中各個參數
    final AlertController mAlert;

    //下麵是三個構造函數
    protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }

    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
    }

    protected AlertDialog(@NonNull Context context, boolean cancelable,
            @Nullable OnCancelListener cancelListener) {
        this(context, 0);
        setCancelable(cancelable);
        setOnCancelListener(cancelListener);
    }

    //事件上這裏調用了是mAlert的setView方法
    public void setView(View view) {
        mAlert.setView(view);
    }

    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
            int viewSpacingBottom) {
        mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
    }

    public static class Builder {
        //這個裏麵存放很多參數
        private final AlertController.AlertParams P;

        //這個是new AlertDialog.Builder(context,R.style.AppTheme)調用的方法
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }

        //這部分代碼省略很多,主要是set設置各種參數
        public Builder setTitle(@Nullable CharSequence title) {
            P.mTitle = title;
            return this;
        }

              //這部分代碼省略很多,主要是set設置各種參數
        public Builder setView(int layoutResId) {
            P.mView = null;
            P.mViewLayoutResId = layoutResId;
            P.mViewSpacingSpecified = false;
            return this;
        }

        //構建dialog,傳遞參數
        public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            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;
        }

        //展示dialog
        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}

5.2 接下來看看build源代碼

  • 其中P是一個保存設置AlterDialog的參數類AlertParams的對象。很顯然,Builder隻是將AlterDialog的參數存放在一個參數對象裏,做一個暫時的保存。在最後調用create方法的時候,創建一個AlterDialog對象,將暫存的參數P一次性應用到這個Dialog對象上。完成Dialog的創建。
    當然,這隻是創建AlertDialog,還沒show出來。可以直接調用剛創建的AlertDialog的show()方法(實際為調用父類Dialog的show()),也可以在構建完Builder之後直接調用Builder的show方法

    public AlertDialog create() {
    //創建
    final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
    //這一步很重要,下麵會單獨分析
    P.apply(dialog.mAlert);
    //設置是否可以取消,默認是true
    dialog.setCancelable(P.mCancelable);
    if (P.mCancelable) {
        dialog.setCanceledOnTouchOutside(true);
    }
    //設置取消監聽
    dialog.setOnCancelListener(P.mOnCancelListener);
    //設置dialog關閉後監聽
    dialog.setOnDismissListener(P.mOnDismissListener);
    //設置返回鍵監聽
    if (P.mOnKeyListener != null) {
        dialog.setOnKeyListener(P.mOnKeyListener);
    }
    //返回對象,創建成功
    return dialog;
    }
    

    5.3 接下來看看show的代碼

    public void show() {
    //如果彈窗已經show出來了 
    if (mShowing) {
        //如果頂級view存在則設置窗口window,並顯示頂級View 
        if (mDecor != null) {
            if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
            }
            mDecor.setVisibility(View.VISIBLE);
        }
        return;
    }
    
    mCanceled = false;
    //如果窗口還未被創建 
    if (!mCreated) {
        dispatchOnCreate(null);
    }
    //獲得Windowd的頂級View,並將其添加到對應的WindowManager中,然後發送show的消息 
    onStart();
    //獲取DecorView
    mDecor = mWindow.getDecorView();
    
    if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
        final ApplicationInfo info = mContext.getApplicationInfo();
        mWindow.setDefaultIcon(info.icon);
        mWindow.setDefaultLogo(info.logo);
        mActionBar = new WindowDecorActionBar(this);
    }
    //獲取布局參數
    WindowManager.LayoutParams l = mWindow.getAttributes();
    if ((l.softInputMode
            & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
        WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
        nl.copyFrom(l);
        nl.softInputMode |=
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        l = nl;
    }
    
    try {
        //將mDecor添加到WindowManager中
        mWindowManager.addView(mDecor, l);
        mShowing = true;
        //發送一個顯示Dialog消息
        sendShowMessage();
    } finally {
    }
    }
    
  • 在show函數中主要做了如下的事情:
    (1)通過dispatchOnCreate函數來調用AlertDialog的onCreate函數
    (2)然後接著調用onStart函數
    (3)最後將Dialog的mDecor添加到WindowManager中(有點不明白這裏,為什麼要獲取到最頂部的布局)

5.4 為什麼AlertDialog要使用builder模式呢?

  • AlterDialog有很多的參數,如我們上麵列舉的title,message,三個button及其回調,除此以外還有icon,View等屬性。如果Android不采用Builder,而采用普通的構造函數來設計AlterDialog,可能要寫出很多類似於如下的構造函數: AlertDialog(String title); AlertDialog(String message) AlertDialog(int resId) AlertDialog(int resId, String title, String message); AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener); AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener); AlterDialog(int resId, String title, String message, String NegativeButtonString, OnClickListener listener); AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener, String NegativeButtonString, OnClickListener listener); ....

6.Builder模式總結

6.1 Builder模式有幾個好處:
1. Builder的setter函數可以包含安全檢查,可以確保構建過程的安全,有效。
2. Builder的setter函數是具名函數,有意義,相對於構造函數的一長串函數列表,更容易閱讀維護。
3. 可以利用單個Builder對象構建多個對象,Builder的參數可以在創建對象的時候利用setter函數進行調整

6.2 當然Builder模式也有缺點:
1. 更多的代碼,需要Builder這樣的內部類
2. 增加了類創建的運行時開銷 ,但是當一個類參數很多的時候,Builder模式帶來的好處要遠勝於其缺點。

7.關於其他說明

7.1 參考案例

  • 參考《Android源碼設計解析與實戰 》

8.關於其他更多內容

最後更新:2017-10-20 19:33:24

  上一篇:go  開源大數據周刊-第69期
  下一篇:go  網站備案資料怎麼填寫?有哪些技巧和注意事項?