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