【java】設計模式之模板方法模式

模板方法模式:由子類決定如何實現父類算法中的哪一步。

例子:咖啡機自動衝咖啡,但是我們可能做卡布奇諾(Cappuccino),也可能做焦糖瑪奇朵(CaramelMacchiato),但是總體來說步驟一致,只是最後加入的其他材料不太一樣。

衝咖啡的步驟:獲得咖啡豆(getBeans()),放入咖啡機(putIntoCoffeemaker()),磨碎咖啡豆(grindBeans()),衝咖啡(makeCoffee())。

假設有這麼一個抽象類,AutoCoffee,它的代碼如下:

public abstract class AutoCoffee {
	
	//獲得咖啡豆
	protected void getBeans(){
		System.out.println("獲得咖啡豆");
	}
	
	//放入咖啡機
	protected void putIntoCoffeemaker(){
		System.out.println("放入咖啡機");
	}
	
	//磨碎咖啡豆
	protected void grindBeans(){
		System.out.println("磨碎咖啡豆");
	}
	
	//衝咖啡
	protected void makeCoffee(){
		System.out.println("衝咖啡");
	}
	
	//自動衝咖啡
	final public void autoMake(){
		this.getBeans();
		this.putIntoCoffeemaker();
		this.grindBeans();
		this.makeCoffee();
	}
}

客戶端不用關心自動衝咖啡內部的步驟如何實現,只需要調用autoMake()方法就夠了,既然這樣,我們的步驟方法,都設爲protected。

然後,我們需要一個設定,讓咖啡機自動衝卡布奇諾,只需要在最後加入牛奶和奶泡就好了:

Cappuccino.java

public class Cappuccino extends AutoCoffee {
	
	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和奶泡");
	}
}
再來一個設定,自動衝焦糖瑪奇朵,只需要在最後加入牛奶和焦糖:

CaramelMacchiato.java

public class CaramelMacchiato extends AutoCoffee {

	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和焦糖");
	}
	
}
OK,這樣,來一個客戶端:

Clinet.java

public class Clinet {
	public static void main(String[] args) {
			AutoCoffee cappuccino = new Cappuccino();
			AutoCoffee caramelMacchiato = new CaramelMacchiato();
			cappuccino.autoMake();
			caramelMacchiato.autoMake();
	}
}

輸出結果:

獲得咖啡豆
放入咖啡機
磨碎咖啡豆
衝咖啡
加入牛奶和奶泡
---------------------
獲得咖啡豆
放入咖啡機
磨碎咖啡豆
衝咖啡
加入牛奶和焦糖


以上就是模板方法。是的,就是這麼簡單,或許你有疑問:如果子類需要更改衝咖啡的過程,或者需要稍微不同怎麼辦?

針對第一種情況,如果真的很不一樣,需要大改,幾近完全不同,那麼,你不應該繼承AutoCoffee這個抽象類,而是寫一個類。但是如果只是稍微不同,那麼我們就引用鉤子(hook)的概念。

假設,我們的磨碎咖啡豆這一步驟,有手搖和電動兩種選擇,無論是卡布奇諾還是焦糖瑪奇朵,我們都採用手動,但是實現方式不一樣,我們可以這樣編寫抽象類AutoCoffee

public abstract class AutoCoffee {
	private boolean flag = true;	
	//獲得咖啡豆
	protected void getBeans(){
		System.out.println("獲得咖啡豆");
	}
	//放入咖啡機
	protected void putIntoCoffeemaker(){
		System.out.println("放入咖啡機");
	}
	//鉤子方法
	protected boolean isMotorDriven(){
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}	
	//磨碎咖啡豆
	protected void grindBeansByMotorDriven(){
		System.out.println("電動磨碎咖啡豆");
	}
	protected void grindBeansByHand(){
		System.out.println("手動磨碎咖啡豆");
	}	
	//衝咖啡
	protected void makeCoffee(){
		System.out.println("衝咖啡");
	}	
	//自動衝咖啡
	final public void autoMake(){
		this.getBeans();
		this.putIntoCoffeemaker();
		
		if(isMotorDriven()){
			this.grindBeansByMotorDriven();
		}else{
			this.grindBeansByHand();
		}
		this.makeCoffee();
	}
}

新增加一個屬性:flag和setter方法,以及判斷flag的isMotorDriven()方法,grindBeans()變爲兩個方法,一個是grindBeansByMotorDriven()電動磨碎咖啡豆,一個是grindBeansByHand()手動磨碎咖啡豆。autoMake()修改爲判斷isMotorDriven(),根據條件不同,選擇是手動還是電動。flag爲true,默認爲電動。

子類實現:

public class Cappuccino extends AutoCoffee {
	//設爲手動
	@Override
	protected boolean isMotorDriven() {	
		return false;
	}
	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和奶泡");
	}
}


public class CaramelMacchiato extends AutoCoffee {
	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和焦糖");
	}
}
Clinet.java

public class Clinet {
	public static void main(String[] args) {
			AutoCoffee cappuccino = new Cappuccino();
			AutoCoffee caramelMacchiato = new CaramelMacchiato();
			cappuccino.autoMake();
			System.out.println("------------------");
			caramelMacchiato.setFlag(false);
			caramelMacchiato.autoMake();
	}
}

卡布奇諾重寫isMotorDriven()方法,返回false(而非flag屬性),這樣就只能是手動,客戶端無法自己選擇(即使調用setFlag()也不能改變),而焦糖瑪奇朵是可以通過setFlag()來設置是手動還是電動。

結果如下:

獲得咖啡豆
放入咖啡機
手動磨碎咖啡豆
衝咖啡
加入牛奶和奶泡
------------------
獲得咖啡豆
放入咖啡機
手動磨碎咖啡豆
衝咖啡
加入牛奶和焦糖


模板方法還體現了一個重要的設計原則:好萊塢原則。但是很多人搞不清楚好萊塢原則和依賴倒置原則,改天另開博客,詳述好萊塢原則、依賴倒置原則、控制反轉和依賴注入的區別。

發佈了43 篇原創文章 · 獲贊 7 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章