设计模式:模板模式(Template)
设计模式:模板模式(Template)
模板模式(Template)属于行为型模式(Behavioral Pattern)的一种。
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
行为型模式分为类行为型模式和对象行为型模式两种:
- 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
- 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
模式动机
在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
模式定义
模板模式(Template)是一种基于继承实现的设计模式,属于行为型模式。
模板模式(Template)将定义的算法抽象成一组步骤,在抽象类种定义算法的骨架,把具体的操作留给子类来实现。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模式结构
模板模式(Template)包含如下角色:
- 抽象模板类(Abstract Template):定义了算法骨架,包含一个或多个抽象方法,这些方法由子类来具体实现。抽象类中通常还包含一个模板方法(Template Method),用来调用抽象方法和具体方法,控制算法的执行顺序;还可以定义钩子方法,用于在算法中进行条件控制。
- 具体模板类(Concrete Template):继承抽象类,实现抽象方法。
这里的“钩子”是说什么事情也不做的方法。子类可以视情况决定要不要重写它。
时序图
略。
模式实现
抽象模板类:
#ifndef _ABSTRACT_TEMPLATE_H_
#define _ABSTRACT_TEMPLATE_H_
#include <iostream>
class AbstractTemplate
{
protected:
// 具体方法
void Step1()
{
std::cout << "step 1, same in every conctrte template." << std::endl;
}
void Step3()
{
std::cout << "step 3, it's up to step 2 to execute it or not." << std::endl;
}
void Step5()
{
std::cout << "step 5, same in every conctrte template." << std::endl;
}
virtual bool Step2() = 0; // 钩子方法,可以重写来做条件控制
virtual void Step4() = 0; // 抽象方法
public:
// 模板方法
void templateMethod()
{
Step1();
if (Step2())
Step3();
for (int i = 0; i < 2; i++)
Step4();
Step5();
}
};
#endif // !_ABSTRACT_TEMPLATE_H_
模板方法 templateMethod 用来调用抽象方法和具体方法,控制算法的执行顺序。
Step1、Step3 和 Step5 都是具体(稳定)方法,在每一个具体模板类中都相同;Step2 是一个钩子方法(纯虚函数),用于在算法中进行条件控制;Step4 是抽象(变化)方法(纯虚函数),由子类来具体实现。
注意,Step2 和 Step4 写成纯虚函数后,必须在子类中重写才可以实例化子类对象,才可以使用 templateMethod 函数。
具体模板 A 类:
#ifndef _CONCRETE_TEMPLATE_A_H_
#define _CONCRETE_TEMPLATE_A_H_
#include "AbstractTemplate.h"
class ConcreteTemplateA : public AbstractTemplate
{
protected:
// 子类重写实现
virtual bool Step2()
{
std::cout << "step 2: judgment pass.\n";
return true;
}
virtual void Step4()
{
std::cout << "step 4, override in conctrte template A." << std::endl;
}
};
#endif // !_CONCRETE_TEMPLATE_A_H_
具体模板 B 类:
#ifndef _CONCRETE_TEMPLATE_B_H_
#define _CONCRETE_TEMPLATE_B_H_
#include "AbstractTemplate.h"
class ConcreteTemplateB : public AbstractTemplate
{
protected:
// 子类重写实现
virtual bool Step2()
{
std::cout << "step 2: judgment fails.\n";
return false;
}
virtual void Step4()
{
std::cout << "step 4, override in conctrte template B." << std::endl;
}
};
#endif // !_CONCRETE_TEMPLATE_A_H_
在单线程环境下的测试
测试程序:
#include <stdlib.h>
#include "ConcreteTemplateA.h"
#include "ConcreteTemplateB.h"
int main()
{
AbstractTemplate* cta = new ConcreteTemplateA();
AbstractTemplate* ctb = new ConcreteTemplateB();
std::cout << "----- Concrete Template A ------\n";
cta->templateMethod();
std::cout << "\n----- Concrete Template B ------\n";
ctb->templateMethod();
delete cta;
delete ctb;
system("pause");
return 0;
}
运行结果:
两个具体模板类都执行了稳定方法 Step1()、Step3()、Step5()。
ConcreteTemplateA 重写了 Step2(),返回 true,于是可以执行 Step3();ConcreteTemplateB 也重写了 Step2(),返回 false,于是不执行 Step3()。
templateMethod() 中规定 Step4() 循环执行 2 次,可以看到两个具体模板类分别执行了自己重写的 Step4()。
在多线程环境下的测试
略。
模式分析
- 模板模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
- 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你” 的反向控制结构是Template Method的典型应用。
- 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。
优缺点
优点:
- 提高代码复用性:将算法的骨架定义在父类中,子类只需要实现具体的细节部分,减少了代码的重复。
- 符合开闭原则:在模板模式中,由父类控制子类的执行,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
- 提高代码可维护性:模板模式定义了一套固定的模板,便于开发人员理解和修改,易于维护。
缺点:
- 由于模板模式制定的是一个固定的结构,所以某些子类可能无法适用,导致无法实现特定的需求或定制。
- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
适用场景
在以下情况下可以使用模板模式:
- 有多个子类共有的方法,且逻辑相同。
- 重要的、复杂的方法,可以考虑作为模板方法。
应用场景
- 学习某门课程的流程,在学习过程中有一些共同的步骤,比如预习、上课、复习做练习等。我们可以定义一个抽象类StudyCourse,在其中定义学习的方法。具体类就是每门具体课程的实现类,它们根据课程内容和学习方式来实现抽象类中的方法。模板方法则是定义在抽象类中的一组方法,用于规定学习的整体流程和一些基本规则。
- JdbcTemplate:JdbcTemplate提供了一系列的模板方法,如execute、query、update等。开发者可以通过继承JdbcTemplate并实现相应的抽象方法来完成数据库操作的具体实现。
- HttpServlet:HttpServlet类是一个抽象类,提供了handleRequest、doGet、doPost等模板方法,用于处理HTTP请求。Servlet开发者可以继承HttpServlet并实现这些方法来处理具体的请求,从而完成一个特定的Servlet实现。
- Servlet过滤器:Java Servlet API中提供了过滤器(Filter)接口,用于对Servlet请求进行拦截和处理。该接口中定义了一个doFilter()方法,该方法是一个模板方法,由子类实现具体的请求拦截和处理方式。
模板模式和策略模式的区别
模板模式的主要思想:定义一个算法流程,将一些特定步骤的具体实现、延迟到子类。使得可以在不改变算法流程的情况下,通过不同的子类、来实现“定制”流程中的特定的步骤。
策略模式的主要思想:使不同的算法可以被相互替换,而不影响客户端的使用。
在思想和意图上看,模板方法更加强调:
- 定义一条线(算法流程),线上的多个点是可以变化的(具体实现在子类中完成),线上的多个点一定是会被执行的,并且一定是按照特定流程被执行的。
- 算法流程只有唯一的入口,对于点的访问是受限的。
策略模式更注重于: 一个“策略”是一个整体的(完整的)算法,算法是可以被整体替换的。而模板方法只能被替换其中的特定点,算法流程是固定不可变的。
模式 | 优点 | 缺点 |
---|---|---|
策略模式 | 横向扩展性好,灵活性高 | 客户端需要知道全部策略,若策略过多会导致复杂度升高 |
模板方法模式 | 可维护性好,纵向扩展性好 | 耦合性较高,子类无法影响父类公用模块代码 |
模式扩展
略。
参考
- https://blog.csdn.net/weixin_45433817/article/details/131037102
- https://blog.csdn.net/weixin_45433817/article/details/131355105
- https://www.runoob.com/design-pattern/template-pattern.html
- https://blog.csdn.net/qq_51340322/article/details/126064122
- https://blog.csdn.net/m0_67168421/article/details/131793824
- https://zhuanlan.zhihu.com/p/630059420