基于C++的《Head First设计模式》笔记——模版方法模式

目录

一.专栏简介

二.是时候来更多的咖啡因了

三.快速来一些咖啡和茶的类

四.我们来抽象Coffee和Tea

五.进一步设计

六.认识模版方法

七.测试我们的代码并说明

八.定义模版方法模式

九.钩子

十.好莱坞原则

十一.好莱坞原则和模版方法

十二.总结


一.专栏简介

本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。

本章将开始模版方法模式的学习,还是以书中的星巴兹咖啡订单系统为例子。

二.是时候来更多的咖啡因了

茶和咖啡的制作方式非常类似:

三.快速来一些咖啡和茶的类

我们来编写一些创建咖啡和茶的代码:

templateMethod.h:

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

class Coffee
{
public:
	void prepareRecipe()
	{
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}

	void boilWater()
	{
		cout << "Boiling water" << endl;
	}

	void brewCoffeeGrinds()
	{
		cout << "Dripping Coffee through filter" << endl;
	}

	void pourInCup()
	{
		cout << "Pouring into cup" << endl;
	}

	void addSugarAndMilk()
	{
		cout << "Adding Sugar and Milk" << endl;
	}
};

class Tea
{
public:
	void prepareRecipe()
	{
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}

	void boilWater()
	{
		cout << "Boiling water" << endl;
	}

	void steepTeaBag()
	{
		cout << "Steeping the tea" << endl;
	}

	void pourInCup()
	{
		cout << "Pouring into cup" << endl;
	}

	void addLemon()
	{
		cout << "Adding Lemon" << endl;
	}
};

创建Coffee和Tea的类有代码重复,是一个我们需要清理设计的迹象。在这里,看起来我们应该把共性抽象到一个基类,因为咖啡和茶如此相似。

四.我们来抽象Coffee和Tea

抽象共性到基类的代码如下:

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

class CaffeineBeverage
{
public:
	virtual void prepareRecipe() = 0;

	void boilWater()
	{
		cout << "Boiling water" << endl;
	}

	void pourInCup()
	{
		cout << "Pouring into cup" << endl;
	}
};

class Coffee : public CaffeineBeverage
{
public:
	void prepareRecipe() override
	{
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}

	void brewCoffeeGrinds()
	{
		cout << "Dripping Coffee through filter" << endl;
	}

	void addSugarAndMilk()
	{
		cout << "Adding Sugar and Milk" << endl;
	}
};

class Tea : public CaffeineBeverage
{
public:
	void prepareRecipe() override
	{
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}

	void steepTeaBag()
	{
		cout << "Steeping the tea" << endl;
	}

	void addLemon()
	{
		cout << "Adding Lemon" << endl;
	}
};

类图如下:

我们这次的改进可以,但忽略了一些Coffee和Tea的共性。

五.进一步设计

我们再看看咖啡和茶的冲泡法,发现两份冲泡法都遵循相同的算法:

那么,我们也能想办法抽象prepareRecipe(),代码如下:

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

class CaffeineBeverage
{
public:
	void prepareRecipe()
	{
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}

	void boilWater()
	{
		cout << "Boiling water" << endl;
	}

	virtual void brew() = 0;

	void pourInCup()
	{
		cout << "Pouring into cup" << endl;
	}

	virtual void addCondiments() = 0;
};

class Coffee : public CaffeineBeverage
{
public:
	void brew() override
	{
		cout << "Dripping Coffee through filter" << endl;
	}

	void addCondiments() override
	{
		cout << "Adding Sugar and Milk" << endl;
	}
};

class Tea : public CaffeineBeverage
{
public:
	void brew() override
	{
		cout << "Steeping the tea" << endl;
	}

	void addCondiments() override
	{
		cout << "Adding Lemon" << endl;
	}
};

类图如下:

上面的类我们做了什么:

六.认识模版方法

我们实现了模版方法模式。模版方法模式是什么,我们来看看CaffeineBeverage类的结构,它包含实际的"模版方法":

模版方法定义一个算法的步骤,允许子类提供一个或多个步骤的实现。

七.测试我们的代码并说明

测试代码

cpp 复制代码
#include "templateMethod.h"

int main()
{
	Tea myTea;
	myTea.prepareRecipe();

	return 0;
}

运行结果

现在我们来一步步泡茶并追踪模版方法如何运作。我们会看到模版方法控制算法。在算法的某一点,它让子类提供步骤的实现......

1.首先我们实例化一个Tea对象

cpp 复制代码
Tea myTea;

2.然后我们调用模版方法

cpp 复制代码
myTea.prepareRecipe();

3.首先我们烧水

cpp 复制代码
boilWater();

这发生在CaffeineBeverage中。

4.接下来,我们需要泡茶,只有子类知道如何做

cpp 复制代码
brew();

5.现在我们把茶倒进杯子;这一点所有饮料都一样,因此它发生在CaffeineBeverage中

cpp 复制代码
pourInCup();

6.最后,我们加调料,这对每种饮料来说是特定的,因此子类实现这一点

cpp 复制代码
addCondiments();

八.定义模版方法模式

模版方法模式在一个方法中定义一个算法的骨架,而把一些步骤延迟到子类。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

这个模式是用来创建一个算法的模版。什么是模版?正如我们已经看到的,它就是一个方法,更具体地说,它是一个把算法定义为一组步骤的方法。这些步骤中的一个或多个被定义为抽象的,由子类实现。这保证了算法的结构保持不变,而子类提供某些部分的实现。

类图如下:

九.钩子

钩子是一个声明在抽象类中的方法,但它只给出空的或缺省的实现。这给了子类在变化点"挂钩进"算法的能力,如果需要,子类也有权忽略钩子。

下面是一种使用钩子的例子,代码如下:

templateMethod.h:

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

class CaffeineBeverage
{
public:
	void prepareRecipe()
	{
		boilWater();
		brew();
		pourInCup();
		if(customerWantsCondiments()) addCondiments();
	}

	void boilWater()
	{
		cout << "Boiling water" << endl;
	}

	virtual void brew() = 0;

	void pourInCup()
	{
		cout << "Pouring into cup" << endl;
	}

	virtual void addCondiments() = 0;

	virtual bool customerWantsCondiments()
	{
		return true;
	}
};

class Coffee : public CaffeineBeverage
{
public:
	void brew() override
	{
		cout << "Dripping Coffee through filter" << endl;
	}

	void addCondiments() override
	{
		cout << "Adding Sugar and Milk" << endl;
	}
};

class Tea : public CaffeineBeverage
{
public:
	void brew() override
	{
		cout << "Steeping the tea" << endl;
	}

	void addCondiments() override
	{
		cout << "Adding Lemon" << endl;
	}

	bool customerWantsCondiments() override
	{
		string answer;
		cout << "是否需要加调料(yes:加,no:不加):";
		cin >> answer;
		if (answer == "yes") return true;
		else return false;
	}
};

main.cpp:

cpp 复制代码
#include "templateMethod.h"

int main()
{
	Tea myTea;
	myTea.prepareRecipe();

	return 0;
}

运行结果如下:

或者

当算法的某部分可选时,使用钩子。

十.好莱坞原则

我们得到了另一个设计原则,称为好莱坞原则:

好莱坞HR恐怕经常这样说话哈哈哈。

好莱坞原则给我们一种防止"依赖腐烂"的方法。"依赖腐烂"的例子如高层组件依赖于低层组件,低层组件又依赖于高层组件,而高层组件又依赖于横向组件,横向组件又依赖于低层组件等。当腐烂蔓延时,没有人能轻易理解系统是怎么设计的。

有了好莱坞原则,我们允许低层组件把自己挂钩进系统,但由高层组件决定何时需要它们,以及怎样需要它们。换句话说,高层组件对低层组件的态度是"不要打电话给我,我们会打电话给你。"

十一.好莱坞原则和模版方法

好莱坞原则和模版方法模式之间的关系可能还是相当明显的:当我们用模版方法模式设计时,我们在告诉子类,"不要打电话给我,我们会打电话给你。"怎么做?我们再看看CaffeineBeverage的设计:

超类主导演出,当它们需要的时候,会调用子类的方法,这就跟好莱坞一样。

我们可以轻易得知工厂方法和观察者也采用了好莱坞原则。

十二.总结

工厂方法模式是模版方法的一个特例。在这个特例中,原语操作用来创建并返回对象。

下面是一些总结:

  • 模版方法定义了算法的步骤,把这些步骤的实现延迟到子类。
  • 模版方法模式为我们提供了一种代码复用的重要技巧。
  • 模版方法的抽象类可以定义具体方法、纯虚方法和钩子(虚方法)。
  • 纯虚方法由子类实现。
  • 钩子是一种虚方法,它在抽象类中不做事或者只做缺省的事情,但子类可以覆盖它。
  • 好莱坞原则告诉我们,把决策权放在高层模块中,以便决定如何以及何时调用低层模块。
  • 策略模式和模版方法模式都封装算法,前者通过组合,后者通过继承。
  • 工厂方法是模版方法的一个特例。
相关推荐
安小牛2 小时前
Apache License 2.0的中文介绍及其许可使用
笔记·apache
航Hang*2 小时前
Photoshop 图形与图像处理技术——第9章:实践训练4——图层和蒙版
图像处理·笔记·ui·photoshop·期末·复习
摇滚侠3 小时前
尚硅谷 Java 零基础全套视频教程,System、Runtime、BigDecimal、BigInteger、Random,笔记 151
java·开发语言·笔记
hetao17338373 小时前
2026-01-14~15 hetao1733837 的刷题笔记
c++·笔记·算法
hetao17338373 小时前
2026-01-12~01-13 hetao1733837 的刷题笔记
c++·笔记·算法
Yu_Lijing3 小时前
基于C++的《Head First设计模式》笔记——外观模式
c++·笔记·设计模式
代码游侠3 小时前
学习笔笔记——ARM 嵌入式系统与内核架构
arm开发·笔记·嵌入式硬件·学习·架构
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [driver][base]container
linux·笔记·学习
June bug4 小时前
【实习笔记】配置Hosts
笔记