【再谈设计模式】模板方法模式 - 算法骨架的构建者

一、引言
在软件工程、软件开发过程中,我们经常会遇到一些算法或者业务逻辑具有固定的流程步骤,但其中个别步骤的实现可能会因具体情况而有所不同的情况。模板方法设计模式(Template Method Design Pattern)就为解决这类问题提供了一个优雅的方案。它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中去实现,使得子类可以在不改变算法结构的情况下重新定义某些特定的步骤。

二、定义与描述
模板方法设计模式是一种行为型设计模式。它包含一个抽象类(在Java和C++中)或者一个抽象基类(在Python中可以通过ABC抽象基类实现类似功能,在Go中通过接口和结构体组合来体现),这个抽象类中定义了一个模板方法,这个模板方法包含了算法的骨架,它按照一定的顺序调用其他的抽象方法或具体方法。抽象方法由子类去实现,从而实现不同的行为。

三、抽象背景
假设我们正在开发一个游戏角色创建系统。游戏中有不同类型的角色,如战士、法师、刺客。每个角色在创建时有一些通用的步骤,例如选择种族、选择性别等,但也有一些特定于角色类型的步骤,比如战士要选择武器类型,法师要选择魔法元素,刺客要选择隐匿技能类型。这时候就可以使用模板方法设计模式来构建这个创建系统。

四、适用场景与现实问题解决

- 场景一:框架开发
- 在框架开发中,框架通常提供了一个固定的处理流程,但允许用户自定义某些特定的操作。例如,Web框架可能定义了处理HTTP请求的基本流程:接收请求、解析请求、处理业务逻辑、构建响应、发送响应。其中处理业务逻辑的部分可以由用户根据自己的需求定制。
- 使用模板方法设计模式,框架可以将整个请求处理流程定义在一个抽象类中的模板方法里,而将处理业务逻辑的部分抽象成抽象方法,让用户通过继承抽象类并实现抽象方法来定制自己的业务逻辑。
- 场景二:算法流程固定但部分可变
- 例如排序算法中的希尔排序。希尔排序的基本思想是将数组按照一定的间隔进行分组,然后对每组进行插入排序,逐渐缩小间隔直到间隔为1。其中分组的计算和整体的排序流程是固定的,但每次分组后的插入排序步骤(比较和交换元素的操作)可以看作是一个可变的部分。
- 可以使用模板方法设计模式,将希尔排序的整体流程定义在模板方法中,而将插入排序的操作抽象成抽象方法,这样如果要对插入排序进行优化或者修改,只需要在子类中重新实现这个抽象方法即可。

五、模板方法设计模式的现实生活的例子
- 泡茶示例
- 泡茶的基本步骤是固定的:准备茶具、烧开水、浸泡茶叶、倒入茶杯、添加调料(如糖、柠檬等,这一步可选)。这里烧开水、浸泡茶叶等步骤是固定的顺序,但不同的茶叶(如绿茶、红茶、黑茶)浸泡的时间和温度可能不同,添加调料的种类也可能不同。
- 可以将泡茶的过程看作一个模板方法,其中准备茶具、烧开水等是固定的步骤,而浸泡茶叶和添加调料可以看作是抽象方法,根据不同的茶叶种类(子类)来具体实现。

六、初衷与问题解决
初衷是为了在具有固定流程的算法或者业务逻辑中,提高代码的复用性和可维护性。通过将固定的流程放在模板方法中,将可变的部分抽象出来由子类实现,可以避免代码的重复编写,并且当业务逻辑发生变化时,只需要修改对应的子类即可,而不需要修改整个算法的结构。

七、代码示例

Java示例
abstract class GameCharacterCreator {// 模板方法,定义了创建角色的流程public final void createCharacter() {chooseRace();chooseGender();chooseClassSpecificOptions();}private void chooseRace() {System.out.println("选择种族");}private void chooseGender() {System.out.println("选择性别");}// 抽象方法,由子类实现abstract void chooseClassSpecificOptions();
}class WarriorCreator extends GameCharacterCreator {@Overridevoid chooseClassSpecificOptions() {System.out.println("选择武器类型");}
}class MageCreator extends GameCharacterCreator {@Overridevoid chooseClassSpecificOptions() {System.out.println("选择魔法元素");}
}class AssassinCreator extends GameCharacterCreator {@Overridevoid chooseClassSpecificOptions() {System.out.println("选择隐匿技能类型");}
}
类图:

- GameCharacterCreator是一个抽象类,有一个公共的createCharacter方法(用+表示),一些私有方法(用-表示)和一个抽象方法(用#表示)。
- WarriorCreator、MageCreator和AssassinCreator都是继承自GameCharacterCreator的具体类,并且实现了抽象方法chooseClassSpecificOptions。
时序图:

以WarriorCreator为例,假设玩家创建战士角色。首先GameCharacterCreator引导玩家进行种族和性别的选择,然后WarriorCreator引导玩家进行战士特定的选项选择(这里是武器类型)。对于MageCreator和AssassinCreator可以类似表示,只是chooseClassSpecificOptions的内容不同。
流程图:

首先进行种族和性别的选择,然后根据选择的角色类型(战士、法师或刺客等)执行相应子类的特定选项选择方法,如果是未知角色类型则给出提示。
C++示例
class GameCharacterCreator {
public:// 模板方法,定义了创建角色的流程,final表示不能被子类重写void createCharacter() {chooseRace();chooseGender();chooseClassSpecificOptions();}
private:void chooseRace() {std::cout << "选择种族" << std::endl;}void chooseGender() {std::cout << "选择性别" << std::endl;}// 纯虚函数,相当于抽象方法,由子类实现virtual void chooseClassSpecificOptions() = 0;
};class WarriorCreator : public GameCharacterCreator {
public:void chooseClassSpecificOptions() override {std::cout << "选择武器类型" << std::endl;}
};class MageCreator : public GameCharacterCreator {
public:void chooseClassSpecificOptions() override {std::cout << "选择魔法元素" << std::endl;}
};class AssassinCreator : public GameCharacterCreator {
public:void chooseClassSpecificOptions() override {std::cout << "选择隐匿技能类型" << std::endl;}
};
Python示例
from abc import ABC, abstractmethodclass GameCharacterCreator(ABC):def create_character(self):self.choose_race()self.choose_gender()self.choose_class_specific_options()def choose_race(self):print("选择种族")def choose_gender(self):print("选择性别")@abstractmethoddef choose_class_specific_options(self):passclass WarriorCreator(GameCharacterCreator):def choose_class_specific_options(self):print("选择武器类型")class MageCreator(GameCharacterCreator):def choose_class_specific_options(self):print("选择魔法元素")class AssassinCreator(GameCharacterCreator):def choose_class_specific_options(self):print("选择隐匿技能类型")
Go示例
package mainimport "fmt"// 抽象结构体
type GameCharacterCreator struct{}// 模板方法
func (g *GameCharacterCreator) createCharacter() {g.chooseRace()g.chooseGender()g.chooseClassSpecificOptions()
}func (g *GameCharacterCreator) chooseRace() {fmt.Println("选择种族")
}func (g *GameCharacterCreator) chooseGender() {fmt.Println("选择性别")
}// 抽象方法,由具体结构体实现
type CharacterCreator interface {chooseClassSpecificOptions()
}type WarriorCreator struct{}func (w *WarriorCreator) chooseClassSpecificOptions() {fmt.Println("选择武器类型")
}type MageCreator struct{}func (m *MageCreator) chooseClassSpecificOptions() {fmt.Println("选择魔法元素")
}type AssassinCreator struct{}func (a *AssassinCreator) chooseClassSpecificOptions() {fmt.Println("选择隐匿技能类型")
}
八、模板方法设计模式的优缺点

优点
- 提高代码复用性
- 算法的骨架在抽象类中定义一次,多个子类可以复用这个模板方法,减少了代码的重复编写。
- 可维护性增强
- 当业务逻辑发生变化时,只需要修改抽象类中的模板方法或者子类中的具体实现,而不需要对整个系统进行大规模的修改。
- 便于代码的扩展
- 可以很容易地添加新的子类来实现不同的具体行为,只要遵循抽象类中定义的模板方法结构。
缺点
- 违反开闭原则
- 如果要对模板方法中的算法骨架进行修改,可能需要修改抽象类,这就违反了开闭原则(对扩展开放,对修改关闭)。
- 类层次结构复杂
- 随着子类的增加,类的层次结构可能会变得比较复杂,导致代码的理解和维护成本增加。
九、模板方法设计模式的升级版

- 钩子方法(Hook Method)
- 钩子方法是一种在模板方法模式中常用的扩展机制。它是在抽象类中定义的一个空的或者有默认实现的方法,子类可以选择性地重写这个方法。例如,在泡茶的例子中,可以添加一个钩子方法“isAddSeasoning”,如果子类(某种茶叶)重写这个方法并返回true,那么在模板方法中就会执行添加调料的步骤,否则就跳过这个步骤。
- 模板方法与策略模式结合
- 可以将模板方法中的某些抽象方法的实现委托给策略模式中的具体策略类。例如,在游戏角色创建系统中,选择武器类型这个步骤可以使用策略模式,将不同的武器选择策略封装成不同的策略类,然后在战士角色创建子类中通过组合的方式使用这些策略类来实现选择武器类型的抽象方法。这样可以进一步提高代码的灵活性和可维护性。
相关文章:
【再谈设计模式】模板方法模式 - 算法骨架的构建者
一、引言 在软件工程、软件开发过程中,我们经常会遇到一些算法或者业务逻辑具有固定的流程步骤,但其中个别步骤的实现可能会因具体情况而有所不同的情况。模板方法设计模式(Template Method Design Pattern)就为解决这类问题提供了…...
Bytebase 3.1.1 - 可定制的快捷访问首页
🚀 新功能 可定制的快捷访问首页。 支持查询 Redis 集群中所有节点。 赋予项目角色时,过期时间可以定义精确到秒级的时间点。 🔔 重大变更 移除 Database 消息里的实例角色信息。调用 GetInstance 或 ListInstanceRoles 以获取实例角色信息…...
Java阶段四04
第4章-第4节 一、知识点 CSRF、token、JWT 二、目标 理解什么是CSRF攻击以及如何防范 理解什么是token 理解什么是JWT 理解session验证和JWT验证的区别 学会使用JWT 三、内容分析 重点 理解什么是CSRF攻击以及如何防范 理解什么是token 理解什么是JWT 理解session验…...
B2C API安全警示:爬虫之外,潜藏更大风险挑战
在数字化时代,B2C(Business-to-Consumer)电子商务模式已成为企业连接消费者、推动业务增长的重要桥梁。而B2C API(应用程序编程接口)作为企业与消费者之间数据交互的桥梁,其安全性更是至关重要。然而&#…...
OCR文字识别—基于PP-OCR模型实现ONNX C++推理部署
概述 PaddleOCR 是一款基于 PaddlePaddle 深度学习平台的开源 OCR 工具。PP-OCR是PaddleOCR自研的实用的超轻量OCR系统。它是一个两阶段的OCR系统,其中文本检测算法选用DB,文本识别算法选用CRNN,并在检测和识别模块之间添加文本方向分类器&a…...
如何播放视频文件
文章目录 1. 概念介绍2. 使用方法2.1 实现步骤2.2 具体细节3. 示例代码4. 内容总结我们在上一章回中介绍了"如何获取文件类型"相关的内容,本章回中将介绍如何播放视频.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 播放视频是我们常用的功能,不过Flutter官方…...
MySQL -- 约束
1. 数据库约束 数据库约束时关系型数据库的一个重要功能,主要的作用是保证数据的有效性,也可以理解为数据的正确性(数据本身是否正确,关联关系是否正确) 人工检查数据的完整性工作量非常大,在数据库中定义一些约束,那么数据在写入数据库的时候,就会帮我们做一些校验.并且约束一…...
php 使用simplexml_load_string转换xml数据格式失败
本文介绍如何使用php函数解析xml数据为数组。 <?php$a <xml><ToUserName><![CDATA[ww8b77afac71336111]]></ToUserName><FromUserName><![CDATA[sys]]></FromUserName><CreateTime>1736328669</CreateTime><Ms…...
net-http-transport 引发的句柄数(协程)泄漏问题
Reference 关于 Golang 中 http.Response.Body 未读取导致连接复用问题的一点研究https://manishrjain.com/must-close-golang-http-responsehttps://www.reddit.com/r/golang/comments/13fphyz/til_go_response_body_must_be_closed_even_if_you/?rdt35002https://medium.co…...
高级软件工程-复习
高级软件工程复习 坐标国科大,下面是老师说的考试重点。 Ruby编程语言的一些特征需要了解要能读得懂Ruby程序Git的基本命令操作知道Rails的MVC工作机理需要清楚,Model, Controller, View各司什么职责明白BDD的User Story需要会写,SMART要求能…...
eslint.config.js和.eslintrc.js有什么区别
eslint.config.js 和 .eslintrc.js 的主要区别在于它们所对应的 ESLint 版本和配置方法: 1. .eslintrc.js: 这是 ESLint v8 及更早版本使用的配置文件格式。 它使用层级式的配置系统。 现在被称为"旧版"配置格式 。 2. eslint.config.js&am…...
如何使用MVC模式设计和实现校园自助点餐系统的微信小程序
随着智慧校园的普及,校园自助点餐系统在提高学生用餐效率、减轻食堂运营压力方面发挥了重要作用。 在开发这类系统时,MVC(Model-View-Controller)模式是一种非常适合的架构,它将系统的业务逻辑、用户界面和数据交互清晰…...
继续坚持与共勉
经过期末考试后,又要开始学习啦。 当时一直在刷算法题就很少写博客了,现在要继续坚持写博客,将每天对于题的感悟记录下来。 同时我将会在学习Linux操作系统,对于过去学习的内容进行回顾!! 在此ÿ…...
人机交互 | 期末复习(上)| 补档
文章目录 📚1-HCI Introduction🐇人机交互的定义,分别解释人-机-交互的概念🐇six ”mantras“ of UCD🐇Difference between User-Interface (UI) and User-Experience(UX)📚2-HCI history🐇WIMP🐇WYSIWYG📚3-Understanding User🐇Design Thinking Process的…...
Oracle 表分区简介
目录 一. 前置知识1.1 什么是表分区1.2 表分区的优势1.3 表分区的使用条件 二. 表分区的方法2.1 范围分区(Range Partitioning)2.2 列表分区(List Partitioning)2.3 哈希分区(Hash Partitioning)2.4 复合分…...
多并发发短信处理(头条项目-07)
1 pipeline操作 Redis数据库 Redis 的 C/S 架构: 基于客户端-服务端模型以及请求/响应协议的 TCP服务。客户端向服务端发送⼀个查询请求,并监听Socket返回。通常是以 阻塞模式,等待服务端响应。服务端处理命令,并将结果返回给客…...
网络编程的进程查看连接描述符信息等
一.查看当前进程的socket对应的fd信息 1. lsof lsof(List Open Files)命令可以列出系统中所有打开的文件的信息,包括 socket。 用法 要查看特定进程的 socket 信息,可以使用以下命令: lsof -p <pid> | grep…...
ChatGPT API快速搭建自己的第一个应用—文章摘要(单轮对话应用)
使用ChatGPT API快速搭建自己的第一个应用 1 安装库2 设置与导入3 文章摘要(单轮对话应用)3.1 任务简介:3.2 初始化3.3 点击发送3.4 保存3.5 检查并打印你的结果1 安装库 !pip install gradiogradio 是一个用于快速搭建交互式用户界面的 Python 库,特别适合展示机器学习模…...
【01】AE特效开发制作特技-Adobe After Effects-AE特效制作快速入门-制作飞机,子弹,爆炸特效以及导出png序列图-优雅草央千澈
【01】AE特效开发制作特技-Adobe After Effects-AE特效制作快速入门-制作飞机,子弹,爆炸特效以及导出png序列图-优雅草央千澈 开发背景 优雅草央千澈所有的合集,系列文章可能是不太适合完全初学者的,因为课程不会非常细致的系统…...
软件测试预备知识④—NTFS权限管理、磁盘配额与文件共享
在软件测试的实际环境搭建与管理过程中,了解和掌握NTFS权限管理、磁盘配额以及文件共享等知识至关重要。这些功能不仅影响系统的安全性和稳定性,还对测试数据的存储、访问以及多用户协作测试有着深远的影响。 一、NTFS权限管理 1.1 NTFS简介 NTFS&am…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
