【java設計模式】之 建造者(Builder)模式
我們還是舉上一節的例子:生產汽車。上一節我們通過模板方法模式控制汽車跑起來的動作,那麼需求是無止境的,現在如果老闆又增加了額外的需求:汽車啟動、停止、鳴笛引擎聲都由客戶自己控制,他想要什麼順序就什麼順序,那該如何做呢?
1. 汽車無休止的改造
假如現在要生產兩種車,賓士和寶馬,這兩輛車都有共性,我們所需要關注的是單個車的執行過程,這才是老闆所關心的點所在。我們先這樣想,針對這個需求,我們要找到一個切入點,那就是產品類,每個車都是一個產品,那麼在產品類中我們可以控制車的執行順序,這樣每個車都可以擁有自己想要的順序了。基於此,我們設計如下類圖:
我們看到CarModel中有個setSequence方法,通過傳入一個ArrayList來控制執行順序,run方法根據這個ArrayList中儲存的順序執行,然後賓士車和寶馬車分別繼承這個CarModel即可,這貌似是很好的實現,它很像上一節的模板方法模式,只是多了個方法可以設定執行順序。我們看一下CarModel具體程式碼的實現:
public abstract class CarModel { private ArrayList<String> sequence = new ArrayList<String>(); //維護一個ArrayList儲存執行命令關鍵字 protected abstract void start(); protected abstract void stop(); protected abstract void alarm(); protected abstract void engineBoom(); final public void run() { for(int i = 0; i < this.sequence.size(); i ++) { //根據ArrayList中儲存的順序執行相應的動作 String actionName = this.sequence.get(i); if(actionName.equalsIgnoreCase("start")) { this.start(); //啟動汽車 } else if(actionName.equalsIgnoreCase("stop")) { this.stop(); //停止汽車 } else if(actionName.equalsIgnoreCase("alarm")) { this.alarm(); //汽車鳴笛 } else if(actionName.equalsIgnoreCase("engine boom")) { this.engineBoom(); //汽車轟鳴 } } } final public void setSequence(ArrayList<String> sequence) { //獲得執行順序的命令,即一個ArrayList this.sequence = sequence; } }
CarModel中的setSequence方法允許客戶自己設定一個順序,我們看看子類的實現:
public class BenzModel extends CarModel { @Override protected void start() { System.out.println("賓士啟動……"); } @Override protected void stop() { System.out.println("賓士停止……"); } @Override protected void alarm() { System.out.println("賓士鳴笛……"); } @Override protected void engineBoom() { System.out.println("賓士轟鳴"); } } //寶馬就略了……一樣的
下面我們增加一個測試類實現該需求:
public class Client {
public static void main(String[] args) {
BenzModel benz = new BenzModel();
//存放run順序
ArrayList<String> sequence = new ArrayList<String>();
sequence.add("engine boom"); //老闆說:跑之前先轟鳴比較帥!
sequence.add("start");
sequence.add("stop");
//我們把這個順序賦予賓士
benz.setSequence(sequence);
benz.run();
}
}
這樣好像已經順利完成了任務了,但是別忘了,我們這只是滿足了一個需求,如果下一個需求是寶馬車只轟鳴,再下一個需求是賓士車只跑不停……等等……那豈不是要一個個寫測試類來實現?顯然這不是我們想要的。
我們可以這樣做:為每種產品模型定義一個建造者,你要啥順序直接告訴建造者,由建造者來建造即可,於是我們重新設計類圖:
我們增加了一個CarBuilder類,由它來組裝各個車模型,要什麼型別的順序就由相關的子類去完成即可,我們來看看CarBuilder的程式碼:
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 this.benz; //將這個模型返回
}
}
//寶馬車一樣,不寫了……
現在兩輛車的組裝者都寫好了,現在我們寫一個測試類來測試一下:
public class Client {
public static void main(String[] args) {
//存放run順序
ArrayList<String> sequence = new ArrayList<String>();
sequence.add("engine boom");
sequence.add("start");
sequence.add("stop");
//要用這個順序造一輛賓士
BenzBuilder benzBuilder = new BenzBuilder();
//把順序給賓士組裝者
benzBuilder.setSequence(sequence);
//賓士組裝者拿到順序後就給你生產一輛來
BenzModel benz = (BenzModel) benzBuilder.getCarModel();
benz.run();
}
}
如果我要生產一輛寶馬車,只需要換成寶馬車的組裝者即可,這樣我們不用直接訪問產品類了,全部訪問組裝者就行,是不是感覺到很方便,我管你怎麼生產,我扔給你個順序,你給我弄輛車出來,要的就是這種效果!
可是人的需求是個無底洞,特別是老闆,他哪天不爽了,又要換順序,這樣還是挺麻煩的,四個過程(start、stop、alarm、engine boom)按排列組合也有很多中情況,我們不能保證老闆想要哪種順序,咋整?無奈,我們只能使出最後的殺手鐗了,找個設計師過來指揮各個時間的先後順序,然後為每種順序指定一個程式碼,你說一種我們立刻就給你生產!我們再修改一下類圖……
類圖看著有點複雜,其實不然,只是在原來的基礎上增加了一個Director類充當著設計師的角色,負責按照指定的順序生產模型,比如我們要一個A順序的賓士車,B順序的賓士車,A順序的寶馬車,B順序的寶馬車……等等,我們來看下Director類的程式碼:
public class Director {
private ArrayList<String> sequence = new ArrayList<String>();
private BenzBuilder benzBuilder = new BenzBuilder();
private BWMBuilder bwmBuilder = new BWMBuilder();
//A順序的賓士車
public BenzModel getABenzModel() {
this.sequence.clear();
this.sequence.add("start");
this.sequence.add("stop");
//返回A順序的賓士車
this.benzBuilder.setSequence(sequence);
return (BenzModel) this.benzBuilder.getCarModel();
}
//B順序的賓士車
public BenzModel getBBenzModel() {
this.sequence.clear();
this.sequence.add("engine boom");
this.sequence.add("start");
this.sequence.add("stop");
//返回B順序的賓士車
this.benzBuilder.setSequence(sequence);
return (BenzModel) this.benzBuilder.getCarModel();
}
//C順序的寶馬車
public BenzModel getCBWMModel() {
this.sequence.clear();
this.sequence.add("start");
this.sequence.add("alarm");
this.sequence.add("stop");
//返回C順序的寶馬車
this.bwmBuilder.setSequence(sequence);
return (BenzModel) this.bwmBuilder.getCarModel();
}
//D順序的寶馬車
public BenzModel getDBWMModel() {
this.sequence.clear();
this.sequence.add("engine boom");
this.sequence.add("start");
//返回D順序的寶馬車
this.bwmBuilder.setSequence(sequence);
return (BenzModel) this.bwmBuilder.getCarModel();
}
//還有很多其他需求,設計師嘛,想啥需求就給你弄啥需求
}
有了這樣一個設計師,我們的測試類就更容易處理了,比如現在老闆要10000輛A類賓士車,100000輛B類賓士車,20000C型別寶馬車,D型別不要:
public class Client {
public static void main(String[] args) {
Director director = new Director();
for(int i = 0; i < 10000; i ++) {
director.getABenzModel();
}
for(int i = 0; i < 100000; i ++) {
director.getBBenzModel();
}
for(int i = 0; i < 20000; i ++) {
director.getCBWMModel();
}
}
}
是不是很清晰很簡單,我們重構程式碼的最終第就是簡單清晰。這就是建造者模式。
2. 建造者模式的定義
我們來看看建造者模式的一般定義:Separate the construction of a complex object from its representation so that the same construction process can create different representations. 即:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。比如上面的例子,我們可以用同樣的順序構造不同的車。建造者模式的通用類圖如下:
Product是最終的產品類,Builder是建造者,Director是指揮者。Director負責安排已有模組的順序,然後告訴Builder開始建造。
3. 建造者模式的優點
1)封裝性:使用建造者模式可以是客戶端不必知道產品內部組成的細節。
2)建造者獨立,容易擴充套件:BenzBuilder和BMWBuilder是相互獨立的,對系統擴充套件非常有利。
3)便於控制細節風險:由於具體的建造者是獨立的,因此可以對建造者過程逐步細化,而不對其他的模組產生任何影響。
4. 建造者模式的使用場景
1)相同的方法,不同的執行順序,產生不同的事件結果時,可以使用建造者模式。
2)多個部件或零件,都可以裝配到一個物件中,但是產生的執行結果又不想同時,可以使用建造者模式。
3)產品類非常複雜,或者產品類中的呼叫順序不同產生了不同的效能,這時候可以使用建造者模式。
4)在物件建立過程中會使用到系統的一些其他物件,這些物件在產品物件的建立過程中不易得到,也可以採用建造者模式封裝該物件的建立過程。這種場景只能是一個補償的方法,因為一個物件不容易獲得,而在設計階段竟然沒有發現,而要通過設計這模式來柔化建立過程,本身設計已經出問題了。
到這裡,我們會發現,建造者模式和工廠方法模式有點像。但是兩者有區別:建造者模式關注的是零件型別和裝配工藝(順序),而工廠模式是建立一個物件,這是最大不同的地方。
建立者模式就介紹這麼多吧,如有錯誤之處,歡迎留言指正~
文末福利:“程式設計師私房菜”,一個有溫度的公眾號~
_____________________________________________________________________________________________________________________________________________________
-----樂於分享,共同進步!