基于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的设计:

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

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

十二.总结

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

下面是一些总结:

  • 模版方法定义了算法的步骤,把这些步骤的实现延迟到子类。
  • 模版方法模式为我们提供了一种代码复用的重要技巧。
  • 模版方法的抽象类可以定义具体方法、纯虚方法和钩子(虚方法)。
  • 纯虚方法由子类实现。
  • 钩子是一种虚方法,它在抽象类中不做事或者只做缺省的事情,但子类可以覆盖它。
  • 好莱坞原则告诉我们,把决策权放在高层模块中,以便决定如何以及何时调用低层模块。
  • 策略模式和模版方法模式都封装算法,前者通过组合,后者通过继承。
  • 工厂方法是模版方法的一个特例。
相关推荐
1104.北光c°17 小时前
【黑马点评项目笔记 | 优惠券秒杀篇】构建高并发秒杀系统
java·开发语言·数据库·redis·笔记·spring·nosql
潇冉沐晴17 小时前
div2 1064补题笔记(A~E)
笔记·算法
Jaxson Lin17 小时前
Java编程进阶:智能仿真无人机项目3.0
java·笔记·无人机
中屹指纹浏览器17 小时前
指纹浏览器技术落地实践:多场景适配与性能优化全解析
经验分享·笔记
张人玉17 小时前
WPF 多语言实现完整笔记(.NET 4.7.2)
笔记·.net·wpf·多语言实现·多语言适配
Hammer_Hans17 小时前
DFT笔记28
笔记
忧郁的Mr.Li17 小时前
设计模式--单例模式
javascript·单例模式·设计模式
范纹杉想快点毕业17 小时前
状态机设计模式与嵌入式系统开发完整指南
java·开发语言·网络·数据库·mongodb·设计模式·架构
请你喝好果汁64118 小时前
## 学习笔记:R 语言中比例字符串的数值转换,如GeneRatio中5/100的处理
笔记·学习·r语言
短剑重铸之日18 小时前
《设计模式》第十篇:三大类型之行为型模式
java·后端·设计模式·责任链模式·访问者模式·行为型模式