我們還是舉上一節的例子:生產汽車。上一節我們通過模板方法模式控制汽車跑起來的動作,那么需求是無止境的,現在如果老板又增加了額外的需求:汽車啟動、停止、鳴笛引擎聲都由客戶自己控制,他想要什么順序就什么順序,那該如何做呢?
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)在對象創建過程中會使用到系統的一些其他對象,這些對象在產品對象的創建過程中不易得到,也可以采用建造者模式封裝該對象的創建過程。這種場景只能是一個補償的方法,因為一個對象不容易獲得,而在設計階段竟然沒有發現,而要通過設計這模式來柔化創建過程,本身設計已經出問題了。
到這里,我們會發現,建造者模式和工廠方法模式有點像。但是兩者有區別:建造者模式關注的是零件類型和裝配工藝(順序),而工廠模式是創建一個對象,這是最大不同的地方。
創建者模式就介紹這么多吧,如有錯誤之處,歡迎留言指正~
文章列表