模板方法模式是在父类中定义一个算法的骨架,而将一些步骤延迟到子类中。使得子类可以在不改变算法结构下,重新定义算法中的某些步骤。
一个简单的例子,泡咖啡的步骤为1.烧开水,2.泡咖啡粉,3.咖啡倒进杯子,4.加糖。泡茶的步骤为1.烧开水,2.泡茶叶,3.茶倒进杯子,4.加调料。可以看到,泡咖啡和泡茶的步骤基本上是一致的,只是有些许不同,也就是骨架是一样的。我们可以把这4步放到父类里的一个方法里,这个方法就是模板方法。1和4是一样的,2和3声明成抽象方法,让子类来具体实现,这就是最简单的模板方法模式。可以把模板方法定义为final,这样子类就修改不了算法的骨架。
说到模板方法模式,这里需要提到hook(钩子)这个概念。所谓的钩子,其实就是父类里的一个方法,一般为空或者默认的实现,子类可以选择用它默认的现实或者覆盖它。例如则则例子里的第四步加调料,我们可以在父类里加一个钩子函数,默认返回true。
Boolean customerWantsCondiments(){
return true;
}
然后在模板方法里调用第4步前加上if(customerWantsCondiments())的判断。这样一来,如果子类不覆盖钩子,就是默认加调料,如果子类想不加调料,这时只需要覆盖这个钩子函数,让它返回false即可。如果你看过一些框架的源码,你会发现用到钩子函数的地方很多很多。这种方式原理很简单,但是作用真的很大,首先它提供了默认的现实,子类在一般情况下完全可以不管,如果需要扩展,也只需要简单的覆盖钩子即可。
实际中遇到模板方法模式的地方很多,但很多都是变种,不是严格遵循上面的概念。比如Java数组的排序。java数组的sort方法里实际调用了mergeSort方法,而这个方法里则调用了compareTo这个比较方法,如果我们想对一个对象数组排序,我们就必须在我们的对象里实现compareTo方法。
模板方法模式和策略模式有点相似,不免容易混淆。它们的相同点是都封装了算法,但是严格来说它们所做的事情并不相同。策略模式是自己封装了所有的算法,而模板方法模式则是父类定义了算法的骨架,而子类则实现其中的一部分。策略模式还有一点是可以在运行时动态改变策略。