模板方法模式

  1. 1. 从一般开始
  2. 2. 进一步复用
  3. 3. 模板方法模式
  4. 4. 钩子
  5. 5. 设计原则
  6. 6. 策略模式与模板方法模式

从一般开始

星巴霖饮料店今天开业了!!!小店初开,提供咖啡和茶两种饮品,这是我们的独家配方法,
下面是星巴霖咖啡的冲泡法:

  1. 把水煮沸;
  2. 用沸水冲泡咖啡;
  3. 把咖啡倒进杯子;
  4. 加糖和牛奶。

然后是星巴霖茶的冲泡法:

  1. 把水煮沸;
  2. 用沸水浸泡茶叶;
  3. 把茶倒进杯子;
  4. 加柠檬。

下面让我们来实现咖啡和茶,代码见目录SimpleBarista

进一步复用

代码很简单,观察后发现boilWater()pourInCup()是相同的,我们可以将其抽象出来,见类图:

类图

我们已经对茶和咖啡进行了一些抽象了,还有没有其它的东西可以抽象呢?回顾它们的冲泡方法:

  1. 把水煮沸;
  2. 用热水包咖啡或茶;
  3. 把饮料倒进杯子;
  4. 在饮料中加入适当的调料;

那么,现在可以尝试将prepareRecipe()抽象化:

咖啡的prepareRecipe()

1
2
3
4
5
6
void Coffee::prepareRecipe() {
boilWater();
brewCoffeeGrinds();//第2步
pourInCup();
addSugarAndMilk();//第4步
}

茶的prepareRecipe()

1
2
3
4
5
6
void Tea::prepareRecipe() {
boilWater();
steepTeaBag();//第2步
pourInCup();
addLemon();//第4步
}

两者的第2步操作,冲泡和浸泡,差异其实不大,将第2步抽象出来,比如说brew()
。加糖和牛奶与加柠檬本质上都属于加调料,可以抽象成addCondiments()。那么,我们可以将prepareRecipe()
方法修改,现在,新的prepareRecipe()方法如下:

1
2
3
4
5
6
void prepareRecipe() {
boilWater();
brew();//第2步
pourInCup();
addCondiments();//第4步
}

这样一来,prepareRecipe()就成了一个模板方法,因为:

  1. 它是一个方法;
  2. 它是一个模板,因为它的算法是固定的,但是具体的实现是不固定的,比如说,咖啡和茶的冲泡方法就不一样。

代码见目录Barista

模板方法模式

模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟的子类中。模板方法是的子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤;

模板方法的主要在于需要进行的抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AbstractClass {   //这是一个抽象类,被用作基类,子类需要实现其操作
public:
virtual void templateMethod() final { //模板方法,被声明为final,防止子类覆盖
//模板方法定义了一连步骤,每个步骤由一个方法代表
primitiveOperation1();
primitiveOperation2();
concreteOperation();
}
//子类需要有自己的实现
virtual void primitiveOperation1() = 0;
virtual void primitiveOperation2() = 0;
//在子类中相同的代码
void concreteOperation() {
//do something
}
};

钩子

钩子是一种被声明在基类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

当顾客点咖啡的时候,我们如何得知顾客是否想要加调料呢?

答案就是,开口问!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CaffeineBeverageWithHook {
public:
void prepareRecipe() {
//同上
if (customerWantsCondiments()) {
addCondiments();
}
}

virtual ~CaffeineBeverageWithHook() = default;
virtual void brew() = 0;
virtual 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. 好莱坞原则

    别调用我们,我们会调用你。这是一种反转控制,也就是说,高层组件对待底层组件的方式是“别调用我们,我们会调用你”。

策略模式与模板方法模式

策略模式和模板方法模式都封装算法,一个用组合,一个用继承。两者的区别在于:

  1. 策略模式:封装可互换的行为,然后使用委托来决定使用哪一个行为;
  2. 模板方法模式:封装算法的骨架,而将一些步骤延迟到子类中;

可以延迟到子类中实现的步骤,视为可互换的行为(比如brew()),这样就又可以采用策略模式对不同的步骤进行封装,便于灵活替换;

完整示例代码详见:代码链接