模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
目录
1. 问题引入;
2. 第一版解决问题;
3. 第二版解决问题;
4. 模板方法初窥;
5. 定义模板方法;
6. 好莱坞原则;
7. 实际中的模板方法;
8. 总结。
1. 问题引入
咖啡和茶问题
我们扮演“师傅”,写一些代码来创建咖啡和茶:
咖啡
茶
2. 第一版解决问题;
上面两种类的实现,出现了重复代码,我们重新编写一种解决方法。
这种方法解决了一部分问题,但是我们是不是忽略了某些共同点呢?
3. 第二版解决问题;
我们可以将prepareRecipe()也抽象化,这样能节省更多的代码。
如此,我们就解决了上面的问题。
4. 模板方法初窥;
我刚刚实现的就是模板方法模式,让我们具体看看。
所以,模板方法定义了一个算法的步骤,允许子类为一个或多个步骤提供实现。
上面的算法相较于原始的实现由很多优点:
a. 由CaffeeBeverage类主导一切,它拥有算法,而且保护这个算法;
b. 对子类来说,CaffeeBeverage类的存在可以将代码的复用最大化;
c. 算法只存在于一个地方,所以容易修改;
d. 这个模板方法提供了一个框架,可以让其他的子类饮料插进来;
e. 父类专注在算法本身,而由子类提供完整的实现。
5. 定义模板方法;
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
类图如下
再进一步
更进一步
上图中,hook是“钩子”。
钩子:一个声明在抽象类中的方法,可以为空或者提供默认实现。
作用:让子类有能力对算法的不同点进行挂钩,是否挂钩,由子类自行决定。
我们来看看一个简单的钩子实现:
为了使用钩子,我们在子类中覆盖它,钩子控制了咖啡因饮料是否执行某部分算法。
问题:当创建一个模板方法时,怎么才知道什么时候该使用抽象方法,什么时候用钩子?
当子类必须提供某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。
问题:钩子的目的?
a. 让子类实现算法中可选的部分;
b. 让子类能够有机会对模板方法中某些即将发生的步骤作出反应。
问题:似乎抽象方法数目越少越少,否则在子类中实现这些方法将会很麻烦?
对的,我们应该让算法步骤不要切割得太细,也不要切割得太粗,会比较没有弹性。也请记住,某些步骤是可选的,可以将这些步骤实现成钩子,而不是实现抽象方法,可以让抽象类的子类的负荷减轻。
6. 好莱坞原则;
设计原则:别调用我们,我们会调用你。
问题
在好莱坞原则下,我们会允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件,换句话说,高层组件对底层组件说:别调用我们,我们会调用你。
看看刚才的模板方法:
7. 实际中的模板方法;
我们看看排序函数中的模板方法
看见了吗,mergeSort就是一个模板方法,元素实现compareTo方法就可以排序了
问题:这真的是一个模板方法吗,还是你的想象力太丰富了?
这个模式的特点在于提供一个算法,并让子类实现某些步骤而数组的排序算法很明显的并非如此!但是我们知道荒野模式,并非总是如教科书例子一般的中规中矩,为了符合当前的环境和实现的约束,它们总要被适当的修改。这个Array类sort方法设计者受到一些约束,通常我们不能设计一个类,继承java数组,而sort方法希望能适用于所有的数组,所以它们定义了一个静态方法,而由被排序的对象的元素自行提供比较大小的算法部分,虽然不是教科书上的模板方法,但是实现时符合模板精神的,再者,由于不需要继承就可以使用这个算法,这样使得排序更有弹性,更有用。
问题:排序的实现实际上看起来更像是策略模式,而不是模板方法???
策略模式使用对象组合,我们这里正好使用数组对象排序我们的数组,这部分和策略模式非常相似,但是记住,策略模式中,组合的类实现了整个算法,而数组所实现的排序算法并不完整,它需要一个类填补compareto方法的实现,因此更像模板方法。
另外两种例子:
8. 总结。