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