建造器模式(builder)

帥氣的 Builder 鏈式調用?
1.1普通對象的創建
再說正題之前。先看一下
在日常開發中,經常可以看到這樣的代碼:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

以及

new AlertDialog.Builder(this)
            .setTitle("hello")
            .setMessage("張三")
            .setIcon(R.drawable.bg_search_corner)
            .setCancelable(true)
            .setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    //...
                }
            })
            .show();

看到這樣鏈式調用看起來好整齊啊,這既是Builder 模式,今天就來詳細瞭解一下。
鏡頭切回來,先問大家普通對象的創建 有幾種?
常見的有兩種方式:重疊構造器,javabeans模式

1.1.1重疊構造器

在我們開發中我們可能經常寫這種代碼

 Person person = new Person("jiangnan","25","school","sh");

這種方式創建對象在屬性不是很多的時候沒感覺有什麼彆扭,假如現在隨着業務的擴大,屬性增加到幾十個, 那我們每次創建對象的時候是不是有頭重腳輕的感覺呢

 Person person = new Person("jiangnan","25","school","sh","","","","","","","","","","","");
public class Person {
    private String name;
    private String age;
    private String school;
    private String adress;

    public Person(String name, String age, String school, String adress) {
        this.name = name;
        this.age = age;
        this.school = school;
        this.adress = adress;
    }
}

1.1.2javabeans模式

       遇到多種構造器參數的時候,我們還有第二個替代方法,即javaBeans模式,調用一個無參的構造器創建對象,然後調用setter()方法來設置每一個必要的參數,以及每一個可選的參數。

public class Person {
    private final String name;
    private String age;
    private String school;
    private String adress;

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    public String getAdress() {
        return adress;
    }

    public void setAdress(String adress) {
        this.adress = adress;
    }
}

這種方式彌補了上面重疊構造器模式的不足,創建對象也很容易,讀起來也很簡單

 Person person = new Person();
        person.setName("jiangnan");
        person.setAge("25");
        person.setSchool("school");
        person.setAdress("sh");

但javabeans模式還有缺點

1:不優雅,

2:拿到的不是完整的對象

1.2builder鏈式調用的好處

幸運的是我們第三者替代方法,既能保證重疊構造器模式的安全性,還能保證javabeans模式那麼好的可讀性,這就是builder模式,不直接創建對象,而是先利用所有必要的參數調用構造器,得到一個builder對象,然後我們調用builder的類似於sertter方法來設置每一個可選的參數,最後,我們用戶可以調用無參的build方法生成一個不可變的對象,這個builder是他構造的類的靜態成員類。

public class Person {
    private String name;
    private String age;
    private String school;
    private String adress;

    /**
     * 構造參數是它的內部靜態類,爲了對靜態內部類的變量進行賦值操作
     * @param builder
     */
    public Person(Builder builder) {
        name=builder.mName;
        age=builder.mAge;
        school=builder.mSchool;
        adress=builder.mAdress;
    }

    public static class Builder{
        private final String mName; //必選參數 final 類型需要在 構造器中初始化,不允許不初始化它的構造器存在
        private String mAge;
        private String mSchool;
        private String mAdress;

        public Builder(String name){
            mName=name;
        }

        public Builder setAge(String age) {
            mAge = age;
            return this;
        }

        public Builder setSchool(String school) {
            mSchool = school;
            return this;
        }

        public Builder setAdress(String adress) {
            mAdress = adress;
            return this;
        }

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

    }

2應用的場景

譬如我們彈出對話框就是用到了builder設計模式

new AlertDialog.Builder(this).setMessage("加載對話框").setTitle("");

看goodle工程師對AlertDialog的源碼你會發現構造是這樣的

public class AlertDialog extends Dialog implements DialogInterface {

protected AlertDialog(Context context) {
        this(context, 0);
    }

protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
        this(context, 0);

        setCancelable(cancelable);
        setOnCancelListener(cancelListener);
    }
 public static class Builder {
        private final AlertController.AlertParams P;

        /**
         * Creates a builder for an alert dialog that uses the default alert
         * dialog theme.
         * <p>
         * The default alert dialog theme is defined by
         * {@link android.R.attr#alertDialogTheme} within the parent
         * {@code context}'s theme.
         *
         * @param context the parent context
         */
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
 public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }

        /**
         * Set the title displayed in the {@link Dialog}.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

        /**
         * Set the title using the custom view {@code customTitleView}.
         * <p>
         * The methods {@link #setTitle(int)} and {@link #setIcon(int)} should
         * be sufficient for most titles, but this is provided if the title
         * needs more customization. Using this will replace the title and icon
         * set via the other methods.
         * <p>
         * <strong>Note:</strong> To ensure consistent styling, the custom view
         * should be inflated or constructed using the alert dialog's themed
         * context obtained via {@link #getContext()}.
         *
         * @param customTitleView the custom view to use as the title
         * @return this Builder object to allow for chaining of calls to set
         *         methods
         */
        public Builder setCustomTitle(View customTitleView) {
            P.mCustomTitleView = customTitleView;
            return this;
        }

        /**
         * Set the message to display using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMessage(@StringRes int messageId) {
            P.mMessage = P.mContext.getText(messageId);
            return this;
        }

        /**
         * Set the message to display.
          *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this;
        }

        /**
         * Set the resource id of the {@link Drawable} to be used in the title.
         * <p>
         * Takes precedence over values set using {@link #setIcon(Drawable)}.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setIcon(@DrawableRes int iconId) {
            P.mIconId = iconId;
            return this;
        }

        /**
         * Set the {@link Drawable} to be used in the title.
         * <p>
         * <strong>Note:</strong> To ensure consistent styling, the drawable
         * should be inflated or constructed using the alert dialog's themed
         * context obtained via {@link #getContext()}.
         *
         * @return this Builder object to allow for chaining of calls to set
         *         methods
         */
        public Builder setIcon(Drawable icon) {
            P.mIcon = icon;
            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;
        }
}

這麼長的代碼如果我們自己寫的話是不是瘋了,慶幸有大牛幫我們開發出了插件,可以直接用
Android Studio 中使用插件自動生成 變種 Builder 模式代碼
在Plugins中搜索Inner Builder
這裏寫圖片描述

然後Generate
這裏寫圖片描述

這裏寫圖片描述

然後生成

public class User {
    private final String name;
    private String age;
    private String school;
    private String adress;

    private User(Builder builder) {
        name = builder.name;
        age = builder.age;
        school = builder.school;
        adress = builder.adress;
    }

    public static final class Builder {
        private final String name;
        private String age;
        private String school;
        private String adress;

        public Builder(String name) {
            this.name = name;
        }

        public Builder age(String val) {
            age = val;
            return this;
        }

        public Builder school(String val) {
            school = val;
            return this;
        }

        public Builder adress(String val) {
            adress = val;
            return this;
        }

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

3感悟

       我們後輩在學習過程中切勿想當然覺得這些都很簡單,都是先人在實際開發中遇到的坑,不斷總結被整理成設計模式,學習的路還很長,沉澱自己,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章