23種設(shè)計模式-建造者模式

  1. 變化是永恒的

在模板方法模式中益咬,用的是汽車模型舉例逮诲,在這里還是用汽車模型,但是需求有變動幽告,制作奔馳汛骂、寶馬的模型,增加了一個新需求:汽車的啟動评腺、停止帘瞭、喇叭聲音、引擎聲音都由客戶自己控制蒿讥,他想要什么順序就是什么順序蝶念。
分析一下需求,奔馳芋绸、寶馬都是一個產(chǎn)品媒殉,他們有共有的屬性,需求關(guān)心的是單個模型的運(yùn)行過程:奔馳模型A是先有引擎聲音摔敛,然后在響喇叭廷蓉;奔馳模型B是先啟動起來,然后在有引擎聲音马昙。那么為了滿足需求桃犬,要什么順序就立馬能產(chǎn)生什么順序的模型,這些模型都有run()方法行楞,但是具體到?jīng)]一個模型中run方法中的執(zhí)行順序并不相同攒暇,需要根據(jù)客戶的需求變化順序。先找一個簡單的切入點(diǎn)——產(chǎn)品類子房,每個車模型都是一個產(chǎn)品形用,類圖11-1:


11-1

類圖比較簡單就轧,在carModel中定義一個setSequence方法,來設(shè)置運(yùn)行的執(zhí)行順序田度,代碼如下:

//汽車抽象類
public abstract class CarModel {
    private ArrayList<String> sequence;
    protected abstract void start();
    protected abstract void stop();
    protected abstract void alarm();
    protected abstract void engineBoom();
    final public void run(){
        if(sequence == null && sequence.size()<=0){
            start();
            alarm();
            engineBoom();
            stop();
        }else{
            for(String action : sequence){
                if("start".equals(action)){
                    start();
                }else if("alarm".equals(action)){
                    alarm();
                }else if("engineBoom".equals(action)){
                    engineBoom();
                }else if("stop".equals(action)){
                    stop();
                }
            }
        }
    }

    final public void setSequence(ArrayList<String> sequence){
        this.sequence = sequence;
    }
}
//寶馬模型
public class BMWModel extends CarModel {
    @Override
    protected void start() {
        System.out.println("寶馬start妒御。。镇饺。");
    }

    @Override
    protected void stop() {
        System.out.println("寶馬stop乎莉。。兰怠。");
    }

    @Override
    protected void alarm() {
        System.out.println("寶馬alarm。李茫。揭保。");
    }

    @Override
    protected void engineBoom() {
        System.out.println("寶馬engineBoom。魄宏。秸侣。");
    }
}
//奔馳模型
public class BenzModel extends CarModel {
    @Override
    protected void start() {
        System.out.println("奔馳start。宠互。味榛。");
    }

    @Override
    protected void stop() {
        System.out.println("奔馳stop。予跌。搏色。");
    }

    @Override
    protected void alarm() {
        System.out.println("奔馳alarm。券册。频轿。");
    }

    @Override
    protected void engineBoom() {
        System.out.println("奔馳engineBoom。烁焙。航邢。");
    }
}
public class Client {
    public static void main(String[] args) {
        ArrayList<String> sequence = new ArrayList<>();
        sequence.add("engineBoom");
        sequence.add("start");
        sequence.add("stop");
        BenzModel benz = new BenzModel();
        benz.setSequence(sequence);
        benz.run();
    }
}

CarModel的設(shè)計原理是這樣的,setSequence方法是允許客戶自己設(shè)置一個順序骄蝇。對于一個具體的模型永遠(yuǎn)都是固定的膳殷,但是對N個模型就是動態(tài)的了,run方法根據(jù)sequence中設(shè)置的順序來決定具體方法的只想順序九火。具體模型代碼就不寫了赚窃。
但是想想需求,汽車的動作執(zhí)行順序是能夠隨意調(diào)整的岔激,我們只滿足了一個需求考榨,還有下一個需求,第二個寶馬模型鹦倚,只要啟動河质、停止,其他的都不要;第三個模型掀鹅,先喇叭散休,然后啟動,然后停止乐尊。戚丸。。我們不可能一個一個的來寫場景類滿足需求扔嵌,那么我們?yōu)槊糠N模型定義一個建造者限府,需要啥順序告訴建造者,由建造者來建造痢缎,類圖11-2:


11-2

增加了一個CarBuilder抽象類胁勺,由它來組裝各個車模,要什么類型什么順序的車輛模型独旷,都由相關(guān)子類完成署穗,代碼如下:

public abstract class CarBuilder {
    public abstract void setSequence(ArrayList<String> sequence);
    public abstract CarModel getCarModel();
}
public class BenzBuilder extends CarBuilder {
    private BenzModel benz = new BenzModel();
    @Override
    public void setSequence(ArrayList<String> sequence) {
        this.benz.setSequence(sequence);
    }

    @Override
    public CarModel getCarModel() {
        return benz;
    }
}
public class BMWBuilder extends CarBuilder {
    private BMWModel bmw = new BMWModel();
    @Override
    public void setSequence(ArrayList<String> sequence) {
        this.bmw.setSequence(sequence);
    }

    @Override
    public CarModel getCarModel() {
        return bmw;
    }
}
public class Client {
    public static void main(String[] args) {
        ArrayList<String> sequence = new ArrayList<>();
        sequence.add("engineBoom");
        sequence.add("start");
        sequence.add("stop");
        BenzBuilder builder = new BenzBuilder();
        builder.setSequence(sequence);
        BenzModel benz = (BenzModel) builder.getCarModel();
        benz.run();
    }
}

書上說,這個代碼比直接訪問產(chǎn)品類要簡單了很多(并沒有感覺嵌洼,還得多寫幾行代碼案疲,直接訪問產(chǎn)品類不也是直接setSequence嗎?)麻养。在我們做項目時褐啡,經(jīng)常會有一個共識:需求是無底洞,是無理性的鳖昌,不可能告訴你不加需求就不加春贸,這四個過程(start,stop遗遵,alarm萍恕,engineboom)按排列組合有很多種,我們不可能預(yù)知他們要什么順序车要,然后這里在封裝一下允粤,類圖11-3:

11-3

我們增加了一個Director類,負(fù)責(zé)按照指定的順序生產(chǎn)模型(就是把setSequenece的步驟寫出來翼岁,需要生成那個順序的模型直接生成就好了)Director類代碼如下:

public class Director {
    private ArrayList<String> sequence = new ArrayList<String>();
    private BenzBuilder benzBuilder = new BenzBuilder();
    private BMWBuilder bmwBuilder = new BMWBuilder();
    
    public BenzModel getABenzModel(){
        this.sequence.clear();
        this.sequence.add("start");
        this.sequence.add("stop");
        this.benzBuilder.setSequence(this.sequence);
        return (BenzModel) this.benzBuilder.getCarModel();
    }
//這里還可以寫很多方法类垫,將可能會需要的順序的模型都寫出來
}

順便說一下,這個程序中用到了很多this琅坡,如果要調(diào)用類中的成員變量或方法悉患,需要在前面加this,還有調(diào)用父類中的成員變量或方法就加上super榆俺,這是為了讓代碼看著更清晰售躁。

注意:這個ArrayList作為成員變量的時候坞淮,要考慮線程安全的問題,要防止數(shù)據(jù)混亂的情況
這個就是建造者模式陪捷。(個人感覺有點(diǎn)類似工廠模式回窘,也是負(fù)責(zé)生產(chǎn)目標(biāo)產(chǎn)品的一個設(shè)計模式

  1. 建造者模式的定義

建造者模式(Builder Pattern)也叫生成器模式,其定義如下:
Separate the construction of a complex object from its representation so that the same construction process can create different representations.(將一個復(fù)雜的對象的構(gòu)建與他的表示分離市袖,是的同樣的構(gòu)建過程可以創(chuàng)建不同的表示啡直。)
建造者模式通用類圖11-4:

11-4

在建造者模式中,有如下4個角色:

  • Product產(chǎn)品類
    通常是實(shí)現(xiàn)了模板方法模式苍碟,也就是有模板方法和基本方法的酒觅,例子中的BenzModel和BMWBuilder就是屬于產(chǎn)品類。
  • Builder抽象建造者
    規(guī)范產(chǎn)品的組件微峰,一般由子類實(shí)現(xiàn)舷丹。例子中的CarBuilder就屬于抽象建造者
  • ConcreteBuilder 具體建造者
    實(shí)現(xiàn)抽象類定義的所有方法,并且返回一個組件好的對象县忌。例子中的BenzBuilder和BMWBuilder就屬于具體建造者掂榔。
  • Director導(dǎo)演類
    負(fù)責(zé)安排已有模塊的順序继效,然后告訴Builder開始建造症杏。

建造者模式的通用源代碼也比較簡單,先看Product類瑞信,通常是一個組合或繼承(如模板方法模式)產(chǎn)生的類厉颤,代碼如下:

public class Product {
    public void doSomething(){
        //獨(dú)立業(yè)務(wù)處理
    }
}

抽象建造者,其中凡简,setPart方法是零件的配置逼友,什么是零件?其他的對象秤涩,獲得一個不同的零件帜乞,或者不同的裝配順序就可能產(chǎn)生不同的產(chǎn)品,代碼如下:

public abstract class Builder {
    //設(shè)置產(chǎn)品部分筐眷,已獲得不同的產(chǎn)品
    public abstract void setPart();
    //建造產(chǎn)品
    public abstract Product buildProduct();
}

具體的建造者黎烈,需要注意的是,如果有多個產(chǎn)品類就有幾個具體的建造者匀谣,而且這多個產(chǎn)品類具有相同接口或抽象類照棋,代碼如下:

public class ConcreteBuilder extends Builder  {
    private Product product = new Product();
    @Override
    public void setPart() {
        //產(chǎn)品類內(nèi)的邏輯處理
    }

    @Override
    public Product buildProduct() {
        return product;
    }
}

導(dǎo)演類,起到封裝的作用,避免高層模塊深入到建造者內(nèi)部的實(shí)現(xiàn)類武翎,當(dāng)然烈炭,在建造者模塊比較龐大時,導(dǎo)演類可以有多個宝恶,代碼如下:

public class Director {
    private Builder builder = new ConcreteBuilder();
    public Product getAProduct(){
        builder.setPart();
        return builder.buildProduct();
    }
}
  1. 建造者模式的應(yīng)用

3.1 建造者模式的優(yōu)點(diǎn)

  • 封裝性
    使用建造者模式可以使客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié)符隙,如例子中我們就不需要關(guān)心每一個具體的模型內(nèi)部是如何實(shí)現(xiàn)的趴捅,產(chǎn)生的對象類型就是CarModel。
  • 建造者獨(dú)立膏执,容易擴(kuò)展
    BenzBuilder和BMWBuilder是相互獨(dú)立的驻售,如果我們要擴(kuò)展新的汽車模型,只需要添加對應(yīng)的產(chǎn)品類和建造類更米,并不影響其他模型欺栗,符合開閉原則
  • 便于控制細(xì)節(jié)風(fēng)險
    由于具體的建造者是獨(dú)立的,因此可以對建造過程逐步細(xì)化征峦,而不對其他模塊產(chǎn)生任何影響迟几,這就是低耦合的表現(xiàn)
    3.2 建造者模式的使用場景
  • 相同的方法,不同的執(zhí)行順序栏笆,產(chǎn)生不同的事件結(jié)果类腮,例子就很明顯的代表
  • 多個部件或零件,都可以裝配到一個對象中蛉加,但是產(chǎn)生的運(yùn)行結(jié)果又不相同時蚜枢,就可以采用建造者模式,例如HttpClientBuilder用來創(chuàng)建httpClient针饥,可以設(shè)置很多相關(guān)的配置參數(shù)厂抽,這些配置參數(shù)就是部件或零件
  • 產(chǎn)品類非常復(fù)雜,或者產(chǎn)品類中的調(diào)用順序不同產(chǎn)生了不同的效能(這感覺和第二條差不多
  • 在對象創(chuàng)建過程中會使用到系統(tǒng)中的一些其他對象丁眼,這些對象在產(chǎn)品對象的創(chuàng)建過程中不易得到時筷凤,也可以采用建造者模式封裝該對象的創(chuàng)建過程。該中場景只能是一個補(bǔ)償方法苞七,因為一個對象不容易獲得藐守,在設(shè)計階段竟然沒有發(fā)覺,這已經(jīng)違反設(shè)計的最初目標(biāo)了
    3.3 建造者模式的注意事項
    建造者模式關(guān)注的是零件類型和裝配工藝(順序)蹂风,這是它與工廠方法模式最大不同的地方卢厂,雖然同為創(chuàng)建類模式,但是注重點(diǎn)不同惠啄。(剛想說慎恒,建造者模式和工廠模式有什么區(qū)別來著,這里就提出來了礁阁,工廠模式關(guān)注點(diǎn)在同一類產(chǎn)品巧号,但是產(chǎn)品的型號或是什么是有區(qū)別的;而建造者模式則關(guān)注產(chǎn)品內(nèi)部的方法執(zhí)行順序而導(dǎo)致效果不同姥闭,建造者模式創(chuàng)建的是相同型號的產(chǎn)品丹鸿,但是裝配工藝(順序)有所不同
    4.建造者模式的擴(kuò)展
    已經(jīng)不用擴(kuò)展了,因為在汽車模型的例子中進(jìn)行了擴(kuò)展棚品,引入了模板方法模式靠欢,建造者模式中還有一個角色沒有說明廊敌,就是零件,建造者怎么去建造一個對象门怪?是零件的組裝骡澈,組裝順序不同對象效能也不同,這才是建造者模式要表達(dá)的核心意義掷空,而怎么才能更好的達(dá)到這種效果呢肋殴?引入模板方法模式是一個非常簡單而有效的辦法。
    這個建造者模式和工廠模式非常的相似坦弟,但是記住一點(diǎn):建造者模式最主要的功能是基本方法的調(diào)用順序安排护锤,也就是基本方法已經(jīng)實(shí)現(xiàn)了,通俗的說就是零件的裝配酿傍,順序不同產(chǎn)生的對象也不同烙懦;而工廠方法的重點(diǎn)是創(chuàng)建,創(chuàng)建零件是他的主要職責(zé)赤炒,組裝順序則不是它關(guān)心的氯析。
    5.最佳實(shí)踐
    在使用建造者模式的時候考慮一下模板方法模式,別孤立的思考一個模式莺褒,僵化地套用一個模式會讓你受害無窮
    內(nèi)容來自《設(shè)計模式之禪》
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掩缓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子癣朗,更是在濱河造成了極大的恐慌拾因,老刑警劉巖旺罢,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旷余,死亡現(xiàn)場離奇詭異,居然都是意外死亡扁达,警方通過查閱死者的電腦和手機(jī)正卧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跪解,“玉大人炉旷,你說我怎么就攤上這事〔婕ィ” “怎么了窘行?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長图仓。 經(jīng)常有香客問我罐盔,道長,這世上最難降的妖魔是什么救崔? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任惶看,我火速辦了婚禮捏顺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纬黎。我一直安慰自己幅骄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布本今。 她就那樣靜靜地躺著拆座,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冠息。 梳的紋絲不亂的頭發(fā)上懂拾,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音铐达,去河邊找鬼岖赋。 笑死,一個胖子當(dāng)著我的面吹牛瓮孙,可吹牛的內(nèi)容都是我干的唐断。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼杭抠,長吁一口氣:“原來是場噩夢啊……” “哼脸甘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起偏灿,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤丹诀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后翁垂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铆遭,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年沿猜,在試婚紗的時候發(fā)現(xiàn)自己被綠了枚荣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡啼肩,死狀恐怖橄妆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祈坠,我是刑警寧澤害碾,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站赦拘,受9級特大地震影響慌随,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜另绩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一儒陨、第九天 我趴在偏房一處隱蔽的房頂上張望花嘶。 院中可真熱鬧,春花似錦蹦漠、人聲如沸椭员。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隘击。三九已至,卻和暖如春研铆,著一層夾襖步出監(jiān)牢的瞬間埋同,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工棵红, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凶赁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓逆甜,卻偏偏與公主長得像虱肄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子交煞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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