來看看某咖啡廳裏,茶和咖啡的沖泡方式。具體以下:java
咖啡的沖泡法:算法
茶的沖泡法:ide
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
那麼,咱們也將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(); } }
別調用咱們,咱們會調用你。
好萊塢原則能夠給咱們一種防止「依賴腐敗」 的方法。在好萊塢原則之下,咱們容許低層組件將本身掛鉤到系統上,可是高層組件會決定何時和怎麼使用這些低層組件。換句話說,高層組件對待低層組件的方式是「別調用咱們,咱們會調用你」。