【再谈设计模式】模板方法模式 - 算法骨架的构建者
一、引言
在软件工程、软件开发过程中,我们经常会遇到一些算法或者业务逻辑具有固定的流程步骤,但其中个别步骤的实现可能会因具体情况而有所不同的情况。模板方法设计模式(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…...
CI/CD 流水线
CI/CD 流水线 CI 与 CD 的边界CI 持续集成CD(持续交付/持续部署)自动化流程示例: Jenkins 引入到 CI/CD 流程在本地或服务器上安装 Jenkins。配置 Jenkins 环境流程设计CI 阶段:Jenkins 流水线实现CD 阶段:Jenkins 流水…...
【python3】 sqlite格式的db文件获得所有表和数据
【python3】 sqlite格式的db文件获得所有表和数据 1.背景2.代码3.解析1.背景 SQLite 格式的 .db 文件就是一个包含 SQLite 数据库的文件。 SQLite 格式的 .db 文件通常存储的是一个关系型数据库。 SQLite广泛用于应用程序、移动设备、浏览器等场景。它将整个数据库存储在一个文…...
【灵码助力安全3】——利用通义灵码辅助智能合约漏洞检测的尝试
前言 随着区块链技术的快速发展,智能合约作为去中心化应用(DApps)的核心组件,其重要性日益凸显。然而,智能合约的安全问题一直是制约区块链技术广泛应用的关键因素之一。由于智能合约代码一旦部署就难以更改…...
openEuler 22.04使用yum源最快速度部署k8s 1.20集群
本文目的 openEuler的官方源里有kubernetes 1.20,使用yum源安装是最快部署一个k8s集群的办法 硬件环境 主机名系统架构ipmasteropenEuler release 22.03 (LTS-SP2)arm192.168.3.11edgeopenEuler release 22.03 (LTS-SP2)arm192.168.3.12deviceopenEuler release 22.…...
Docker Compose 教程
Docker Compose 是一个 Docker 容器的依赖管理工具。 例如我们一个服务需要依赖到多个 Docker 容器,那么使用 Docker Compose 这个工具就能很方便的帮助我们管理。 Docker Compose 通过配置文件 .yml。 定义了所有容器的依赖关系。 然后我们只需把我们想要的 Docke…...
opencv的NLM去噪算法
NLM(Non-Local Means)去噪算法是一种基于图像块(patch)相似性的去噪方法。其基本原理是: 图像块相似性:算法首先定义了一个搜索窗口(search window),然后在该窗口内寻找…...
scala基础学习_方法函数
文章目录 方法与函数函数(又称函数值/匿名函数)定义方法注意 单参数函数多参数函数函数作为参数传递 方法将方法转换为函数方法的返回值总结 方法与函数 函数(又称函数值/匿名函数) 定义在任何地方:函数可以定义在类…...
Android车机DIY开发之软件篇(八)单独编译
Android车机DIY开发之软件篇(八)单独编译 1.CarLauncher单独编译 CarLauncher源码位于 packages/apps/Car/Launcher 用Eclipse ADT 谷歌定制版编译而成,.mk .bp编译 Android13目录如下: alientekalientek:~/packages/apps/Car$ ls Calendar …...
【Bug】报错信息:Required request body is missing(包含五种详细解决方案)
大家好,我是摇光~ 遇到“Required request body is missing”错误通常意味着服务器期望在HTTP请求中包含一个请求体(body),但是实际上并没有收到。 例如: 当你在使用网页或应用程序的后台(比如一个网站或手…...
Docker 专栏 —— Dockerfile 指令详解
文章目录 ADD 复制文件COPY 复制文件ARG 设置构建参数CMD 容器启动命令ENTRYPOINT ⼊⼝点ENV 设置环境变量EXPOSE 声明暴露的端⼝FROM 指定基础镜像LABEL 为镜像添加元数据MAINTAINER 指定维护者的信息RUN 执⾏命令USER 设置⽤户VOLUME 指定挂载点WORKDIR 指定⼯作⽬录 ADD 复制…...
从0开始做网站/矿产网站建设价格
通常我们在学习CSS的时候,感觉语法很容易掌握,实际应用中却碰到各式各样难以填补的“坑”。为避免大家受到同样的困惑与不解,本文详细讲解了CSS中优先级和Stacking Context等高级特性。让你更深入了解CSS。CSS 优先级优先级是浏览器是通过判断…...
个人电脑做服务器网站/广告推销
点击上方“服务端思维”,选择“设为星标”回复”669“获取独家整理的精选资料集回复”加群“加入全国服务端高端社群「后端圈作者:终端研发部NO.1 Java.alng.NullPointerException这个异常大家肯定都经常遇到,异常的解释是 “程序遇上了空指…...
优度公司做的网站/广州十大营销策划公司
线程同步与异步套接字编程1.事件对象上一次介绍了利用互斥对象实现线程同步 http://blog.csdn.net/walkerkalr/article/details/19510909。这次将继续介绍另两种线程同步的方法:事件对象和关键字代码段。1.1事件对象事件对象也属于内核对象,包括三个成员…...
企业网站做电脑营销/文章推广平台
问题 当物体正常展示后,设置为实体展示,却无法显示出立体的效果来,按照给出的MeshLambertMaterial材料设置的例子反复尝试都不成功 实例 var cubeGeometry new THREE.BoxGeometry(15, 15, 15); var meshMaterial new THREE.MeshLamebertM…...
网站建设 长春/营销方式和渠道有哪些
Python3.x 1 数据类型 1.0 标准数据类型 Python3.x标准数据类型有6中,如下: 序号数据类型描述1数字Number2字符串String3列表List4元组Tuple5字典Dictionary6集合Set 1.2 数字(Number) 数字包括整数,浮点数,布尔数据和复数四种,python3.x中将True和False定义成关键字,表示…...
烟台H5网站设计公司/东莞网站建设推广技巧
点击上方“Github爱好者社区”,选择星标回复“资料”,获取小编整理的一份资料作者 l Hollis来源 l Hollis在我的博客和公众号中,发表过很多篇关于并发编程的文章,之前的文章中我们介绍过了两个在Java并发编程中比较重要的两个关键…...