问题引入:
- 咖啡冲泡过程:
1、把水煮沸;
2、用沸水冲泡咖啡;
3、倒进杯子;
4、加咖啡和牛奶;
- 茶冲泡过程:
1、把水煮沸;
2、用沸水浸泡茶叶;
3、倒进杯子;
4、加柠檬;
如果根据茶和咖啡各提供一个类然后提供冲泡的方法,会发现这两个类有很多重复的代码,而这个重复是体现在步骤1和3上。
解决方案:
抽取一个抽象父类,抽离出两个类一样的方法:冲泡和倒进杯子。不同的方法由具体子类实现。
public abstract class CaffeineBeverage { final void prepareRecipe() { //1、把水煮沸 boilWater(); //2、用沸水冲泡咖啡/或者是茶 brew(); //3、倒入杯中 pourInCup(); //4、加配料 addCondiments(); } private void boilWater() { //具体实现 } protected abstract void brew(); private void pourInCup() { //具体实现 } protected abstract void addCondiments();}
模板方法模式定义:
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板就是一个方法。更具体的说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。
对模板方法进行挂钩
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
public abstract class CaffeineBeverageWithHook { final void prepareRecipe() { //1、把水煮沸 boilWater(); //2、用沸水冲泡咖啡/或者是茶 brew(); //3、倒入杯中 pourInCup(); //是否加配料可以由子类决定 if(customerWantsCondiments()) { //4、加配料 addCondiments(); } } /** * 子类可以决定是否覆盖这个方法,子类可以选择不覆盖,不覆盖就默认加配料。 * @return */ private boolean customerWantsCondiments() { return true; } private void boilWater() { //具体实现 } protected abstract void brew(); private void pourInCup() { //具体实现 } protected abstract void addCondiments();}
当创建一个模板方法时,什么时候使用抽象方法,什么时候使用钩子?
当你的子类必须提供算法某个方法或者步骤的实现时,就是用抽象方法。如果算法的这个部分是可选的,就用钩子。
好莱坞原则:
别调用我们,我们会调用你。
低层组件参与计算,但高层组件控制何时以及如何让低层组件参与,低层组件绝对不可以调用高层组件。
工厂方法、策略模式、模板方法的比较:
工厂方法是模板方法的一种特殊版本,工厂方法是讲bean的实例化延迟到子类。而模板方法是将算法的步骤延迟到子类,都是用的是继承,但是目的不一样,我们可以称工厂方法是模板方法的特殊版本。
策略模式和模板方法模式都封装算法,但一个利用的是组合,一个利用的是继承,类似于策略模式中提到的jdk的comparable接口,应该归为模板方法,因为它体现的是继承。