HeadFirst設計模式(九) - 模板方法模式

直接舉個例子

    來看看某咖啡廳裏,茶和咖啡的沖泡方式。具體以下:java

    咖啡的沖泡法:算法

  1. 把水煮沸;
  2. 用沸水沖泡咖啡;
  3. 把咖啡倒進杯子;
  4. 加糖和牛奶;

    茶的沖泡法:ide

  1. 把水煮沸;
  2. 用沸水侵泡茶葉;
  3. 把茶倒進杯子;
  4. 加檸檬;

用代碼實現上面的例子

public class Coffee {

	public Coffee() {
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}
	
	public void boilWater() {
		System.out.println("把水煮沸...");
	}

	public void brewCoffeeGrinds() {
		System.out.println("用沸水沖泡咖啡...");
	}

	public void pourInCup() {
		System.out.println("把咖啡倒進杯子...");
	}

	public void addSugarAndMilk() {
		System.out.println("加糖和牛奶...");
	}
}
public class Tea {

	public Tea() {
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}
	
	public void boilWater() {
		System.out.println("把水煮沸...");
	}

	public void steepTeaBag() {
		System.out.println("用沸水侵泡茶葉...");
	}

	public void pourInCup() {
		System.out.println("把茶倒進杯子...");
	}

	public void addLemon() {
		System.out.println("加檸檬...");
	}
}

    上面兩個類分別是咖啡類與茶類,咱們能夠看到,兩個類中有重複的代碼,這表示咱們須要清理一下設計了。在這裏,既然茶和咖啡師如此地類似,彷佛咱們應該將共同的部分抽取出來,放進一個基類中。函數

抽取相同的部分

    因爲咖啡與茶都含有咖啡因,因此咱們抽象出一個咖啡因對象做爲抽象基類,在製做咖啡與茶的過程當中,boilWater()方法和pourlnCup()方法是相同的,因此在基類中實現,其他的不一樣函數則在子類中實現。測試

    在基類中,定義了一個抽象方法prepareRecipe(),它負責調用具體的製做步驟,由子類本身實現。url

    在上面這個設計中,還能夠更進一步,在觀察一下,咖啡與茶還有什麼共同點呢?注意,兩種飲品都採用了相同的沖泡方法:spa

  1. 把水煮沸;
  2. 用熱水沖泡咖啡或者茶;
  3. 把飲品倒入杯子;
  4. 添加適當的調料;

    那麼,咱們也將prepareRecipe()方法進行抽象,如今就來看看怎麼作…….net

    將沖泡定義一個抽象方法brew(),在將添加調料定義一個抽象方法addCondiments(),最後將抽象方法prepareRecipe()進行修改,讓他負責沖泡步驟,新的CaffeineBeverage類看起來就像這樣:命令行

public abstract class CaffeineBeverage {
	
	// 相同的步驟在基類中作  
	public void boilWater() {
		System.out.println("把水煮沸...");
	}
	
	public void pourInCup() {
		System.out.println("把飲品倒進杯子...");
	}
	
	// 不一樣的步驟由子類本身實現
	public abstract void brew();
	public abstract void addCondiments();
	
	// 基類負責對每一步進行調用
	final void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}
}
package cn.net.bysoft.template;

public class Coffee extends CaffeineBeverage {
	@Override
	public void brew() {
		System.out.println("用沸水沖泡咖啡...");
	}
	@Override
	public void addCondiments() {
		System.out.println("加糖和牛奶...");
	}
}

package cn.net.bysoft.template;

public class Tea extends CaffeineBeverage {
	@Override
	public void brew() {
		System.out.println("用沸水侵泡茶葉...");
	}

	@Override
	public void addCondiments() {
		System.out.println("加檸檬...");
	}
}

    在上面的代碼中,咱們所作的第一件事就是把原來brewCoffeeGrinds()方法和steepTeaBag()方法進行了抽象,接着把addSugarAndMilk()方法和addLemon()方法也進行了抽象。接下來將沖泡的步驟調用封裝到了基類中的prepareRecipe()方法裏,最後重寫了Coffee對象和Tea對象。下面進行測試:設計

public class Client {
	public static void main(String[] args) {
		// 泡一杯茶
		Tea tea = new Tea();
		tea.prepareRecipe();
		
		System.out.println("\n");
		
		// 衝一杯咖啡
		Coffee coffee = new Coffee();
		coffee.prepareRecipe();
	}
}

    測試成功,如今沖泡的步驟調用由CaffeineBeverage類主導,它擁有算法,並且保護這個算法。對子類來講,CaffeineBeverage類的存在能夠將代碼的複用最大化。算法只存在於一個地方,因此容易修改。相同的算法有基類實現,不一樣的算法由子類提供完整的實現。

定義模板方法模式

    這個方法將算法定義成一組步驟,其中的任何步驟均可以是抽象的,由子類負責實現。這能夠確保算法的結構保持不變,同時由子類提供部分實現。

使用鉤子

    鉤子是一種被聲明在抽象類中的方法,但只有空的或者默認的實現。鉤子的存在,可讓子類有能力對算法的不一樣點進行掛鉤。要不要掛鉤由子類決定。

    根據上面的業務來舉例,在是否添加調料時對客戶進行詢問,若是客戶須要在添加調料。

    在上圖中,基類加入了是否添加調料的鉤子方法,在添加調料之間調用,算法仍是由prepareRecipe()方法管理,Coffee類重寫了鉤子函數,經過命令行向用戶確認是否要添加調料,回覆y/n。而茶類沒有重寫鉤子函數,則調用缺省函數,調用ture,每次都添加調料。下面進行測試:

public class Client {
	public static void main(String[] args) {
		// 泡一杯茶
		Tea tea = new Tea();
		tea.prepareRecipe();
		
		System.out.println("\n");
		
		// 衝一杯咖啡
		Coffee coffee = new Coffee();
		coffee.prepareRecipe();
		
		System.out.println("\n");
		
		// 再衝一杯咖啡
		Coffee coffee2 = new Coffee();
		coffee2.prepareRecipe();
	}
}

好萊塢原則

別調用咱們,咱們會調用你。

    好萊塢原則能夠給咱們一種防止「依賴腐敗」 的方法。在好萊塢原則之下,咱們容許低層組件將本身掛鉤到系統上,可是高層組件會決定何時和怎麼使用這些低層組件。換句話說,高層組件對待低層組件的方式是「別調用咱們,咱們會調用你」。

相關文章
相關標籤/搜索