深入理解设计模式-行为型之模板(和回调区别联系)
概述
模板设计模式(Template Design Pattern)是一种行为型设计模式,它定义了一个算法的骨架,将算法的一些步骤延迟到子类中实现。模板设计模式允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
模板设计模式的核心思想是:将一个算法的主要结构定义在一个模板方法中,而将具体(某些)步骤的实现交给子类去完成。
// 模板类 抽象类:Beverage-->饮料
abstract class Beverage {// 模板方法,定义算法的骨架public final void prepareRecipe() {boilWater();brew();pourInCup();addCondiments();}// 具体步骤,由子类实现abstract void brew();abstract void addCondiments();// 公共方法void boilWater() {System.out.println("Boiling water");}void pourInCup() {System.out.println("Pouring into cup");}
}// 具体子类
class Coffee extends Beverage {void brew() {System.out.println("Dripping coffee through filter");}void addCondiments() {System.out.println("Adding sugar and milk");}
}class Tea extends Beverage {void brew() {System.out.println("Steeping the tea");}void addCondiments() {System.out.println("Adding lemon");}
}// 客户端代码
public class TemplateExample {public static void main(String[] args) {Beverage coffee = new Coffee();Beverage tea = new Tea();System.out.println("Making coffee:");coffee.prepareRecipe();System.out.println("\nMaking tea:");tea.prepareRecipe();}
}
在这个示例中,Beverage 是模板类,定义了模板方法 prepareRecipe(),其中包含了煮水、冲泡、倒入杯子和加调料的步骤。brew() 和 addCondiments() 是具体步骤(冲泡和加调料),由子类实现。Coffee 和 Tea 是具体子类,分别实现了不同的冲泡和调料步骤。
通过模板设计模式,模板类 Beverage 提供了一个通用的算法骨架,而具体步骤的实现交给子类。这样可以确保算法的结构一致,同时允许不同子类根据自身特点进行实现。
使用场景、源码应用
模板设计模式在许多场景下都可以应用,特别是在需要定义一组具有共同流程的操作时,但每个操作可能有不同的实现细节。以下是一些常见的应用场景:
-
框架和库:许多框架和库使用模板设计模式来定义通用的操作流程,然后允许用户通过子类来实现特定的操作细节。比如,数据库操作框架可以定义一个通用的操作流程,然后用户可以通过继承来实现特定数据库的连接和操作。
-
算法实现:在某些算法中,有一些步骤是通用的,但有些步骤可能因情况而异。模板设计模式允许你将通用的步骤放在模板方法中,然后由子类来实现不同的步骤。
-
工作流程:在工作流程管理中,可以使用模板设计模式来定义通用的工作流程,然后让不同的流程实例来实现具体的任务。
-
生命周期管理:在许多应用中,有一些生命周期的操作是通用的,例如初始化、清理资源等。模板设计模式可以用于定义这些通用的生命周期操作。
在源码中,模板设计模式也有许多应用。以下是一些示例:
-
Java Servlet:在 Java Servlet 中,HttpServlet 就是一个使用模板设计模式的例子。HttpServlet 定义了 service() 方法作为模板方法,然后具体的 HTTP 请求处理由不同的子类来实现。
-
JUnit 测试框架:在 JUnit 中,测试用例的执行过程也是一个典型的模板设计模式。JUnit 提供了测试用例的生命周期方法,例如 setUp() 和 tearDown(),然后用户可以在子类中实现这些方法来执行测试。
-
Spring Framework:在 Spring 中,JdbcTemplate 类用于执行数据库操作,它将数据库操作的通用流程定义在模板方法中,而具体的 SQL 执行由用户提供的回调函数实现。
这些只是一些示例,模板设计模式在许多框架和库中都有广泛的应用,它提供了一种结构化的方式来定义通用的操作流程,并允许具体实现在子类中进行定制。
Java Servlet
对于 Java Web 项目开发来说,常用的开发框架是 SpringMVC。利用它,我们只需要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果我们抛开这些高级框架来开发 Web 项目,必然会用到 Servlet。实际上,使用比较底层的 Servlet 来开发 Web 项目也不难。我们只需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。具体的代码示例如下所示:
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("Hello World.");}
}
除此之外,我们还需要在配置文件 web.xml 中做如下配置。Tomcat、Jetty 等 Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL 和 Servlet 之间的映射关系。
<servlet><servlet-name>HelloServlet</servlet-name><servlet-class>com.xzg.cd.HelloServlet</servlet-class>
</servlet>
<servlet-mapping><servlet-name>HelloServlet</servlet-name><url-pattern>/hello</url-pattern>
</servlet-mapping>
当我们在浏览器中输入网址(比如,http://127.0.0.1:8080/hello )的时候,Servlet 容器会接收到相应的请求,并且根据 URL 和 Servlet 之间的映射关系,找到相应的 Servlet(HelloServlet),然后执行它的 service() 方法。service() 方法定义在父类 HttpServlet 中,它会调用 doGet() 或 doPost() 方法,然后输出数据(“Hello world”)到网页。我们现在来看,HttpServlet 的 service() 函数长什么样子。
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException{HttpServletRequest request;HttpServletResponse response;if (!(req instanceof HttpServletRequest &&res instanceof HttpServletResponse)) {throw new ServletException("non-HTTP request or response");}request = (HttpServletRequest) req;response = (HttpServletResponse) res;service(request, response);
}protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);if (ifModifiedSince < lastModified) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);// 子类实现的扩展点doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {// 子类实现的扩展点doPost(req, resp);} else if (method.equals(METHOD_PUT)) {// 子类实现的扩展点doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {// 子类实现的扩展点doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {// 子类实现的扩展点doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {// 子类实现的扩展点doTrace(req,resp);} else {String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}
从上面的代码中我们可以看出,HttpServlet 的 service() 方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,**doGet()、doPost() 是模板中可以由子类来定制的部分。**实际上,这就相当于 Servlet 框架提供了一个扩展点(doGet()、doPost() 方法),让框架用户在不用修改 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。
模板模式与Callback回调函数有何区别和联系?
联系:
- 共同点:两者都涉及将一些逻辑从调用代码中抽离出来,使代码更具模块化和可维护性。
- 抽象步骤:在模板模式中,一个通用的算法框架定义了一系列的抽象步骤,子类可以通过实现这些步骤来完成特定的行为。在回调函数中,一个函数可以接受一个回调函数作为参数,使调用者能够在适当的时候执行这个回调函数,完成特定的操作。
区别:
-
角色和目的:
- 模板模式:主要目的是在超类中定义算法的骨架,而将一些具体步骤的实现推迟到子类中。它更关注整个流程的结构和控制。
- 回调函数:主要目的是允许调用者在某个代码块执行时插入自己的代码逻辑。它更关注于将执行权交给外部代码,以便根据需要执行回调逻辑。
-
控制权:
- 模板模式:控制权由超类控制,子类只实现具体的步骤,流程由模板方法决定。
- 回调函数:控制权在调用者手中,调用者通过提供回调函数来决定在何时执行回调逻辑。
-
调用关系:
- 模板模式:子类通过继承超类来实现抽象步骤,超类负责调用子类的方法。
- 回调函数:调用者将回调函数作为参数传递给被调用者,被调用者在适当的时候调用回调函数。
举例:
一个具体的区别和联系示例可以是在GUI编程中,比如在按钮被点击时要执行的操作。使用模板模式,你可以定义一个通用的按钮点击流程,包括按钮的渲染、点击事件的处理等。使用回调函数,你可以将点击事件处理的逻辑作为一个回调函数传递给按钮组件,以便在按钮被点击时执行。
Java中的 java.util.concurrent 包中的一些类使用了回调来实现多线程编程。例如,Executor 接口中的 execute(Runnable command) 方法就接受一个 Runnable 对象作为回调,用于在线程池中执行任务。
总之,模板模式和回调函数在不同的场景下有不同的应用,但都关注于提高代码的模块化和可重用性,同时也都涉及到将一些代码逻辑从调用者中分离出来。
相关文章:
深入理解设计模式-行为型之模板(和回调区别联系)
概述 模板设计模式(Template Design Pattern)是一种行为型设计模式,它定义了一个算法的骨架,将算法的一些步骤延迟到子类中实现。模板设计模式允许子类在不改变算法结构的情况下重新定义算法的某些步骤。 模板设计模式的核心思想…...
LabVIEW控制通用工作台
LabVIEW控制通用工作台 用于教育目的的计算机化实验室显着增长,特别是用于运动控制的实验室。它们代表了各种工业应用中不断扩大的领域,并成为以安全的方式使用通常昂贵或独特的实验室设备进行实时实验的宝贵工具。NI LabVIEW等软件应用程序的开发和不断…...
什么是事务,并发带来的事务问题以及事务隔离级别(图文详解)
一、什么是事务? 简单说就是逻辑上的一组操作,要么都执行,要么都不执行。 举个例子,假如小明要给小红转账100元,这个转账会涉及到两个关键操作:①将小明的余额减少100元。 ②将小红的余额增加100元 。但…...
【MySQL】MySQL数据库的delete from table和truncate table之间的区别
DELETE FROM table 和 TRUNCATE TABLE 是两种不同的数据库操作,用于从MySQL数据库的表中删除数据。它们有以下区别: 操作方式:DELETE FROM table 是一种逐行删除的操作,它会逐个删除表中的每一行数据,并且可以带有条件…...
强制Edge或Chrome使用独立显卡【WIN10】
现代浏览器通常将图形密集型任务卸载到 GPU,以改善你的网页浏览体验,从而释放 CPU 资源用于其他任务。 如果你的系统有多个 GPU,Windows 10 可以自动决定最适合 Microsoft Edge 自动使用的 GPU,但这并不一定意味着最强大的 GPU。 …...
easyx图形库基础:3实现弹球小游戏
实现弹球小游戏 一.实现弹球小游戏:1.初始化布:2.初始化一个球的信息:3.球的移动和碰撞反弹4.底边挡板的绘制和移动碰撞重置数据。 二.整体代码: 一.实现弹球小游戏: 1.初始化布: int main() {initgraph(800, 600);setorigin(40…...
vue基础知识三:v-show和v-if有什么区别?使用场景分别是什么?
一、v-show与v-if的共同点 我们都知道在 vue 中 v-show 与 v-if 的作用效果是相同的(不含v-else),都能控制元素在页面是否显示 在用法上也是相同的 <Model v-show"isShow" /> <Model v-if"isShow" />当表达式为true的时候&#…...
Python Opencv实践 - 图像旋转
import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR)#图像旋转 #Opencv中的旋转,首先通过cv.getRotationMatrix2D获得旋转矩阵 #cv.getRotationMatrix2D(center,ang…...
第五章 Opencv图像处理框架实战 5-10 文档扫描OCR识别
一、整体流程演示 上一篇我们进行了银行卡数字识别,这次我们利用opnecv等基础图像处理方法实现文档扫描OCR识别,该项目可以对任何一个文档,识别扫描出该文档上所有的文字信息。 为了方便后续程序运行,大家可以在Run->Edit Configuration中配置相关参数,选择相应编译器…...
CentOS 7 源码制作openssh 9.4p1 rpm包 —— 筑梦之路
参考之前的博客: centos 7 制作openssh8.7/8.8/8.9/9.0/9.1/9.2/9.3 p1 rpm包升级——筑梦之路_openssh rpm包_筑梦之路的博客-CSDN博客 需要说明的是9.4版本必须要openssl 1.1.1,低于此版本无法完成编译。这也是单独写这篇文章的必要性。 参考这篇编…...
OpenCV图像处理——轮廓检测
目录 图像的轮廓查找轮廓绘制轮廓 轮廓的特征轮廓面积轮廓周长轮廓近似凸包边界矩形最小外接圆椭圆拟合直线拟合 图像的矩特征矩的概念图像中的矩特征 图像的轮廓 查找轮廓 binary,contours,hierarchycv.findContours(img,mode,method)绘制轮廓 cv.drawContours(img,coutours…...
【论文阅读】基于深度学习的时序预测——Non-stationary Transformers
系列文章链接 论文一:2020 Informer:长时序数据预测 论文二:2021 Autoformer:长序列数据预测 论文三:2022 FEDformer:长序列数据预测 论文四:2022 Non-Stationary Transformers:非平…...
开发者如何使用讯飞星火认知大模型API?
目录 1、申请星火API接口 2、使用星火API接口 3、测试编译效果 之前我们使用网页文本输入的方式体验了讯飞星火认知大模型的功能(是什么让科大讯飞1个月股价翻倍?),本篇博文将从开发者角度来看看如何使用讯飞星火认知大模型API…...
linux 系统中vi 编辑器和库的制作和使用
目录 1 vim 1.1 vim简单介绍 1.2 vim的三种模式 1.3 vim基本操作 1.3.1命令模式下的操作 1.3.2 切换到文本输入模式 1.3.3 末行模式下的操作 2 gcc编译器 2.1 gcc的工作流程 2.2 gcc常用参数 3 静态库和共享(动态)库 3.1库的介绍 3.2静态…...
麒麟arm架构 编译安装qt5.14.2
1、先在官网下载qt源码: https://download.qt.io/archive/qt/5.14/5.14.2/single/[qt源码下载地址] 2、解压编译 使用tar -xvf qt-everywhere-src-5.14.2.tar.xz 解压压缩包 cd qt-everywhere-src-5.14.2 执行 ./configure --prefix/usr/local/qt.5.14.2 make -…...
【springmvc系】利用RequestBodyAdviceAdapter做接口鉴权
需求 有个简单的需求,对于第三方接口我们需要做个简单的鉴权机制,这边使用的是非对称性加密的机制。我们提供三方公钥,他们通过公钥对接口json报文使用加密后的报文请求,我们通过对接收过来的请求某一个加密报文字段来进行RSA解密…...
ROS学习笔记(三)---好用的终端Terminator
ROS学习笔记文章目录 01. ROS学习笔记(一)—Linux安装VScode 02. ROS学习笔记(二)—使用 VScode 开发 ROS 的Python程序(简例) 一、Terminator是什么? 在前面的学习中,为了运行hello.py我是在vscode频繁的点击运行窗口的“”号…...
NFT Insider#102:The Sandbox重新上线LAND桥接服务,YGG加入Base生态
引言:NFT Insider由NFT收藏组织WHALE Members(https://twitter.com/WHALEMembers)、BeepCrypto(https://twitter.com/beep_crypto)联合出品,浓缩每周NFT新闻,为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周…...
Webpack 的 sass-loader 在生产模式下最小化 CSS 问题
学习webpack时候我发现一个问题: 将mode 改为production模式后,生成的css会被压缩了,但是我并没有引入CssMinimizerPlugin插件,然后我试着将optimization.minimize 设置为false,测试是否为webpack自带的压缩࿰…...
pytest自动化测试框架tep环境变量、fixtures、用例三者之间的关系
tep是一款测试工具,在pytest测试框架基础上集成了第三方包,提供项目脚手架,帮助以写Python代码方式,快速实现自动化项目落地。 在tep项目中,自动化测试用例都是放到tests目录下的,每个.py文件相互独立&…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
