通過(guò)示例和源碼闡述建造者模式

0. 序言

  • 建造者模式的定義:將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離殷勘,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示舷礼。

1. 介紹

  • 建造者模式是一步一步創(chuàng)建一個(gè)復(fù)雜對(duì)象的創(chuàng)建型模式赴邻,允許在不知道內(nèi)部構(gòu)建細(xì)節(jié)的情況下握恳,可以更精細(xì)地控制對(duì)象的構(gòu)造流程迟隅。也就是它注重的是構(gòu)建對(duì)象的構(gòu)成梭纹,而不是構(gòu)件對(duì)象過(guò)程中所需要的部件表示的細(xì)節(jié)躲惰。
  • 舉例:賈躍亭會(huì)計(jì)要造一臺(tái)FF,而一臺(tái)FF需要輪胎变抽、方向盤(pán)础拨、發(fā)動(dòng)機(jī)等部件。而賈躍亭關(guān)心的是如何組裝這些零件為一輛車绍载,而不是關(guān)心這個(gè)輪胎怎么造的诡宗,這個(gè)方向盤(pán)怎么造的。
  • 優(yōu)點(diǎn)就是構(gòu)建過(guò)程和部件可以自由擴(kuò)展击儡,兩者之間的耦合降到最低僚焦。

2. 場(chǎng)景

  • 相同的方法,不同的執(zhí)行順序曙痘,產(chǎn)生不同的結(jié)果芳悲。
    舉例:街舞大家都看過(guò),有時(shí)候是動(dòng)手边坤、動(dòng)腳名扛、轉(zhuǎn)身,有時(shí)候是動(dòng)腳茧痒、轉(zhuǎn)身肮韧、動(dòng)手等等,不同的行動(dòng)方式旺订,呈現(xiàn)不同的美弄企。
  • 初始化一個(gè)對(duì)象特別復(fù)雜,如參數(shù)多区拳。
    舉例:初始化一個(gè)AlertDialog或者一個(gè)notification拘领,有的需要標(biāo)題、內(nèi)容兩項(xiàng)樱调,有的需要標(biāo)題约素、內(nèi)容届良、進(jìn)度條三項(xiàng)等等,這個(gè)時(shí)候如果通過(guò)構(gòu)造方法初始化參數(shù)的話圣猎,會(huì)有很多種搭配組合士葫,就要寫(xiě)很多構(gòu)造方法,這個(gè)時(shí)候我們就可以使用建造者模式送悔,需要哪個(gè)參數(shù)我們就拿每個(gè)參數(shù)對(duì)應(yīng)的方法就行慢显。

3. UML類圖(用PC觀看)

建造者模式類圖.png

角色介紹:
① Product產(chǎn)品類——產(chǎn)品的抽象類。
② Builder——抽象Builder類欠啤,規(guī)范產(chǎn)品的組件鳍怨。
③ ConcreteBuilder——具體的Builder類,實(shí)現(xiàn)具體的組件過(guò)程跪妥。
④ Director——統(tǒng)一組裝過(guò)程

4. 場(chǎng)景一實(shí)現(xiàn)示例

以跳舞為例:

  • 定義Dance抽象類,即Product角色:
public abstract class Dance {
    protected abstract void hand();
    protected abstract void foot();
    protected abstract void turn_around();
}
  • 定義Hiphop類声滥,即具體的Dance類:
public class Hiphop extends Dance {

    private List<String> mPerformList = new ArrayList<>();

    @Override
    protected void hand() {
        mPerformList.add("街舞:動(dòng)動(dòng)手");
    }

    @Override
    protected void foot() {
        mPerformList.add("街舞:動(dòng)動(dòng)腳");
    }

    @Override
    protected void turn_around() {
        mPerformList.add("街舞:轉(zhuǎn)身");
    }

    public List<String> getPerformList() {
        return mPerformList;
    }
}

說(shuō)明:為了能看出不同的跳舞順序眉撵,我們?cè)黾恿艘粋€(gè)List集合和訪問(wèn)List集合的方法。

  • 定義抽象Builder類落塑,即構(gòu)建對(duì)象類:
public abstract class Builder {
    public abstract void hand();
    public abstract void foot();
    public abstract void turn_around();
    public abstract Dance create();
}
  • 定義構(gòu)建對(duì)象的具體實(shí)例類DanceBuilder:
public class DanceBuilder extends Builder {

    Dance mDance = new Hiphop();

    @Override
    public void hand() {
        mDance.hand();
    }

    @Override
    public void foot() {
        mDance.foot();
    }

    @Override
    public void turn_around() {
        mDance.turn_around();
    }

    @Override
    public Dance create() {
        return mDance;
    }
}
  • 定義導(dǎo)演類纽疟,負(fù)責(zé)組裝過(guò)程:
public class Director {
    Builder mBuilder = null;

    public Director(Builder builder) {
        mBuilder = builder;
    }

    // 可以定義不同的順序
    public void perform(){
        mBuilder.hand();
        mBuilder.foot();
        mBuilder.turn_around();
    }
}
  • 進(jìn)行測(cè)試:
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Builder builder = new DanceBuilder();
        Director director = new Director(builder);
        director.perform();
        Log.i("fukq","跳舞的順序是:"+((Hiphop)(builder.create())).getPerformList());
    }
}
12-05 16:18:52.241 30387-30387/com.smartisan.builder I/fukq: 跳舞的順序是:[街舞:動(dòng)動(dòng)手, 街舞:動(dòng)動(dòng)腳, 街舞:轉(zhuǎn)身]

說(shuō)明:
① 通過(guò)DanceBuilder來(lái)構(gòu)建Dance對(duì)象,而Director封裝了構(gòu)建復(fù)雜產(chǎn)品對(duì)象的過(guò)程憾赁,而DanceBuilder中有跳舞的不同的順序污朽,展示了場(chǎng)景一:相同的方法,不同的順序龙考,帶來(lái)不同的結(jié)果蟆肆。
② 開(kāi)發(fā)中,Director會(huì)被省略晦款,直接使用Builder來(lái)進(jìn)行對(duì)象的組裝炎功,我們?nèi)サ鬌irector類,修改下DanceBuilder類:

public abstract class Builder {
    public abstract Dance setHand();
    public abstract Dance setFoot();
    public abstract Dance setTurn_around();
}
public class DanceBuilder extends Builder {

    Dance mDance = new Hiphop();

    @Override
    public Dance setHand() {
        mDance.hand();
        return mDance;
    }

    @Override
    public Dance setFoot() {
        mDance.foot();
        return mDance;
    }

    @Override
    public Dance setTurn_around() {
        mDance.turn_around();
        return mDance;
    }
}

說(shuō)明:建造者模式通常用Builder進(jìn)行鏈?zhǔn)秸{(diào)用(為了呈現(xiàn)鏈?zhǔn)秸{(diào)用這里修改下方法名)缓溅,它的關(guān)鍵點(diǎn)在于每個(gè)setter方法都返回自身蛇损,代碼我們可以這樣寫(xiě):

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Dance dance = new DanceBuilder().setTurn_around().setFoot().setHand().create();
        Log.i("fukq","跳舞的順序是:"+ ((Hiphop)dance).getPerformList());
    }
}

說(shuō)明:通過(guò)鏈?zhǔn)秸{(diào)用,隨意對(duì)順序進(jìn)行編輯坛怪,得到復(fù)雜對(duì)象Dance淤齐。

5. 場(chǎng)景二實(shí)現(xiàn)示例:

以AlertDialog為例:

           new AlertDialog.Builder(this)
                .setIcon(R.mipmap.ic_launcher)
                .setTitle("Title")
                .setMessage("Messages")
                .setPositiveButton("Button01",new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                }).setNegativeButton("Button02",new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                }).setNeutralButton("Button03", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                }).create().show();

說(shuō)明:通過(guò)類名Builder以及鏈?zhǔn)秸{(diào)用我們可以看出來(lái)它是一個(gè)建造者模式,通過(guò)Builder來(lái)組裝Dialog的各個(gè)部分袜匿,我們分析下源碼更啄,再次驗(yàn)證下:

public class AlertDialog extends AppCompatDialog implements DialogInterface {
    final AlertController mAlert;  // 1
    ...
    protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }

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

    protected AlertDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
        this(context, 0);
        this.setCancelable(cancelable);
        this.setOnCancelListener(cancelListener);
    }
    ...
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        this.mAlert.setTitle(title);
    }

    public void setCustomTitle(View customTitleView) {
        this.mAlert.setCustomTitle(customTitleView);
    }

    public void setMessage(CharSequence message) {
        this.mAlert.setMessage(message);
    }
   ...
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.mAlert.installContent();
    }
   ...
    public static class Builder { // 3
        private final AlertParams P;
        private final int mTheme;

        public Builder(@NonNull Context context) {
            this(context, AlertDialog.resolveDialogTheme(context, 0));
        }

        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            this.P = new AlertParams(new ContextThemeWrapper(context, AlertDialog.resolveDialogTheme(context, themeResId)));
            this.mTheme = themeResId;
        }

        @NonNull
        public Context getContext() {
            return this.P.mContext;
        }

        public AlertDialog.Builder setTitle(@StringRes int titleId) {
            this.P.mTitle = this.P.mContext.getText(titleId);
            return this;
        }

        public AlertDialog.Builder setTitle(@Nullable CharSequence title) {
            this.P.mTitle = title;
            return this;
        }

        public AlertDialog.Builder setCustomTitle(@Nullable View customTitleView) {
            this.P.mCustomTitleView = customTitleView;
            return this;
        }

        public AlertDialog.Builder setMessage(@StringRes int messageId) {
            this.P.mMessage = this.P.mContext.getText(messageId);
            return this;
        }

        public AlertDialog.Builder setMessage(@Nullable CharSequence message) {
            this.P.mMessage = message;
            return this;
        }

       ...
        public AlertDialog.Builder setView(View view) {
            this.P.mView = view;
            this.P.mViewLayoutResId = 0;
            this.P.mViewSpacingSpecified = false;
            return this;
        }
        ...
        public AlertDialog create() { // 4
            AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);
            this.P.apply(dialog.mAlert);
            dialog.setCancelable(this.P.mCancelable);
            if (this.P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }

            dialog.setOnCancelListener(this.P.mOnCancelListener);
            dialog.setOnDismissListener(this.P.mOnDismissListener);
            if (this.P.mOnKeyListener != null) {
                dialog.setOnKeyListener(this.P.mOnKeyListener);
            }

            return dialog;
        }

        public AlertDialog show() {
            AlertDialog dialog = this.create();
            dialog.show();
            return dialog;
        }
    }
}

說(shuō)明:
① 省略號(hào)的地方省略一些代碼。
② 整個(gè)的AlertDialog大分為構(gòu)造方法居灯、set方法和內(nèi)部類Builder锈死。
③ 分析源碼的順序是:從調(diào)用開(kāi)始的地方贫堰,查看涉及到的所有代碼的源碼,再來(lái)看下調(diào)用的代碼:

 new AlertDialog.Builder(this)
                .setTitle("Title")
                ...
                .create().show();

所以這里查看源碼的順序是:先看AlertDialog的內(nèi)部類Builder待牵,然后再看setTitle方法其屏,再看create方法,最后看show方法缨该。
④ .Builder(this) 的源碼:

        public Builder(@NonNull Context context) {
            this(context, AlertDialog.resolveDialogTheme(context, 0));
        }

說(shuō)明:這里只是傳入了上下文this偎行。
⑤ .setTitle("Title")的源碼:

private final AlertParams P;
        public AlertDialog.Builder setTitle(@Nullable CharSequence title) {
            this.P.mTitle = title;
            return this;
        }

說(shuō)明:給Title賦值,職責(zé)賦值給了AlertParams P中的title贰拿。
⑥ .create()的源碼:

        public AlertDialog create() {
            AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);
            this.P.apply(dialog.mAlert);
            dialog.setCancelable(this.P.mCancelable);
            if (this.P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }

            dialog.setOnCancelListener(this.P.mOnCancelListener);
            dialog.setOnDismissListener(this.P.mOnDismissListener);
            if (this.P.mOnKeyListener != null) {
                dialog.setOnKeyListener(this.P.mOnKeyListener);
            }

            return dialog;
        }

說(shuō)明:這些代碼里面蛤袒,最可能和title相關(guān)的只剩下 this.P.apply(dialog.mAlert); 這一句,所以我們看下這個(gè)apply方法的源碼:

 public void apply(AlertController dialog) {
            if (this.mCustomTitleView != null) {
                dialog.setCustomTitle(this.mCustomTitleView);
            } else {
                if (this.mTitle != null) {
                    dialog.setTitle(this.mTitle);
                }
                ...
            }
            ...
        }

說(shuō)明:apply這個(gè)方法是在AlertParams類中膨更,dialog.mAlert 指的就是AlertController妙真,所以apply的作用就是把AlertParams P中的字段值都賦值給AlertController的字段值。
⑦ show()方法從源碼來(lái)看都是關(guān)于如何把Dialog顯示在屏幕上的內(nèi)容荚守,不再附代碼珍德,不再分析。
⑧ 通過(guò)以上分析我們可以看到矗漾,當(dāng)需要多個(gè)參數(shù)的時(shí)候锈候,可以通過(guò)builder模式來(lái)傳遞需要的參數(shù),而且兼容多種參數(shù)配置敞贡,就像kotlin中的默認(rèn)參數(shù)所帶來(lái)的便利一樣泵琳。

6. 后續(xù)

如果大家喜歡這篇文章,歡迎點(diǎn)贊誊役!
如果想看更多 設(shè)計(jì)模式 方面的技術(shù)获列,歡迎關(guān)注!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛔垢,一起剝皮案震驚了整個(gè)濱河市蛛倦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啦桌,老刑警劉巖溯壶,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異甫男,居然都是意外死亡且改,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)板驳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)又跛,“玉大人,你說(shuō)我怎么就攤上這事若治】叮” “怎么了感混?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)礼烈。 經(jīng)常有香客問(wèn)我弧满,道長(zhǎng),這世上最難降的妖魔是什么此熬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任庭呜,我火速辦了婚禮,結(jié)果婚禮上犀忱,老公的妹妹穿的比我還像新娘募谎。我一直安慰自己,他們只是感情好阴汇,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布数冬。 她就那樣靜靜地躺著,像睡著了一般搀庶。 火紅的嫁衣襯著肌膚如雪拐纱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天地来,我揣著相機(jī)與錄音,去河邊找鬼熙掺。 笑死未斑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的币绩。 我是一名探鬼主播蜡秽,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缆镣!你這毒婦竟也來(lái)了芽突?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤董瞻,失蹤者是張志新(化名)和其女友劉穎寞蚌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钠糊,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挟秤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抄伍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艘刚。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖截珍,靈堂內(nèi)的尸體忽然破棺而出攀甚,到底是詐尸還是另有隱情箩朴,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布秋度,位于F島的核電站炸庞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏静陈。R本人自食惡果不足惜燕雁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲸拥。 院中可真熱鬧拐格,春花似錦、人聲如沸刑赶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撞叨。三九已至金踪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牵敷,已是汗流浹背胡岔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枷餐,地道東北人靶瘸。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像毛肋,于是被迫代替她去往敵國(guó)和親怨咪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容