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…...
ubuntu更新失败:apt-get install -f Transaction failed: 软件包系统已损坏
检查您是否使用了第三方源。如果是就禁用它们,它们常常导致问题。 然后在终端中运行以下命令:apt-get install -f Transaction failed: 软件包系统已损坏下列软件包未满足的依赖关系:sunloginclient: Depends: libappindicator3-1 但是 %%s 没…...
16-使用QtChart创建动态图表:入门指南
QtChart是Qt框架中的一个强大模块,用于创建各种类型的图表,如折线图、柱状图、饼图等。它提供了丰富的API和灵活的配置选项,使得开发者能够轻松地将数据可视化集成到应用程序中。本文将介绍如何使用QtChart创建一个简单的动态折线图ÿ…...
C++ | 虚函数
在 C 面向对象编程领域,多态性堪称核心概念,而虚函数则是实现运行时多态的关键所在。 一、虚函数的概念与作用 1.1 什么是虚函数 虚函数是 C 中用于实现动态多态的成员函数。在基类中使用virtual关键字声明虚函数后,派生类能够重写&#x…...
单元测试整理
在国外软件开发中,单元测试必不可少,但是国内并不太重视这一块,一个好的单元测试可以提前发现很多问题,也减去和测试battle的时间 Spring单元测试 JUnit4 RunWith 指明单元测试框架 e.g. RunWith(SpringJUnit4ClassRunner.cla…...
Delphi语言的软件工程
Delphi语言的软件工程 引言 在软件工程的历史长河中,Delphi语言作为一种快速应用程序开发(RAD)的工具,凭借其高效的开发环境和强大的编程能力,一直在软件开发领域占有一席之地。本文将探讨Delphi语言的历史背景、特性…...
XSS攻击(跨站脚本攻击)详解与实战
文章目录 一、什么是XSS?二、XSS分类与场景三、XSS攻击实战流程四、CTF中的XSS利用五、XSS防御方案六、绕过过滤的常见技巧七、实战练习资源 一、什么是XSS? XSS(Cross-Site Scripting) 是一种通过向网页注入恶意脚本(…...

【C++指南】类和对象(十):const成员函数
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 引言 一、const成员函数的定义与语法 1. 基本语法 2. 底层原理 二、const成员函数的作用与约束…...
数值分析与科学计算导引——误差与算法举例
文章目录 第一章 数值分析与科学计算导引1.1 数值分析的对象、作用与特点数值分析的对象数值分析的作用数值分析的特点 1.2 数值计算的误差误差分类误差与有效数字数值运算的误差估计 1.3 算法举例秦九韶算法求多项式值开根号迭代算法牛顿切线加权平均的松弛技术 第一章 数值分…...
ubuntu安装docker 无法拉取问题
sudo docker run hello-world [sudo] ubuntu 的密码: Unable to find image hello-world:latest locally docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded (Client.Timeout exceeded while awai…...

【C++项目】Rpc通信框架设计
目录 Rpc远程调用的思想 项目框架设计 服务端模块划分 网络通信模块 Network 应用层通信协议模块 Protocol 消息分发处理模块 Dispatcher 远程调用路由功能模块 RpcRouter 编辑 发布订阅功能模块 Publish-Subscribe 服务注册/发现/上线/下线功能模块 Registry-Disc…...
八股取士--dockerk8s
一、Docker 基础 Docker 和虚拟机的区别是什么? 答案: 虚拟机(VM):虚拟化硬件,每个 VM 有独立操作系统,资源占用高,启动慢。Docker:容器化应用,共享宿主机内核…...
Autojs: 使用 SQLite
例子 let db new SQLiteUtil("/sdcard/A_My_DB/sqlite.db");db.fastCreateTable("user_table",{name: "",online: false,},["name"] // 设置 name 为唯一, 重复项 不会添加成功 );// 新增数据的 ID let row_id db.insert("use…...
思科、华为、H3C常用命令对照表
取消/关闭 思科no华为undo华三undo 查看 思科show华为display华三display 退出 思科exit华为quit华三quit 设备命名 思科hostname华为sysname华三sysname 进入全局模式 思科enable、config terminal华为system-view华三system-view 删除文件 思科delete华为delete华…...

解决 `pip is configured with locations that require TLS/SSL` 错误
问题描述 在使用 pip 安装 Python 包时,可能会遇到以下错误: WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.这意味着 Python 的 ssl 模块未正确安装或配置,导致 p…...

2025-arXiv-OmniThink:通过思考扩展机器写作的知识边界
arXiv | https://arxiv.org/abs/2501.09751 GitHub | https://github.com/zjunlp/OmniThink 项目主页 | https://zjunlp.github.io/project/OmniThink/ ModelScope 在线 Demo | https://www.modelscope.cn/studios/iic/OmniThink 摘要: 大语言模型驱动的机器写作通…...

【广州大学主办,发表有保障 | IEEE出版,稳定EI检索,往届见刊后快至1个月检索】第二届电气技术与自动化工程国际学术会议 (ETAE 2025)
第二届电气技术与自动化工程国际学术会议 (ETAE 2025) The 2nd International Conference on Electrical Technology and Automation Engineering 大会官网:http://www.icetae.com/【更多详情】 会议时间:2025年4月25-27日 会议地点:…...

机器学习:01数学基础教程
函数 极限 按照一定次数排列的一列数:“,“,…,"…,其中u 叫做通项。 对于数列{Un}如果当n无限增大时,其通项无限接近于一个常数A,则称该数列以A为极限或称数列收敛于A,否则称数列为发散, 极限值 左…...

仿叮咚买菜鸿蒙原生APP
# DingdongShopping 这是一个原生鸿蒙版的仿叮咚买菜APP项目 鸿蒙Next发布至今已经有一年多的时间了,但有时候我们想要实现一些复杂的功能或者效果,在开发文档上查阅一些资料还是比较费时的,有可能还找不到我们想要的内容。而社会层面上分享…...

WordPress“更新失败,响应不是有效的JSON响应”问题的修复
在使用WordPress搭建网站时,许多人在编辑或更新文章时,可能会遇到一个提示框,显示“更新失败,响应不是有效的JSON响应”。这个提示信息对于不了解技术细节的用户来说,太难懂。其实,这个问题并不复杂&#x…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...

群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...