1. 程式人生 > >【java設計模式】之 建造者(Builder)模式

【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)在物件建立過程中會使用到系統的一些其他物件,這些物件在產品物件的建立過程中不易得到,也可以採用建造者模式封裝該物件的建立過程。這種場景只能是一個補償的方法,因為一個物件不容易獲得,而在設計階段竟然沒有發現,而要通過設計這模式來柔化建立過程,本身設計已經出問題了。

        到這裡,我們會發現,建造者模式和工廠方法模式有點像。但是兩者有區別:建造者模式關注的是零件型別和裝配工藝(順序),而工廠模式是建立一個物件,這是最大不同的地方。

        建立者模式就介紹這麼多吧,如有錯誤之處,歡迎留言指正~

文末福利:“程式設計師私房菜”,一個有溫度的公眾號~ 
程式設計師私房菜

_____________________________________________________________________________________________________________________________________________________

-----樂於分享,共同進步!