設計模式之二: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圖(摘自網絡)
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.關於其他更多內容
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 領英:https://www.linkedin.com/in/chong-yang-049216146/
- 簡書:https://www.jianshu.com/u/b7b2c6ed9284
- csdn:https://my.csdn.net/m0_37700275
- 網易博客:https://yangchong211.blog.163.com/
- 新浪博客:https://blog.sina.com.cn/786041010yc
- 喜馬拉雅聽書:https://www.ximalaya.com/zhubo/71989305/
- 脈脈:yc930211
- 360圖書館:https://www.360doc.com/myfiles.aspx
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:https://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿裏雲博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV
- 博客園:https://www.cnblogs.com/yc211/
最後更新:2017-10-20 19:33:24
上一篇:
開源大數據周刊-第69期
下一篇:
網站備案資料怎麼填寫?有哪些技巧和注意事項?
在電腦和安卓設備之間使用 FTP 傳輸文件
Check Point 強勢攜手 LG,助力保護智能家用設備
PostgreSQL DISTINCT 和 DISTINCT ON 語法的使用
商之翼新聞|電信網絡詐騙案持續高發,老年群體成騙子重點目標
從一個案例看mysqldump的複製選項
docker私有庫搭建過程(Registry)
數據過濾器使用法則
雲棲大會直播:阿裏巴巴成立全球研究院“達摩院” 計劃三年內投資超1000億元
解決The type or namespace name \'XXXX\' does not exist in the namespace \'XXXXXXXXX\' 的錯誤
centos下yum安裝Redis