深入理解设计模式-行为型之模板(和回调区别联系)

概述

模板设计模式(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 对象作为回调,用于在线程池中执行任务。

总之,模板模式和回调函数在不同的场景下有不同的应用,但都关注于提高代码的模块化和可重用性,同时也都涉及到将一些代码逻辑从调用者中分离出来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/41110.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络通信原理应用层(第五十一课)

1)DNS:域名解析系统,端口号TCP或UDP的53 2)域名注册网站 -新网 www.xinnet.com -万网-阿里云 www.net.cn -中国互联 hulian.top 配置通过域名访问网站(NETBASE第七课)_IHOPEDREAM的博客-CSDN博客 2、FTP 1)FTP概述 -文件传输协议 -控制连接:TCP 21 <

对redis、redisson、springcache总结

<一> redis-缓存中间件 什么是redis redis是c语言开发的&#xff0c;一个高性能key-value键值对内存数据库&#xff0c;可以用来做数据库、缓存、消息中间件的一种非关系型数据库。 redis数据存储在哪里 内存和磁盘中&#xff0c;但是redis的读写都在内存中&#xff0c;…

leetcode-413. 等差数列划分(java)

等差数列划分 leetcode-413. 等差数列划分题目描述双指针 上期经典算法 leetcode-413. 等差数列划分 难度 - 中等 原题链接 - 等差数列划分 题目描述 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0…

16 脑洞大开:GUI测试还能这么玩

页面对象自动生成技术 页面对象自动生成技术&#xff0c;属于典型的“自动化你的自动化”的应用场景。它的基本思路是&#xff0c;你不用再手工维护 Page Class 了&#xff0c;只需要提供 Web 的 URL&#xff0c;它就会自动帮你生成这个页面上所有控件的定位信息&#xff0c;并…

JMeter接口测试数据分离驱动应用

步骤&#xff1a; 创建csv文件&#xff0c;编写接口测试用例 新建线程组——创建循环控制器&#xff08;循环次数填用例总数&#xff09; 创建CSV数据文件设置&#xff0c;设置参数。&#xff08;注意&#xff1a;是否允许带引号&#xff1f;&#xff1a;一定要设置为true&a…

深度学习实战48-【未来的专家团队】基于AutoCompany模型的自动化企业概念设计与设想

大家好,我是微学AI,今天给大家介绍一下深度学习实战48-【未来的专家团队】基于AutoCompany模型的自动化企业概念设计与设想,文本将介绍AutoCompany模型的概念设计,涵盖了AI智能公司的各个角色,并结合了GPT-4接口来实现各个角色的功能,设置中央控制器,公司运作过程会生成…

【MFC常用问题记录】

MFC 记录 MFC的edit control控件显示1.控件添加变量M_edit后&#xff1a;2.控件ID为IDC_EDIT1: 线程函数使用 MFC的edit control控件显示 1.控件添加变量M_edit后&#xff1a; CString str; int x 10; str.Format(_T("%d"),x); M_edit.SetWindowText(str)2.控件ID…

JMM内存模型之happens-before阐述

文章目录 一、happens-before的定义二、happens-before的规则1. 程序顺序规则&#xff1a;2. 监视器锁规则&#xff1a;3. volatile变量规则&#xff1a;4. 传递性&#xff1a;5. start()规则&#xff1a;6. join()规则&#xff1a; 一、happens-before的定义 如果一个操作hap…

【编程二三事】ES究竟是个啥?

在最近的项目中&#xff0c;总是或多或少接触到了搜索的能力。而在这些项目之中&#xff0c;或多或少都离不开一个中间件 - ElasticSearch。 今天忙里偷闲&#xff0c;就来好好了解下这个中间件是用来干什么的。 ES是什么? ​ ES全称ElasticSearch&#xff0c;是个基于Lucen…

性能优化的重要性

性能优化的重要性 性能优化的重要性摘要引言注意事项代码示例及注释性能优化的重要性 性能优化的重要性在 Java 中的体现响应速度资源利用效率扩展性与可维护性并发性能合理的锁策略线程安全的数据结构并发工具类的应用避免竞态条件和死锁 总结代码示例 博主 默语带您 Go to Ne…

一张图看懂 USDT三种类型地址 Omni、ERC20、TRC20的区别

USDT是当前实用最广泛&#xff0c;市值最高的稳定币&#xff0c;它是中心化的公司Tether发行的。在今年的4月17日之前&#xff0c;市场上存在着2种不同类型的USDT。4月17日又多了一种波场TRC20协议发行的USDT&#xff0c;它们各自有什么区别呢?哪个转账最快到账&#xff1f;哪…

谷歌推出首款量子弹性 FIDO2 安全密钥

谷歌在本周二宣布推出首个量子弹性 FIDO2 安全密钥&#xff0c;作为其 OpenSK 安全密钥计划的一部分。 Elie Bursztein和Fabian Kaczmarczyck表示&#xff1a;这一开源硬件优化的实现采用了一种新颖的ECC/Dilithium混合签名模式&#xff0c;它结合了ECC抵御标准攻击的安全性和…

[LeetCode]矩阵对角线元素的和

解题 思路 1: 循环,找到主对角线的下标和副对角线的下标,如果矩阵长或宽为奇数的时候,需要减去中间公共的那一个值,中间公共的那个数的下标为mat[mat.size()/2][mat.size()/2]副对角线的下标为 mat [i][mat.size()-i-1] class Solution { public:int diagonalSum(vector<ve…

JVM中判定对象是否回收的的方法

引用计数法 引用计数法是一种垃圾回收&#xff08;Garbage Collection&#xff09;算法&#xff0c;用于自动管理内存中的对象。在引用计数法中&#xff0c;每个对象都有一个关联的引用计数器&#xff0c;用于记录对该对象的引用数量。 当一个新的引用指向对象时&#xff0c;…

Hive底层数据存储格式

前言 在大数据领域,Hive是一种常用的数据仓库工具,用于管理和处理大规模数据集。Hive底层支持多种数据存储格式,这些格式对于数据存储、查询性能和压缩效率等方面有不同的优缺点。本文将介绍Hive底层的三种主要数据存储格式:文本文件格式、Parquet格式和ORC格式。 一、三…

SpringBoot复习:(42)WebServerCustomizer的customize方法是在哪里被调用的?

ServletWebServletAutoConfiguration类定义如下&#xff1a; 可以看到其中通过Import注解导入了其内部类BeanPostProcessorRegister。 BeanPostProcessor中定义的registerBeanDefinition方法会被Spring容器调用。 registerBeanDefinitions方法调用了RegistrySyntheticBeanIf…

解决vue3前端获取文件的绝对路径问题

解决vue3前端获取文件的绝对路径问题 公司的项目是基于vue3的&#xff0c;由于需求需要前端获取用户选的文件的绝对路径。但是浏览器处于安全策略无法获取真实的文件路径&#xff0c;只能拿到相对路径或者是D:\fakepath\xxxx. 看了网上很多方法都很坑&#xff0c;明明没拿到路…

vue基础-vue监听当前屏幕大小做不同的操作

文章目录 前言一、代码如下&#xff1a;总结 前言 在vue项目开发过程中&#xff0c;有个需求&#xff0c;就是当屏幕大于1024时&#xff0c;我们默认为PC模式。小于1024时&#xff0c;我们默认为H5模式。但是有的界面我们想在PC和H5上面展示不同的数据&#xff0c;请求不同的接…

Intellij IDEA SBT依赖分析插件

可分析模块和传递依赖 安装完插件后&#xff0c;由于IDEA BUG&#xff0c;会出现两个分析按钮&#xff0c;一个是gradle的&#xff0c;一般是后者是新安装的sbt。 选择需要分析的模块 只需要在project/plugins.sbt中添加代码&#xff0c;启动官方分析插件addDependencyTreeP…

1281. 整数的各位积和之差

诸神缄默不语-个人CSDN博文目录 力扣刷题笔记 文章目录 1. 简单粗暴的遍历2. 其实也是遍历&#xff0c;但是用Python内置函数只用写一行 1. 简单粗暴的遍历 Python版&#xff1a; class Solution:def subtractProductAndSum(self, n: int) -> int:he0ji1while n>1:last…