模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
场景,对于咖啡来说,我们需要把水煮沸,然后用沸水冲泡咖啡,再把咖啡倒入杯子中,最后加入糖和牛奶,对于茶而言,也是先把水煮沸,用沸水去浸泡茶叶,在把茶倒入杯子中,最后加上柠檬。我们可以看这两种方法其实都采用了相同算法,1:把水煮沸 2:用沸水泡咖啡或茶 3:把咖啡因饮料倒入杯子中 4:在杯子中加入相应调料。现在我们用代码去实现看看
首先先创建一个咖啡因饮料的抽象类
//定义一个咖啡因饮料的抽象类 public abstract class CaffeineBeverage { //浸泡茶和冲泡咖啡其实差不多,我们这就用brew方法来表示 //声明为final是因为我们不希望子类覆盖这算法型的方法。我们将步骤2和4泛化成方法brew和addCondiments。 //这里prepareRecipe是我们的模板方法(1:这是一个方法,2:它用作一个方法的模板,这里是制作咖啡因饮料的),在这个模板中,算法内每一个步骤都被一个方法代表了,其中某些方法在这个类中处理,某些需要子类实现 final void prepareRecipe(){ boilWater(); brew(); pourInCup(); addCondiments(); } abstract void brew(); abstract void addCondiments(); void boilWater(){ System.out.println("1:把水煮沸开来"); } void pourInCup(){ System.out.println("3:到入杯子中"); } }
然后创建茶和咖啡类去实现这个抽象类
//茶继承咖啡因饮料这抽象类,并实现自己的冲泡和添加调料的方法 public class Tea extends CaffeineBeverage{ @Override void brew() { System.out.println("2:将茶浸泡在沸水中"); } @Override void addCondiments() { System.out.println("4:加入柠檬"); } } //咖啡继承咖啡因饮料这抽象类,并实现自己的冲泡和添加调料的方法 public class Coffee extends CaffeineBeverage{ @Override void brew() { System.out.println("2:用沸水冲泡咖啡"); } @Override void addCondiments() { System.out.println("4:加入糖和牛奶"); } }
最后我们进行测试
public class Test { public static void main(String[] args) { //测试茶的制作过程 Tea tea = new Tea(); tea.prepareRecipe(); System.out.println("----"); //测试咖啡的制作过程 Coffee coffee = new Coffee(); coffee.prepareRecipe(); } }
运行结果如下:
好了,这就是一个简单的模板方法模式了,我们总结下怎么定义抽象类的,首先抽象类是作为基类的,子类必须实现其操作,然后模板方法被声明了final,以免子类改变这个算法的内容,在模板方法中定义了一连串的步骤,每个步骤有一个方法代表。
最后,在讲下在模板方法中比较常用的“钩子”,什么是模板方法中的“钩子”?其实就是被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩。下面我们将上面场景加入钩子,用钩子来询问客户是否需要加入调料。
抽象类代码如下:
public abstract class CaffeineBeverage { final void prepareRecipe(){ boilWater(); brew(); pourInCup(); if(customerWantsCondiments()){ addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater(){ System.out.println("1:把水煮沸开来"); } void pourInCup(){ System.out.println("3:到入杯子中"); } //这就是钩子,默认是空的实现,这里返回true不做任何事情,子类可以视情况决定要不要覆盖它们 boolean customerWantsCondiments(){ return true; } }
接着定义的两个实现类如下
//茶类,覆盖了使用了钩子方法 public class TeaWithHook extends CaffeineBeverage { @Override void brew() { System.out.println("2:将茶浸泡在沸水中"); } @Override void addCondiments() { System.out.println("4:加入柠檬"); } public boolean customerWantsCondiments(){ String answer = UserInput(); if(answer.toLowerCase().startsWith("y")){ return true; } else{ System.out.println("不加入任何东西"); return false; } } public String UserInput(){ String answer = null; System.out.println("你想在茶中加入柠檬吗?(y/n)"); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try{ answer = reader.readLine(); }catch(IOException e){ System.err.println("输入错误!"); } return answer; } } //咖啡类,覆盖了使用了钩子方法 public class CoffeeWithHook extends CaffeineBeverage { @Override void brew() { System.out.println("2:用沸水冲泡咖啡"); } @Override void addCondiments() { System.out.println("4:加入糖和牛奶"); } public boolean customerWantsCondiments(){ String answer = UserInput(); if(answer.toLowerCase().startsWith("y")){ return true; } else{ System.out.println("不加入任何东西"); return false; } } public String UserInput(){ String answer = null; System.out.println("你想在咖啡中加入糖和牛奶吗?(y/n)"); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try{ answer = reader.readLine(); }catch(IOException e){ System.err.println("输入错误!"); } return answer; } }
最后我们进行钩子的测试类编写
public class Test2 { public static void main(String[] args) { TeaWithHook teaWithHook = new TeaWithHook(); teaWithHook.prepareRecipe(); System.out.println("-----"); CoffeeWithHook coffeeWithHook = new CoffeeWithHook(); coffeeWithHook.prepareRecipe(); } }
运行结果如下
好了,这就是模板方法中简单的钩子使用了。
让我们区别下模板方法、策略、工厂方法模式的区别吧,模板方法是子类决定如何实现算法中的某些步骤,策略模式是封装可互换的行为,然后使用委托来决定采用哪一个行为,工厂方法是由子类决定实例化哪个具体类。
使用原则:好莱坞原则,别调用我们,我们会调用你。在此场景中,高层组件CaffeineBeverage控制冲泡的算法,只有在需要子类实现某个方法时才调用子类,如果Tea和Coffee没有先被调用,绝对不会直接调用抽象类的。
下一节:迭代器模式