Java 与设计模式(15):模板方法模式
一、定义
模板方法模式是一种行为设计模式,它定义了一个操作中的算法的骨架(也就是大致的步骤和流程),而将一些具体步骤的实现延迟到子类中。这样,子类可以不改变算法的结构即可重新定义算法的某些特定步骤。
二、Java示例
举个简单的例子:假设我们要泡一杯茶和一杯咖啡,这两者的制作过程有一些共同的步骤,比如烧水、倒水、搅拌等,但也有不同的地方,比如茶需要放茶叶,而咖啡需要放咖啡粉。
泡茶的过程:
烧水、放茶叶、倒水、搅拌、品尝
泡咖啡的过程:
烧水、放咖啡粉、倒水、搅拌、品尝
我们可以看到,烧水、倒水、搅拌和品尝这些步骤是相同的,而放茶叶和放咖啡粉这两个步骤是不同的。
用模板方法模式来实现:
我们可以定义一个抽象类 Beverage,它包含一个模板方法 prepareRecipe,这个方法定义了整个流程的骨架。然后,我们创建两个子类 Tea 和 Coffee,分别实现具体的步骤。
// 抽象类
abstract class Beverage {// 模板方法,定义算法骨架public final void prepareRecipe() {boilWater();brew();pourInCup();addCondiments();}// 抽象方法,由子类实现abstract void brew();// 抽象方法,由子类实现abstract void addCondiments();// 具体方法void boilWater() {System.out.println("烧水");}// 具体方法void pourInCup() {System.out.println("倒水到杯子里");}
}// 子类:茶
class Tea extends Beverage {@Overridevoid brew() {System.out.println("泡茶");}@Overridevoid addCondiments() {System.out.println("加糖");}
}// 子类:咖啡
class Coffee extends Beverage {@Overridevoid brew() {System.out.println("泡咖啡");}@Overridevoid addCondiments() {System.out.println("加牛奶");}
}// 测试代码
public class Main {public static void main(String[] args) {Beverage tea = new Tea();tea.prepareRecipe();System.out.println();Beverage coffee = new Coffee();coffee.prepareRecipe();}
}
输出结果:
烧水
泡茶
倒水到杯子里
加糖烧水
泡咖啡
倒水到杯子里
加牛奶
模板方法:prepareRecipe 是模板方法,它定义了整个流程的骨架,包括烧水、泡茶/咖啡、倒水、加调料等步骤。
抽象方法:brew 和 addCondiments 是抽象方法,由子类 Tea 和 Coffee 具体实现。
具体方法:boilWater 和 pourInCup 是具体方法,直接在抽象类中实现。
三、优点
- 代码复用性高
优势:模板方法模式通过在抽象类中定义算法的骨架,将公共的代码逻辑集中到一个地方,避免了在多个子类中重复相同的代码。这样可以大大提高代码的复用性,减少代码冗余。
示例:在订单处理系统中,无论是普通订单、VIP订单还是国际订单,它们的处理流程都包括接收订单、验证订单、支付处理、发货和通知客户等步骤。这些公共步骤可以在抽象类中实现,而具体的实现细节则由子类完成。 - 提高代码的可维护性
优势:由于公共的代码逻辑集中在抽象类中,当需要修改这些公共逻辑时,只需在抽象类中进行修改,而不需要在每个子类中逐一修改。这大大减少了维护的复杂性和出错的概率。
示例:如果需要在订单处理流程中增加一个新的步骤,如记录日志,只需在抽象类的模板方法中添加这一步骤,所有子类都会自动继承这一修改。 - 增强代码的可读性和可理解性
优势:模板方法模式通过将算法的骨架和具体实现分离,使得代码结构更加清晰和有条理。这有助于开发者更好地理解系统的整体架构和各个部分的职责。
示例:在泡茶和泡咖啡的例子中,Beverage 类定义了整个流程的骨架,而 Tea 和 Coffee 类则分别实现了具体的步骤。这种结构使得代码更加直观,易于理解和维护。 - 提高代码的灵活性和扩展性
优势:模板方法模式允许子类在不改变算法骨架的前提下,通过重写抽象方法来实现特定的行为。这使得系统更加灵活,能够轻松应对需求的变化和扩展。
示例:如果需要增加一种新的饮料,如奶茶,只需创建一个新的子类 MilkTea,并实现具体的泡制步骤,而不需要修改现有的代码。 - 约束子类的行为
优势:通过在抽象类中定义抽象方法,模板方法模式可以强制子类实现这些方法,从而确保子类遵循一定的行为规范。这有助于避免子类的不一致实现,保证系统的稳定性和可靠性。
示例:在订单处理系统中,抽象类可以定义 validateOrder 和 processPayment 等抽象方法,迫使子类实现这些方法,确保每个子类都按照规定的流程处理订单。 - 便于算法的替换和扩展
优势:模板方法模式使得算法的骨架和具体实现分离,便于在不改变算法结构的情况下,通过继承和重写来替换或扩展算法的具体实现。
示例:如果需要改变订单处理流程中的某个步骤,如支付处理方式,只需在相应的子类中重写 processPayment 方法,而不需要修改整个流程的骨架。 - 提高系统的可测试性
优势:模板方法模式使得算法的骨架和具体实现分离,便于对算法的各个部分进行单独测试。这有助于提高系统的可测试性,确保每个部分都能正确工作。
示例:可以单独测试 Beverage 类中的公共方法,如 boilWater 和 pourInCup,也可以单独测试 Tea 和 Coffee 类中的具体实现方法,如 brew 和 addCondiments。 - 促进代码的规范化和标准化
优势:模板方法模式通过定义统一的算法骨架,促进了代码的规范化和标准化。这有助于团队协作和代码的统一管理,提高开发效率和代码质量。
示例:在大型项目中,使用模板方法模式可以确保各个模块遵循统一的设计规范,减少因设计不一致导致的问题。
四、缺点
- 灵活性受限
劣势:模板方法模式要求子类必须遵循父类定义的模板方法的结构,不能随意改变算法的骨架。这可能会限制子类的灵活性,使得子类在某些情况下难以实现特定的需求。
示例:如果父类定义的模板方法中某个步骤的顺序或逻辑不符合子类的实际需求,子类无法直接修改模板方法的结构,只能通过重写抽象方法来实现特定的行为。这可能会导致代码的复杂性增加,或者需要通过其他方式来实现需求。 - 增加类的数量
劣势:模板方法模式需要定义一个抽象类和多个子类,这会增加类的数量,从而增加系统的复杂性。在小型项目或简单场景中,这种增加的复杂性可能不值得。
示例:如果一个简单的算法只需要几种不同的实现,使用模板方法模式可能会显得过于繁琐。相比之下,使用简单的条件语句(如 if-else 或 switch)可能更加简洁和高效。 - 维护成本增加
劣势:由于模板方法模式涉及多个类和方法,维护和修改这些类的成本可能会增加。特别是当需要修改模板方法的结构时,可能需要同时修改多个子类,这会增加出错的概率和维护的难度。
示例:如果需要在模板方法中添加一个新的步骤,不仅需要修改抽象类,还需要确保所有子类都能正确地实现这个新步骤。这可能会导致维护工作量增加,尤其是在子类数量较多的情况下。 - 代码的耦合性增加
劣势:模板方法模式中,子类与抽象类之间的耦合性较高。如果抽象类中的方法或属性发生变化,可能会影响到所有子类的实现。这会增加系统的脆弱性,降低代码的可维护性。
示例:如果抽象类中的某个方法被修改或删除,所有依赖该方法的子类都需要进行相应的修改。这可能会导致连锁反应,增加系统的维护难度。 - 学习和理解成本增加
劣势:对于不熟悉模板方法模式的开发者来说,理解和学习这种模式可能需要一定的时间和精力。特别是在复杂的项目中,模板方法模式的使用可能会增加开发者的认知负担。
示例:如果一个项目中大量使用了模板方法模式,新加入的开发者需要花费时间来理解这些模式的结构和实现,这可能会影响开发效率。 - 不适合频繁变化的场景
劣势:如果算法的步骤或逻辑经常发生变化,使用模板方法模式可能会导致频繁的修改和维护。相比之下,使用其他设计模式(如策略模式)可能更加灵活和适应变化。
示例:在快速迭代的项目中,如果算法的步骤经常需要调整,使用模板方法模式可能会导致频繁的类结构修改,增加开发和维护的成本。 - 可能导致代码的冗余
劣势:虽然模板方法模式可以减少公共代码的重复,但在某些情况下,如果子类需要实现许多抽象方法,可能会导致代码的冗余。特别是当子类只需要少量的自定义行为时,这种冗余可能会更加明显。
示例:如果一个子类只需要修改模板方法中的一个步骤,但仍然需要实现所有抽象方法,这可能会导致代码的冗余和不必要的复杂性。
五、使用场景
- 业务流程处理
在业务系统中,许多业务流程具有固定的步骤,但某些步骤的具体实现可能因业务类型不同而有所差异。模板方法模式可以用来定义这些固定步骤的骨架,而将具体的实现留给子类。
示例:
订单处理流程:包括接收订单、验证订单、支付处理、发货、通知客户等步骤。这些步骤的顺序是固定的,但每个步骤的具体实现可能因订单类型(如普通订单、VIP订单、国际订单等)而不同。
用户注册流程:包括输入信息、验证信息、发送验证码、注册成功等步骤。这些步骤的顺序是固定的,但每个步骤的具体实现可能因注册方式(如邮箱注册、手机号注册、第三方登录等)而不同。 - 算法框架
在算法实现中,某些算法的步骤是固定的,但某些步骤的具体实现可能因具体问题而不同。模板方法模式可以用来定义算法的框架,而将具体的实现留给子类。
示例:
排序算法:许多排序算法(如冒泡排序、选择排序、插入排序等)的基本步骤是固定的,但具体的比较和交换操作可能不同。
搜索算法:许多搜索算法(如深度优先搜索、广度优先搜索等)的基本步骤是固定的,但具体的搜索策略可能不同。 - 数据访问操作
在数据访问层,许多数据操作(如插入、更新、删除、查询等)的基本步骤是固定的,但具体的实现可能因数据源(如数据库、文件、网络等)而不同。模板方法模式可以用来定义这些操作的框架,而将具体的实现留给子类。
示例:
数据库操作:包括连接数据库、执行SQL语句、处理结果集等步骤。这些步骤的顺序是固定的,但具体的实现可能因数据库类型(如MySQL、Oracle、SQL Server等)而不同。
文件操作:包括打开文件、读取数据、写入数据、关闭文件等步骤。这些步骤的顺序是固定的,但具体的实现可能因文件类型(如文本文件、二进制文件、XML文件等)而不同。
六、注意事项
- 明确算法骨架
注意:在使用模板方法模式时,必须明确算法的骨架,即整个流程的步骤和顺序。这需要在设计阶段仔细分析和规划,确保算法的骨架能够涵盖所有可能的实现。
建议:在设计模板方法时,尽量将算法的骨架设计得通用和灵活,以便子类可以根据需要进行扩展和定制。 - 合理定义抽象方法
注意:抽象方法是子类实现具体逻辑的地方,必须合理定义抽象方法的签名和职责。如果抽象方法定义得不合理,可能会导致子类实现困难或不一致。
建议:抽象方法的签名应尽量简洁明了,职责单一,避免过于复杂或模糊的定义。同时,可以提供一些默认实现,减少子类的实现负担。 - 避免过度设计
注意:模板方法模式可能会增加类的数量和复杂性,如果过度使用,可能会导致系统变得臃肿和难以维护。
建议:在使用模板方法模式时,应根据实际需求进行权衡,避免过度设计。如果算法的步骤和逻辑相对简单,可以考虑使用其他更简单的设计模式或直接使用条件语句来实现。 - 确保子类的实现一致性
注意:模板方法模式要求子类必须实现抽象方法,以确保算法的骨架能够正常运行。如果子类没有正确实现抽象方法,可能会导致算法无法正常执行。
建议:在设计抽象类时,应提供清晰的文档和示例代码,指导子类如何正确实现抽象方法。同时,可以通过单元测试和代码审查来确保子类的实现一致性。 - 处理算法的变体
注意:在某些情况下,算法的某些步骤可能会有多种变体,需要根据具体情况进行选择。如果这些变体没有得到妥善处理,可能会导致算法的灵活性和可扩展性受到影响。
建议:可以通过增加抽象方法或使用其他设计模式(如策略模式)来处理算法的变体,提高算法的灵活性和可扩展性。 - 注意类的耦合性
注意:模板方法模式中,子类与抽象类之间的耦合性较高。如果抽象类中的方法或属性发生变化,可能会影响到所有子类的实现。
建议:在设计抽象类时,应尽量保持其稳定性,避免频繁修改。如果需要修改抽象类,应仔细评估对子类的影响,并及时通知相关开发者。 - 考虑性能影响
注意:模板方法模式可能会增加方法调用的开销,特别是在算法步骤较多或调用频繁的情况下,可能会影响性能。
建议:在性能敏感的场景中,应仔细评估模板方法模式的性能影响,必要时可以考虑优化算法或使用其他设计模式。 - 保持代码的可读性和可维护性
注意:模板方法模式可能会增加代码的复杂性,如果代码结构不清晰,可能会导致可读性和可维护性下降。
建议:在使用模板方法模式时,应保持代码结构清晰,注释详细,逻辑分明。可以通过合理的命名、分层和模块化来提高代码的可读性和可维护性。 - 避免滥用模板方法模式
注意:模板方法模式并不是万能的,如果滥用可能会导致设计过于复杂,增加开发和维护的难度。
建议:在使用模板方法模式时,应根据实际需求和场景进行选择,避免滥用。如果其他设计模式或简单的实现方式能够满足需求,应优先考虑。 - 结合其他设计模式使用
注意:模板方法模式可以与其他设计模式(如策略模式、工厂方法模式等)结合使用,以提高系统的灵活性和可扩展性。
建议:在设计系统时,可以根据实际需求和场景,灵活地结合使用多种设计模式,以达到最佳的设计效果。
七、在spring 中的应用
- JdbcTemplate
JdbcTemplate 是 Spring 中用于简化 JDBC 操作的类,它使用了模板方法模式来封装 JDBC 的操作流程。JdbcTemplate 定义了整个 JDBC 操作的骨架,包括连接获取、SQL 执行、结果集处理等步骤,而具体的 SQL 语句和结果集处理逻辑由用户通过回调接口实现。 - AbstractController
AbstractController 是 Spring MVC 中的一个抽象类,它使用了模板方法模式来定义控制器的处理流程。AbstractController 定义了 handleRequestInternal 方法作为模板方法,该方法定义了处理请求的骨架,而具体的请求处理逻辑由子类实现。
相关文章:
Java 与设计模式(15):模板方法模式
一、定义 模板方法模式是一种行为设计模式,它定义了一个操作中的算法的骨架(也就是大致的步骤和流程),而将一些具体步骤的实现延迟到子类中。这样,子类可以不改变算法的结构即可重新定义算法的某些特定步骤。 二、Ja…...
Arduino 第十六章:pir红外人体传感器练习
Arduino 第十六章:PIR 传感器练习 一、引言 在 Arduino 的众多有趣项目中,传感器的应用是非常重要的一部分。今天我们要学习的主角是 PIR(被动红外)传感器。PIR 传感器能够检测人体发出的红外线,常用于安防系统、自动…...
C++中间件DDS介绍
C DDS 库简介 DDS(Data Distribution Service) 是一种用于实时分布式系统通信的中间件标准,由 OMG(Object Management Group) 提出。它是一种发布/订阅(Publish/Subscribe)模式的数据通信框架&…...
自动化之ansible(二)
一、ansible中playbook(剧本) 官方文档: Ansible playbooks — Ansible Community Documentation 1、playbook的基本结构 一个基本的playbook由以下几个主要部分组成 hosts: 定义要执行任务的主机组或主机。 become: 是否需要使用超级用户…...
QSNCTF-WEB做题记录
第一题,文章管理系统 来自 <天狩CTF竞赛平台> 描述:这是我们的文章管理系统,快来看看有什么漏洞可以拿到FLAG吧?注意:可能有个假FLAG哦 1,首先观察题目网站的结构和特征 这个一个文件管理系统&#x…...
Ruoyi-Vue 3.8.7集成积木报表JmReport和积木大屏JimuBI
Ruoyi-Vue 3.8.7集成积木报表JmReport和积木大屏JimuBI 一、版本 RuoYi-Vue版本:v3.8.7 JMreport报表版本: v1.9.4 JimuBI大屏版本:V1.9.4 二、数据库 积木数据库sql 下载后,使用数据库管理工具执行sql脚本,将需…...
OSPF(开放路径最短优先)
ospf优先级:内部优先级默认为10,外部优先级默认为150 1.ospf的三张表 (1)邻居表 <记录邻居状态和关系> (2)拓扑表 <链路状态数据库> (3)路由表 <对链路状态数据库进…...
请谈谈 Vue 中的响应式原理,如何实现?
一、Vue2响应式原理:Object.defineProperty的利与弊 实现原理: // 数据劫持核心实现 function defineReactive(obj, key, val) {const dep new Dep(); // 依赖收集容器Object.defineProperty(obj, key, {get() {if (Dep.target) { // 当前Watcher实例…...
亲测可用,IDEA中使用满血版DeepSeek R1!支持深度思考!免费!免配置!
作者:程序员 Hollis 之前介绍过在IDEA中使用DeepSeek的方案,但是很多人表示还是用的不够爽,比如用CodeChat的方案,只支持V3版本,不支持带推理的R1。想要配置R1的话有特别的麻烦。 那么,今天,给…...
jvm中各个参数的理解
MEMORY - MANAGERS 定义 MEMORY - MANAGERS即内存管理器,它是操作系统或软件系统中负责管理计算机内存资源的组件。从本质上来说,它是一种软件机制,旨在协调计算机系统中内存的分配、使用和回收等操作,确保系统能够高效、稳定地…...
【队列】循环队列(Circular Queue)详解
文章目录 一、循环队列简介二、循环队列的判空和判满三、循环队列的实现leetcode 622. 设计循环队列 一、循环队列简介 在实际开发中,队列是一种常用的数据结构,而循环队列(Circular Queue)则一般是一种基于数组实现的队列&#x…...
Deepseek快速做PPT
背景: DeepSeek大纲生成 → Kimi结构化排版 → 数据审查,细节调整 DeepSeek 拥有深度思考能力,擅长逻辑构建与内容生成,它会根据我们的问题进行思考,其深度思考能力当前测试下来,不愧为国内No.1,而且还会把中间的思考过程展示出来,大多时候会给出很多我们意想不到的思…...
DeepSeek掀起推理服务器新风暴,AI应用迎来变革转折点?
AI 浪潮下,推理服务器崭露头角 在科技飞速发展的当下,AI 是耀眼明星,席卷各行业,深刻改变生活与工作模式,从语音助手到医疗诊断、金融风险预测,AI 无处不在。其发展分数据收集整理、模型训练、推理应用三个…...
离线部署大模型:ollama+deepseek+open-webui
ollama 是一个开源的本地大语言模型运行框架,它提供了非常简单便捷的使用形式,让用户可以十分方便的在本地机器上部署和运行大型语言模型,从而实现免费离线的方式使用 LLM 能力,并确保私有数据的隐私和安全性。 1 ollama 安装 o…...
深入解析浏览器渲染全流程:从URL输入到页面渲染的底层原理与性能优化(附实战代码)
本文以https://example.com为例,逐层剖析浏览器从输入URL到页面渲染的完整链路,涵盖DNS解析、TCP/TLS握手、HTTP请求、DOM/CSSOM构建等核心阶段,结合代码示例与性能调优技巧,助你掌握浏览器底层运行机制。 一、导航阶段࿱…...
现代游戏UI架构深度解析——以UIController为核心的模块化界面管理系统
一、架构全景与设计哲学 本文将以重构后的UIController为核心,深入探讨Unity引擎下的高效UI管理方案。该体系采用"分层-分治"设计理念,通过界面生命周期管理、动态适配策略、资源优化机制三个维度的协同工作,构建了适应复杂交互需…...
Vue 项目中逐步引入 TypeScript 的类型检查
在现有的 Vue 项目中逐步引入 TypeScript 的类型检查 本文源于一道面试题:注:两种问法一个意思哈!! 问题一:“ 老项目Js写的,如何轻量方式享受 ts 类型?” 问题二:“如何 在现有的 …...
Git企业开发
Git(版本控制器) 在我们对于文档进行操作的时候,很多时候可能会出现多个文档,对这些文档进行多个版本的保存和记录就变成必要的。通俗的讲,就是记录每次的修改和记录版本迭代的管理系统。目前最主流的版本控制器就是G…...
DeepSeek预测25考研分数线
25考研分数马上要出了。 目前,多所大学已经陆续给出了分数查分时间,综合往年情况来看,每年的查分时间一般集中在2月底。 等待出成绩的日子,学子们的心情是万分焦急,小编用最近爆火的“活人感”十足的DeepSeek帮大家预…...
备战蓝桥杯 -牛客
习题-[NOIP2006]明明的随机数 1046-习题-[NOIP2006]明明的随机数_2021秋季算法入门班第一章习题:模拟、枚举、贪心 思路:这道题用stl的set,今天写这道题复习了一下set的用法: s.find(a) s.end()的意思是判断元素a是否存在于集…...
基于springboot校园健康系统的设计与实现(源码+文档)
大家好我是风歌,今天要和大家聊的是一款基于springboot的园健康系统的设计与实现。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 基于springboot校园健康系统的设计与实现的主要使用者管理员具有最高的权限,通…...
出现 [ app.json 文件内容错误] app.json: 在项目根目录未找到 app.json (env: Windows,mp 解决方法
目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 hbuilder X 执行代码的时候出现如下所示 [ app.json 文件内容错误] app.json: 在项目根目录未找到 app.json (env: Windows,mp,1.06.2412050; lib:...
设计模式教程:责任链模式(Chain of Responsibility Pattern)
责任链模式(Chain of Responsibility Pattern)是一种常用的设计模式,它属于行为型模式,主要解决的是多个对象处理一个请求时,如何解耦请求的发送者和接收者,以及如何将请求的处理职责分配给不同的对象。 1…...
【YOLOv8】损失函数
学习视频: yolov8 | 损失函数 之 5、类别损失_哔哩哔哩_bilibili yolov8 | 损失函数 之 6、定位损失 CIoU DFL_哔哩哔哩_bilibili 2.13、yolov8损失函数_哔哩哔哩_bilibili YOLOv8 的损失函数由类别损失和定位损失构成 类别损失:BCE Loss 定位损失…...
ollama修改监听ip: 0.0.0.0
确认Ollama绑定IP地址 默认情况下,Ollama可能仅监听本地回环地址(127.0.0.1)。要允许外部访问,需将其配置为监听所有IP(0.0.0.0)或指定IP(如10…19)。 修改启动命令(推荐…...
【Linux】【网络】Libevent 内部实现简略版
【Linux】【网络】Libevent 内部实现简略版 1 event_base结构–>相当于Reactor 在使用libevent之前,就必须先创建这个结构。 以epoll为例: 1.1evbase void* evbase-->epollop结构体(以epoll为例) libevent通过一个void…...
计算机网络抄手 运输层
一、运输层协议概述 1. 进程之间的通信 从通信和信息处理的角度看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。当网络边缘部分的两台主机使用网络核心部分的功能进行端到端的通信时&…...
MATLAB图像处理:图像分割方法
图像分割将图像划分为具有特定意义的子区域,是目标检测、医学影像分析、自动驾驶等领域的核心预处理步骤。本文讲解阈值分割、边缘检测、区域生长、聚类分割、基于图的方法等经典与前沿技术,提供MATLAB代码实现。 目录 1. 图像分割基础 2. 经典分割方…...
【机器学习】线性回归 多元线性回归
多元线性回归 V1.1多元线性回归一元线性回归与多元线性回归多元线性回归模型的误差衡量多元线性回归的最优解多元线性回归的解析解(标准数学解法)多元线性回归的解析解公式分析 多元线性回归的搜索解法 V1.1 加入链接会影响文章推荐权重,阅读…...
【VSCode】MicroPython环境配置
【VSCode】MicroPython环境配置 RT-Thread MicroPython 插件安装MicroPython 库文件配置结束语 RT-Thread MicroPython 插件安装 在 VSCode 拓展中搜索 “RT-Thread MicroPython” 并安装,详细配置步骤(修改 VSCode 默认终端、MicroPython 代码补全&…...
