概念
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
适用场景
1 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
2 将各子类中公共的行为提取出来并集中到一个公共父类中以避免代码重复。
3 控制子类扩展。模板方法只在特定点调用"hook"操作,这样就只允许在这些点进行扩展。
结构
优缺点
优点
1 抽象类(父类)主导一切,它拥有算法,而且保护这个算法。
2 算法只存在于一个地方,所以容易修改
3 抽象基类专注在算法本身,而由子类提供完整的实现
4 模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为
5 模板方法模式导致一种反向的控制结构,这种结构有时被称为“好莱坞原则”,即“别打电话给我们,我们会打电话给你”,通过一个父类调用其子类的操作(而不是相反的子类调用父类)
缺点
每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,系统也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。
模板方法模式中的钩子(hook)
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类决定。
例如
#include <iostream>
class CaffeineBeverageWithHook
{
public:
void prepareRecipe()
{
boilWater();
brew();
pourIncup();
// 加上了一个小小的条件语句,而该条件是否成立,
// 是由一个具体方法customerWantsCondiments决定的
if(customerWantsCondiments())
{
addCondiments();
}
}
virtual void brew() = 0;
void addCondiments() = 0;
void boilWater()
{
std::cout << "Boiling water" << std::endl;
}
void pourInCup()
{
std::cout << "Pouring into cup" << std::endl;
}
// 这就是一个钩子,子类可以覆盖这个方法,但不见得一定要这么做
virtual bool customerWantsCondiments()
{
return true;
}
}
使用钩子的目的:
1 钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。
2 让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤做出反应。比方说,名为justReOrderedList()的钩子方法允许子类在内部列表重新组织后执行某些动作(例如在屏幕上显示数据)。
3 钩子也可以让子类有能力为其抽象类做一些决定
模板方法模式与策略模式的对比
1 策略模式的目的是封装算法,它定义一个算法家族,并让这些算法可以互换,客户可以轻易的使用不同的算法。而模板方法是要定义一个算法的大纲,而由子类定义其中某些步骤的内容。这么一来, 在算法中的个别步骤可以有不同的实现细节,但是算法的结构依然维持不变。
2 策略模式使用组合,而模板方法模式使用继承。由于使用继承,模板方法模式对算法有更多的控制权,而且不会重复代码。事实上,除了极少的一部分之外,模板方法模式的算法的每一个部分都是相同的,所以模板方法模式比策略模式更有效率。而策略模式使用对象组合,更有弹性。利用策略模式,客户可以在运行时改变他们的算法,而客户所需要做的,只是改用不同的策略对象罢了。