来看看某咖啡厅里,茶和咖啡的冲泡方式。具体以下: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(); } }
别调用咱们,咱们会调用你。
好莱坞原则能够给咱们一种防止“依赖腐败” 的方法。在好莱坞原则之下,咱们容许低层组件将本身挂钩到系统上,可是高层组件会决定何时和怎么使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用咱们,咱们会调用你”。