设计模式的秘密武器:深入理解模板方法模式的设计原理与实战技巧

举报
Lion Long 发表于 2023/10/18 21:59:25 2023/10/18
【摘要】 揭示设计模式中模板方法模式的核心原理和实用技巧的文章。模板方法模式是一种行为设计模式,它通过定义一个算法的骨架,将一些步骤的具体实现延迟到子类中。本文将深入探讨该模式的设计原理,以及如何在实际项目中应用该模式来提高代码的可维护性、扩展性和复用性。通过学习本文,您将了解模板方法模式的内部工作原理,并学会如何使用该模式解决实际开发中的问题。

一、设计模式的定义

经典设计模式大概有23种。
设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。
从定义可以看出,设计模式的使用有很多的局限性。一定要明确它解决什么问题,再使用它。当不清楚设计模式解决什么问题时不要轻易使用。

通俗的讲,设计模式是解决软件开发过程中一些问题的固定套路。不要过度的封装或使用设计模式,除非明确了需求的具体变化方向,而且变化方向的点是反复的出现,才会使用设计模式;即慎用设计模式。

设计模式要到达一定的工程代码量才能精通。但是,了解设计模式是需要的。

二、模板方法模式详解

我们通过一个例子,一步步的演化出一个设计模式。

2.1、背景----柠檬茶和咖啡的的冲泡方式

以柠檬茶和咖啡的的冲泡方式为例子,它们的冲泡方式非常类似。
咖啡冲泡方式:
(1)把水煮沸;
(2)把咖啡倒进杯子;
(3)用沸水冲泡咖啡;
(4)加糖和牛奶。
柠檬茶冲泡方法:
(1)把水煮沸;
(2)把茶叶倒进杯子;
(3)用沸水冲泡茶;
(4)加柠檬。

2.2、代码实现咖啡和茶冲泡的类

class Coffee {
public:
	void prepare_recipe(){
		boil_water();
		brew_coffee_grinds();
		pour_in_cup();
		add_sugar_and_milk();
	}
private:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	void brew_coffee_grinds() {
		cout << "Dripping Coffee through filter." << endl;
	}
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	void add_sugar_and_milk() {
		cout << "Adding and sugar and milk." << endl;
	}
};

class Tea {
public:
	void prepare_recipe() {
		boil_water();
		steep_tea_bag();
		pour_in_cup();
		add_lemon();
	}
private:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	void steep_tea_bag() {
		cout << "Steeping the tea." << endl;
	}
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	void add_lemon() {
		cout << "Adding lemon." << endl;
	}
};

可以看到:
(1)prepare_recipe()中每个步骤都实现在分离的方法中。
(2)每个方法都实现了算法中的一个步骤。
(3)Coffe类和Tea类的boil_water()和pour_in_cup()是完全一样的,出现了重复代码。
(4)除了boil_water()和pour_in_cup()以为,其他方法是该类的专有方法。
(5)Coffe类和Tea类的实现很像,基本上相同的流程prepare_recipe()。

2.3、整理相似点

前面的代码实现可以看到出现了重复代码,这个现象使我们想要进一步理清一下设计。在例子中,既然Coffe和Tea如此相似,似乎可以将公共部分提取出来,放入一个基类中。

(1)boil_water()和pour_in_cup()被子类所共享,所以被定义在这个超类中。
(2)prepare_recipe()在每个类中都不一样,所以定义为抽象方法。
(3)每个子类都实现自己的冲泡方法。
(4)每个子类都重写prepare_recipe()方法,并实现自己的冲泡方法。

2.4、进一步设计

两个冲泡方法采用的算法可以理解为:
(1)把水煮沸;
(2)把饮料倒进杯子;
(3)用沸水冲泡咖啡或茶;
(4)在饮料中加入适当的调料。

只是应用在不同的饮料上,那么可以抽象prepare_recipe()。
(1)Coffe的brew_coffee_grinds()、add_sugar_and_milk()分别与Tea类的steep_tea_bag()、add_lemon()差异不大,所以可以给它们新的方法名称brew()和add_condiments()。泡茶或泡咖啡都用brew()方法;加糖、牛奶、柠檬都用add_condiments()方法,都是往饮料中加调料。新的prepare_recipe()方法就成了这样:

void prepare_recipe() {
	boil_water();
	brew();
	pour_in_cup();
	add_condiments();
}

(2)有了新的prepare_recipe()方法,但需要它能够符合代码。建立一个超类,并且设置prepare_recipe()方法不能被重写,因为不希望子类重写这个方法;将brew()和add_condiments定义为虚函数,交由不同子类去重写。

class CoffeineBeverage {
public:
	void prepare_recipe() {
		boil_water();
		brew();
		pour_in_cup();
		add_condiments();
	}
private:
	virtual void brew() {
		cout << "Dripping something through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding something." << endl;
	}
protected:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	
};

(3)最后,处理咖啡和茶类。这两个类都是依赖超类CoffeineBeverage来处理冲泡方法,所以需要重写brew()和add_condiments()方法。

class Coffee : public CoffeineBeverage{
private:
	virtual void brew() {
		cout << "Dripping coffe through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding sugar and mile." << endl;
	}
};

class Tea : public CoffeineBeverage {
private:
	virtual void brew() {
		cout << "Dripping tea through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding lemon." << endl;
	}
};

2.5、小结一下

咖啡冲泡方式:
(1)把水煮沸;
(2)把咖啡倒进杯子;
(3)用沸水冲泡咖啡;
(4)加糖和牛奶。
柠檬茶冲泡方法:
(1)把水煮沸;
(2)把茶叶倒进杯子;
(3)用沸水冲泡茶;
(4)加柠檬。

我们明白两种冲泡方法基本相同,只是一些步骤需要不同的实现,所以泛化冲泡方法,把它放到基类。

(1)把水煮沸;
(2)把饮料倒进杯子;
(3)用沸水冲泡咖啡或茶;
(4)在饮料中加入适当的调料。

子类依赖基类,对有差异的方法进行重写。
基本上,我们刚刚就实现了一个模板方法模式。

三、认识模板方法

class CoffeineBeverage {
public:
	void prepare_recipe() {
		boil_water();
		brew();
		pour_in_cup();
		add_condiments();
	}
	
private:
	virtual void brew() {
	}

	virtual void add_condiments() {
	}
	
protected:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	
};

prepare_recipe()是我们的模板方法。原因是:
(1)它是一个方法;
(2)它用作一个算法的模板。在例子中就是用来制作咖啡因饮料的。

在这个模板中,算法内的每一个步骤都被一个算法代表了。一些方法由超类处理,一些方法由子类处理;需要由子类处理的方法需要声明为virtual。

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

模板方法模式的定义:

一个方法定义一个算法的骨架 ,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

这个模式是用来创建一个算法的模板,所谓模板就是一个方法。更具体的算,这个方法将算法定义成一组步骤,其中的任何步骤都可以抽象化,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。

3.1、测试一下

// Template method
#include <iostream>
using namespace std;


class CoffeineBeverage {
public:
	void prepare_recipe() {
		boil_water();
		brew();
		pour_in_cup();
		add_condiments();
	}
private:
	virtual void brew() {
		cout << "Dripping something through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding something." << endl;
	}
protected:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	

};

class Coffee : public CoffeineBeverage{
private:
	virtual void brew() {
		cout << "Dripping coffe through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding sugar and mile." << endl;
	}
};

class Tea : public CoffeineBeverage {
private:
	virtual void brew() {
		cout << "Dripping tea through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding lemon." << endl;
	}
};


/************************* TEST ******************************/
int main(int argc, char **argv)
{
	CoffeineBeverage *cb = new Coffee;
	CoffeineBeverage *cb2 = new Tea;

	cout << "Coffee:" << endl;
	cb->prepare_recipe();
	cout << "Tea:" << endl;
	cb2->prepare_recipe();

	return 0;
}

输出结果:

Coffee:
Boiling water.
Dripping coffe through filter.
pouring into cup.
Adding sugar and mile.
Tea:
Boiling water.
Dripping tea through filter.
pouring into cup.
Adding lemon.

3.2、模板方法模式的优点

比较没有使用模板方法和使用了模板方法:

模板方法
Coffee和Tea主导一切,它们控制了算法 CoffeineBeverage主导一切,它拥有算法并且保护这个算法
Coffee和Tea之间存在重复代码 对于子类来说,CoffeineBeverage的存在可以将代码的复用最大化
对算法代码修改,需要打开子类修改很多地方 算法只存在一个地方,所以很容易修改。
类的组织方式不具有弹性,加入新类需要做很多工作 模板方法提供了一个框架,加入的新子类只需要实现自己的方法即可
算法的知识和它的实现会分散在很多类中 CoffeineBeverage类专注算法本身,而子类提供完整的实现

3.3、模板方法的钩子

这里的“钩子”是说什么事情也不做的方法。子类可以视情况决定要不要重写它。

class CoffeineBeverage {
public:
	void prepare_recipe() {
		boil_water();
		brew();
		pour_in_cup();
		add_condiments();
		hook();
	}
	
private:
	// 子类实现
	virtual void brew() {
	// ...
	}
	// 子类实现
	virtual void add_condiments() {
	// ...
	}
	// 这是一个钩子,它什么也不做
	virtual void hook(){
	}
	
protected:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	
};

钩子是一种被声明在抽象类中的方法,当只有空或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,要不要挂钩由子类决定。
看一个例子,用户自己决定要不要加调料:

class CoffeineBeverage {
public:
	void prepare_recipe() {
		boil_water();
		brew();
		pour_in_cup();
		if(customer_wants_condiments())
			add_condiments();
	}
	
private:
	// 子类实现
	virtual void brew() {
	// ...
	}
	// 子类实现
	virtual void add_condiments() {
	// ...
	}
	// 这是一个钩子,它什么也不做
	virtual bool customer_wants_condiments(){
		return true;
	}
	
protected:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	
};

默认是加调料,如果不加就重写钩子的方法。

使用钩子:

// Template method
#include <iostream>
using namespace std;


class CoffeineBeverage {
public:
	void prepare_recipe() {
		boil_water();
		brew();
		pour_in_cup();
		if (customer_wants_condiments())
			add_condiments();
	}
private:
	virtual void brew() {
		cout << "Dripping something through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding something." << endl;
	}

	virtual bool customer_wants_condiments(){
		return true;
	}
protected:
	void boil_water() {
		cout << "Boiling water." << endl;
	}
	
	void pour_in_cup() {
		cout << "pouring into cup." << endl;
	}
	

};

class Coffee : public CoffeineBeverage{
private:
	virtual void brew() {
		cout << "Dripping coffe through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding sugar and mile." << endl;
	}
};

class Tea : public CoffeineBeverage {
private:
	virtual void brew() {
		cout << "Dripping tea through filter." << endl;
	}

	virtual void add_condiments() {
		cout << "Adding lemon." << endl;
	}
	virtual bool customer_wants_condiments() {
		cout << "I don't want lemons." << endl;
		return false;
	}
};


/************************* TEST ******************************/
int main(int argc, char **argv)
{
	CoffeineBeverage *cb = new Coffee;
	CoffeineBeverage *cb2 = new Tea;

	cout << "Coffee:" << endl;
	cb->prepare_recipe();
	cout << "Tea:" << endl;
	
	cb2->prepare_recipe();

	return 0;
}

总结

模板方法模式 有骨架接口,子流程通过virtual关键字暴露给子类重写,调用时晚绑定;即 子类可以重写父类的子流程,使父类流程丰富;本质是通过固定骨架约束子类的行为。这是最常用的设计模式。

模板方法模式是一个非常常见的模式,到处都是。

欢迎关注公众号《Lion 莱恩呀》学习技术,每日推送文章。

在这里插入图片描述


【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。