Android中的構(gòu)建者(Builder)模式

目錄

一冯吓、場(chǎng)景分析

二、定義

三令漂、Builder模式變種-鏈?zhǔn)秸{(diào)用

四膝昆、經(jīng)典Builder模式

五丸边、用到Builder模式的例子

六、優(yōu)缺點(diǎn)

參考資料

最近在使用RetrofitOkHttp框架的過(guò)程中發(fā)現(xiàn)創(chuàng)建相關(guān)對(duì)象時(shí)頻繁使用到了Builder模式荚孵,鏈?zhǔn)秸{(diào)用的方式讓代碼變得簡(jiǎn)潔妹窖、易懂,但自己也只是知其然而不知其所以然收叶,所以決定做個(gè)筆記加深下印象骄呼。

一、場(chǎng)景分析

在實(shí)際開(kāi)發(fā)中判没,往往會(huì)遇到需要構(gòu)建一個(gè)復(fù)雜的對(duì)象的代碼蜓萄,像這樣的:


public class User {

    private String name;            // 必傳
    private String cardID;          // 必傳
    private int age;                // 可選
    private String address;         // 可選
}

于是我們起手就是擼了一串這樣的代碼,譬如:

通過(guò)構(gòu)造函數(shù)的參數(shù)形式去寫(xiě)一個(gè)實(shí)現(xiàn)類(lèi)


User(String name);

User(String name, String cardID);

User(String name, String cardID,int age);

User(String name, String cardID,int age, String address);

又或者通過(guò)設(shè)置setter和getter方法的形式寫(xiě)一個(gè)實(shí)現(xiàn)類(lèi)


public class User {
    private String name;            // 必傳
    private String cardID;          // 必傳
    private int age;                // 可選
    private String address;         // 可選

    public String getName() {
        return name;
    }

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

    public String getCardID() {
        return cardID;
    }

    public void setCardID(String cardID) {
        this.cardID = cardID;
    }

    public int getAge() {
        return age;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

先說(shuō)說(shuō)這兩種方式的優(yōu)劣:

第一種在參數(shù)不多的情況下澄峰,是比較方便快捷的嫉沽,一旦參數(shù)多了,代碼可讀性大大降低摊阀,并且難以維護(hù)耻蛇,對(duì)調(diào)用者來(lái)說(shuō)也造成一定困惑;

第二種可讀性不錯(cuò)胞此,也易于維護(hù)臣咖,但是這樣子做對(duì)象會(huì)產(chǎn)生不一致的狀態(tài),當(dāng)你想要傳入全部參數(shù)的時(shí)候漱牵,你必需將所有的setXX方法調(diào)用完成之后才行夺蛇。然而一部分的調(diào)用者看到了這個(gè)對(duì)象后,以為這個(gè)對(duì)象已經(jīng)創(chuàng)建完畢酣胀,就直接使用了刁赦,其實(shí)User對(duì)象并沒(méi)有創(chuàng)建完成,另外闻镶,這個(gè)User對(duì)象也是可變的甚脉,不可變類(lèi)所有好處都不復(fù)存在。

寫(xiě)到這里真想為自己最近封裝的表單控件捏一把汗铆农。牺氨。。所以有沒(méi)有更好地方式去實(shí)現(xiàn)它呢墩剖,那就是接下來(lái)要理解的Builder模式了猴凹。

二、定義

    將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離岭皂,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的展示郊霎。

Builder模式屬于創(chuàng)建型,一步一步將一個(gè)復(fù)雜對(duì)象創(chuàng)建出來(lái)爷绘,允許用戶(hù)在不知道內(nèi)部構(gòu)建細(xì)節(jié)的情況下书劝,可以更精細(xì)地控制對(duì)象的構(gòu)造流程进倍。

三、Builder模式變種-鏈?zhǔn)秸{(diào)用

代碼實(shí)現(xiàn)


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

    private User(UserBuilder userBuilder){
        this.name=userBuilder.name;
        this.cardID=userBuilder.cardID;
        this.age=userBuilder.age;
        this.address=userBuilder.address;
        this.phone=userBuilder.phone;
    }

    public String getName() {
        return name;
    }

    public String getCardID() {
        return cardID;
    }

    public int getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }

    public String getPhone() {
        return phone;
    }

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

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

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

需要注意的點(diǎn):

  • User類(lèi)的構(gòu)造方法是私有的庄撮,調(diào)用者不能直接創(chuàng)建User對(duì)象。
  • User類(lèi)的屬性都是不可變的洞斯。所有的屬性都添加了final修飾符,并且在構(gòu)造方法中設(shè)置了值烙如。并且么抗,對(duì)外只提供getters方法蝇刀。
  • Builder的內(nèi)部類(lèi)構(gòu)造方法中只接收必傳的參數(shù),并且該必傳的參數(shù)使用了final修飾符吞琐。

調(diào)用方式


        new User.UserBuilder("Jack","10086")
                .age(25)
                .address("GuangZhou")
                .phone("13800138000")
                .build();

相比起前面通過(guò)構(gòu)造函數(shù)和setter/getter方法兩種方式,可讀性更強(qiáng)。唯一可能存在的問(wèn)題就是會(huì)產(chǎn)生多余的Builder對(duì)象站粟,消耗內(nèi)存。然而大多數(shù)情況下我們的Builder內(nèi)部類(lèi)使用的是靜態(tài)修飾的(static)奴烙,所以這個(gè)問(wèn)題也沒(méi)多大關(guān)系。

關(guān)于線(xiàn)程安全

Builder模式是非線(xiàn)程安全的剖张,如果要在Builder內(nèi)部類(lèi)中檢查一個(gè)參數(shù)的合法性切诀,必需要在對(duì)象創(chuàng)建完成之后再檢查,

正確示例:


public User build() {
  User user = new user(this);
  if (user.getAge() > 120) {
    throw new IllegalStateException(“Age out of range”); // 線(xiàn)程安全
  }
  return user;
}

錯(cuò)誤示例:


public User build() {
  if (age > 120) {
    throw new IllegalStateException(“Age out of range”); // 非線(xiàn)程安全
  }
  return new User(this);
}

四搔弄、經(jīng)典Builder模式

UML類(lèi)圖

來(lái)源自《Android源碼設(shè)計(jì)模式與解析實(shí)戰(zhàn)》

Product : 產(chǎn)品抽象類(lèi)

Builder : 抽象Builder類(lèi)幅虑,規(guī)范產(chǎn)品組建,一般是由子類(lèi)實(shí)現(xiàn)具體的組建過(guò)程

ConcreteBuilder : 具體的Builder類(lèi)

Director : 統(tǒng)一組裝過(guò)程

Product角色


/**
 * 用戶(hù)抽象類(lèi)
 */
public abstract class User {
    protected String name;
    protected String cardID;
    protected int age;
    protected String address;

    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 "User [name ="+name+",cardID="+cardID+",age="+age+"," +
                "address="+address+"]";
    }
}

具體的Product類(lèi)


/**
 * 具體的Product角色 SysUser
 */
public class SysUser extends User {
    public SysUser() {

    }

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

抽象Builder類(lèi)


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 User create();
}

具體的Builder類(lèi)


public class AccountBuilder extends Builder{

    private User user=new SysUser();

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

Director角色顾犹,負(fù)責(zé)構(gòu)造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);
    }
}

測(cè)試代碼


public class Test{
    public static void main(String args){
        //構(gòu)建器
        Builder builder=new AccountBuilder();
        //Director
        Director director=new Director(builder);
        //封裝構(gòu)建過(guò)程:Jack倒庵,10086,25,GuangZhou
        director.construct("Jack",25,"GuangZhou");
        //打印結(jié)果
        System.out.println("Info :" +builder.create().toString());
    }
}

輸出結(jié)果

    System.out: Info :User [name =Jack,cardID=10086,age=25,address=GuangZhou]

五、用到Builder模式的例子

  • Android中的AlertDialog.Builder

private void showDialog(){
        AlertDialog.Builder builder=new AlertDialog.Builder(context);
        builder.setIcon(R.drawable.icon);
        builder.setTitle("Title");
        builder.setMessage("Message");
        builder.setPositiveButton("Button1", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //TODO
            }
        });
        builder.setNegativeButton("Button2", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //TODO
            }
        });
        
        builder.create().show();
}

  • OkHttp中OkHttpClient的創(chuàng)建

OkHttpClient  okHttpClient = new OkHttpClient.Builder()
                 .cache(getCache()) 
                 .addInterceptor(new HttpCacheInterceptor())
                 .addInterceptor(new LogInterceptor())
                 .addNetworkInterceptor(new HttpRequestInterceptor()) 
                 .build();

  • Retrofit中Retrofit對(duì)象的創(chuàng)建

Retrofit retrofit = new Retrofit.Builder()
         .client(createOkHttp())
         .addConverterFactory(GsonConverterFactory.create())
         .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
         .baseUrl(BASE_URL)
         .build();

可見(jiàn)在實(shí)際使用中蹦渣,均省略掉了Director角色,在很多框架源碼中貌亭,涉及到Builder模式時(shí)柬唯,大多都不是經(jīng)典GOF的Builder模式,而是選擇了結(jié)構(gòu)更加簡(jiǎn)單的后者圃庭。

六锄奢、優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 良好的封裝性失晴,使得客戶(hù)端不需要知道產(chǎn)品內(nèi)部實(shí)現(xiàn)的細(xì)節(jié)
  • 建造者獨(dú)立,擴(kuò)展性強(qiáng)

缺點(diǎn):

  • 產(chǎn)生多余的Builder對(duì)象拘央、Director對(duì)象涂屁,消耗內(nèi)存

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市灰伟,隨后出現(xiàn)的幾起案子拆又,更是在濱河造成了極大的恐慌,老刑警劉巖栏账,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帖族,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挡爵,警方通過(guò)查閱死者的電腦和手機(jī)竖般,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茶鹃,“玉大人涣雕,你說(shuō)我怎么就攤上這事”蒸妫” “怎么了挣郭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)男杈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)旺垒,這世上最難降的妖魔是什么肤无? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任宛渐,我火速辦了婚禮,結(jié)果婚禮上业岁,老公的妹妹穿的比我還像新娘寇蚊。我一直安慰自己,他們只是感情好允耿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著业稼,像睡著了一般蚂蕴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谦纱,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天跨嘉,我揣著相機(jī)與錄音吃嘿,去河邊找鬼。 笑死亮瓷,一個(gè)胖子當(dāng)著我的面吹牛嘱支,可吹牛的內(nèi)容都是我干的挣饥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼汛聚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼倚舀!你這毒婦竟也來(lái)了忍宋?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舵稠,失蹤者是張志新(化名)和其女友劉穎柱查,沒(méi)想到半個(gè)月后唉工,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體汹忠,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年谣膳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了继谚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阵幸。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挚赊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妹卿,到底是詐尸還是另有隱情蔑鹦,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布懊直,位于F島的核電站室囊,受9級(jí)特大地震影響魁索,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尝偎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肤寝。 院中可真熱鬧抖僵,春花似錦、人聲如沸义桂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嚷闭,卻和暖如春赖临,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗅榕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工吵聪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帽蝶。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓励稳,卻偏偏與公主長(zhǎng)得像囱井,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子新翎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • 1 場(chǎng)景問(wèn)題# 1.1 繼續(xù)導(dǎo)出數(shù)據(jù)的應(yīng)用框架## 在討論工廠(chǎng)方法模式的時(shí)候,提到了一個(gè)導(dǎo)出數(shù)據(jù)的應(yīng)用框架愁拭。 對(duì)于...
    七寸知架構(gòu)閱讀 5,736評(píng)論 1 64
  • 沒(méi)有人買(mǎi)車(chē)會(huì)只買(mǎi)一個(gè)輪胎或者方向盤(pán)敛苇,大家買(mǎi)的都是一輛包含輪胎顺呕、方向盤(pán)和發(fā)動(dòng)機(jī)等多個(gè)部件的完整汽車(chē)括饶。如何將這些部件組...
    justCode_閱讀 1,841評(píng)論 1 6
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法图焰,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法僵闯,異常的語(yǔ)法藤滥,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,630評(píng)論 18 399
  • 面向?qū)ο蟮牧笤瓌t 單一職責(zé)原則 所謂職責(zé)是指類(lèi)變化的原因。如果一個(gè)類(lèi)有多于一個(gè)的動(dòng)機(jī)被改變向图,那么這個(gè)類(lèi)就具有多于...
    JxMY閱讀 940評(píng)論 1 3
  • 青春里最讓人留戀的還是那段青澀的感情榄攀。 小學(xué)二年級(jí)時(shí)金句,一個(gè)普通的早上,班上來(lái)個(gè)個(gè)外地的女生违寞。 畢竟是新來(lái)的,所以大...
    古月適之不適閱讀 243評(píng)論 0 2