Java设计模式-模板方法模式

Java设计模式-模板方法模式

模式概述

模板方法模式简介

核心思想:定义一个操作中的算法骨架(模板方法),将算法中某些步骤的具体实现延迟到子类中完成。子类可以在不改变算法整体结构的前提下,重定义这些步骤的行为,从而实现代码复用与扩展的平衡。

模式类型:行为型设计模式(关注对象间的交互与职责分配)。

作用

  • 复用公共逻辑:将多个子类共有的算法步骤提取到父类,避免代码重复。
  • 提高扩展性:子类仅需实现差异化的步骤,符合“开闭原则”(对扩展开放,对修改关闭)。
  • 规范流程:父类通过模板方法固定算法的整体结构,确保子类行为的一致性。

典型应用场景

  • 多个子类有公共的行为逻辑,但部分步骤实现不同(如数据库访问:连接、执行SQL、关闭连接的流程固定,但不同数据库的驱动实现不同)。
  • 框架中需要控制子类的执行流程(如Spring的JdbcTemplate封装了JDBC操作的通用流程,具体SQL执行由子类或回调实现)。
  • 需要约束子类的行为,确保关键步骤不被遗漏(如订单处理流程:下单→支付→发货→通知,其中支付方式可自定义)。

我认为:模板方法模式是“流程标准化”与“步骤定制化”的完美结合,父类搭骨架,子类填细节。

课程目标

  • 理解模板方法模式的核心思想和经典应用场景
  • 识别应用场景,使用模板方法模式解决功能要求
  • 了解模板方法模式的优缺点

核心组件

角色-职责表

角色职责示例类名
抽象模板角色定义模板方法(算法骨架)和基本方法(具体方法、抽象方法、钩子方法)AbstractBeverageMaker
具体模板角色继承抽象模板角色,实现所有抽象方法,并可选重写钩子方法CoffeeMakerTeaMaker

类图

下面是一个简化的类图表示,展示了模板方法模式中的主要角色及其交互方式:

继承
继承
«abstract»
AbstractBeverageMaker
+final void makeBeverage()
-void boilWater()
-abstract void brew()
-void pourInCup()
-boolean needAddCondiments()
-void addCondiments()
CoffeeMaker
+void brew()
+boolean needAddCondiments()
+void addCondiments()
TeaMaker
+void brew()

传统实现 VS 模板方法模式

案例需求

案例背景:实现饮料制作功能(如咖啡、茶),通用流程为:烧水→冲泡→倒入杯子→添加调料(可选)。不同饮料的冲泡方式(如咖啡粉 vs 茶叶)和调料添加(如加糖 vs 不加)不同。

传统实现(痛点版)

代码实现

// 传统实现:每个饮料独立编写完整流程
class CoffeeMaker {public void makeCoffee() {boilWater();  // 重复代码brewCoffee(); // 咖啡特有逻辑pourInCup();  // 重复代码addSugar();   // 咖啡特有逻辑}private void boilWater() {System.out.println("烧水:煮沸100℃");}private void brewCoffee() {System.out.println("冲泡:用热水冲咖啡粉");}private void pourInCup() {System.out.println("倒入杯子");}private void addSugar() {System.out.println("添加:糖和牛奶");}
}class TeaMaker {public void makeTea() {boilWater();  // 重复代码brewTea();    // 茶叶特有逻辑pourInCup();  // 重复代码// 茶不需要调料,无需添加}private void boilWater() {System.out.println("烧水:煮沸100℃"); // 重复代码}private void brewTea() {System.out.println("冲泡:用热水泡茶叶");}private void pourInCup() {System.out.println("倒入杯子"); // 重复代码}
}

痛点总结

  • 代码冗余boilWater()pourInCup()等方法在每个子类中重复实现。
  • 扩展性差:新增饮料(如果汁)需复制大量重复代码,违反开闭原则。
  • 流程不可控:无法保证所有饮料遵循相同的基础流程(如漏掉“倒入杯子”步骤)。

模板方法模式 实现(优雅版)

代码实现

// 抽象模板角色:定义流程骨架
abstract class AbstractBeverageMaker {// 模板方法(final修饰,防止子类修改流程)public final void makeBeverage() {boilWater();brew();       // 调用抽象方法(子类实现)pourInCup();if (needAddCondiments()) {  // 调用钩子方法(控制是否添加调料)addCondiments();}}// 具体方法(通用逻辑)private void boilWater() {System.out.println("烧水:煮沸100℃");}// 抽象方法(子类必须实现)protected abstract void brew();// 具体方法(通用逻辑)private void pourInCup() {System.out.println("倒入杯子");}// 钩子方法(默认不添加调料,子类可选重写)protected boolean needAddCondiments() {return false;}// 钩子方法关联的具体操作(子类可选重写)protected void addCondiments() {// 默认空实现}
}// 具体模板角色:咖啡制作
class CoffeeMaker extends AbstractBeverageMaker {@Overrideprotected void brew() {System.out.println("冲泡:用热水冲咖啡粉");}@Overrideprotected boolean needAddCondiments() {return true;  // 咖啡需要添加调料}@Overrideprotected void addCondiments() {System.out.println("添加:糖和牛奶");}
}// 具体模板角色:茶叶制作
class TeaMaker extends AbstractBeverageMaker {@Overrideprotected void brew() {System.out.println("冲泡:用热水泡茶叶");}// 不重写needAddCondiments(),默认不添加调料
}

优势

  • 消除冗余:公共方法(如boilWater())在抽象类中实现,子类无需重复。
  • 流程可控:模板方法通过final修饰,确保子类无法修改基础流程。
  • 灵活扩展:子类仅需实现抽象方法(如brew()),并通过钩子方法(needAddCondiments())控制可选逻辑。

局限

  • 类数量增加:每个差异化的子类需单独定义,可能增加系统复杂度。
  • 抽象类设计成本:需合理规划抽象方法与钩子方法,过度设计可能导致冗余。

模式变体

  • 具体模板方法:将模板方法声明为final,禁止子类修改算法骨架(强制遵循固定流程)。
  • 钩子方法(Hook Method):提供默认实现的方法(通常返回布尔值或空操作),子类可选择是否重写以影响模板方法的行为(如上述案例中的needAddCondiments())。
  • 参数化模板:在模板方法中添加参数,允许子类通过参数调整行为(如数据库操作模板支持传入事务隔离级别)。

最佳实践

建议理由
抽象类保持稳定模板方法模式的核心是流程固定,频繁修改抽象类会导致所有子类连锁改动。
钩子方法提供默认实现减少子类必须重写的负担,仅当需要差异化时才覆盖。
避免过度抽象若子类间差异极小(如仅有1个步骤不同),可能更适合直接继承而非模板模式。
模板方法用final修饰防止子类意外修改算法骨架,确保流程一致性。

一句话总结

模板方法模式通过“父类定义流程骨架,子类实现差异化步骤”,在保证流程规范的同时,实现了代码复用与灵活扩展。

如果关注Java设计模式内容,可以查阅作者的其他Java设计模式系列文章。😊

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

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

相关文章

Centos7物理安装 Redis8.2.0

Centos7物理安装 Redis8.2.0一、准备依赖环境首先安装编译 Redis 所需的依赖:# CentOS/RHEL系统 yum install -y gcc gcc-c make wget 二、下载并编译 Redis 8.2.0# 1. 下载Redis 8.2.0源码包 wget https://download.redis.io/releases/redis-8.2.0.tar.gz# 2. 解压…

牛津大学xDeepMind 自然语言处理(3)

条件语言模型无条件语言模型 概率计算:通过链式法则分解为预测下一词概率(将语言建模问题简化为建模给定前面词语历史的下一个词的概率)基于循环神经网络的无条件语言模型:根据历史词语预测下一个词的概率条件语言模型 定义&#…

Vue2.x核心技术与实战(一)

目录 一、Vue2.x:快速上手+插值表达式+指令上 1.1 Vue快速上手 1.1.1 Vue概念 1.1.2 创建实例 1.1.3 插值表达式 { { }} 1.1.4 响应式特性 1.1.5 开发者工具 1.2 Vue指令 1.2.1 v-html 1.2.3 v-show / v-if v-show v-if 1.2.4 v-else / v-else-if 1.2.5 v-on v…

SCAU学习笔记 - 自科三面前端方向实战演示

本来是准备写完二面直接开始写算法三面的,maimai那个封面图我都做好了。但是可恶的出题人说要等我出完解析再针对性避开出题,所以swan决定把那个先搁置,本文我们先以2023年的自科三面前端方向题为例带各位快速入门前端三件套(因为…

前后端联合实现文件上传,实现 SQL Server image 类型文件上传

1、前端 Vue3QualityFileInfoDialog.vue<script setup lang"ts" name"QualityFile"> ...... // 上传&#xff0c;防抖 const onUploadClick debounce(() > {// 模拟点击元素if (fileInputRef.value) {// 重置以允许重复选择相同文件fileInputRef…

使用安卓平板,通过USB数据线(而不是Wi-Fi)来控制电脑(版本1)

这是一个对延迟和稳定性要求很高的场景。 核心原理是&#xff1a;利用USB数据线&#xff0c;在手机和电脑之间创建一个高速的“虚拟网络连接”&#xff0c;然后在这个稳定的网络通道上运行远程控制软件。 方案1&#xff1a; 在完全没有无线网络&#xff08;Wi-Fi&#xff09;和…

linux报permission denied问题

linux报permission denied问题 一般是没有可执行权限&#xff0c;需要先添加执行权限 1. 确认文件权限 在你的项目目录下执行&#xff1a; ls -l ./folder你可能会看到类似&#xff1a; -rw-r--r-- 1 user user 1234 Aug 18 12:00 script.sh注意&#xff1a;这里缺少 x&#xf…

Vue深入组件:组件事件详解2

声明触发的事件 为了让组件的用法更清晰(作为文档),同时让 Vue 能区分事件与透传 attribute,推荐显式声明组件要触发的事件。根据组件是否使用 <script setup>,声明方式有所不同。 使用 <script setup> 时:defineEmits() 宏 在 <script setup> 中,…

FLASK项目快速构建

Flask 项目构建 exts.py # flask_sqlalchemy from flask_sqlalchemy import SQLAlchemy from flask_mail import Mail from flask_caching import Cache from flask_wtf import CSRFProtect from flask_avatars import Avatars from flask_jwt_extended import JWTManager from…

数据结构--2:ArrayList与顺序表

1.顺序表的创建 2.常见操作 3.遍历 4.扩容机制 5.例子1.顺序表的创建在集合框架中&#xff0c;ArrayList是⼀个普通的类&#xff0c;实现了List接口&#xff0c;具体框架图如下&#xff1a;2.常见操作代码…

【Kubesphere】K8s容器无法访问内网xx网络问题

问题遇到的现象和发生背景 Kubesphere中运行的一个容器&#xff0c;可以ping通我们公司内网网段172.16.XX.XX&#xff0c;但是在容器内无法ping通192.168.5.XX&#xff0c;但是我在宿主机是可以ping通192.168.5.XX&#xff0c;这个192.168.5.XX是通过xx设备接进来的&#xff0c…

【开发语言】Groovy语言:Java生态中的动态力量

博客目录一、Groovy 的诞生与发展二、核心特性深度解析1. 与 Java 的无缝集成2. 动态类型与可选静态类型3. 强大的集合操作三、Groovy 在实际开发中的应用场景1. 构建自动化&#xff08;Gradle&#xff09;2. 测试开发&#xff08;Spock 框架&#xff09;3. 脚本任务自动化四、…

Obsidian 1.9.10升级

概述 Obsidian发布了更新版本1.9.10&#xff0c;是一次比较大的升级&#xff0c;尤其是增加了一些以前没有的核心插件&#xff0c;尤其是重磅的数据库功能。虽然可能还是比较初期&#xff0c;但是这意味着OB还是往更好的方向进化了。 本文以一些目前的视频教程加自己的实际上手…

内容审计技术

一、 内容审计需求背景1.网络安全法要求明确责任人&#xff1a;制定内部安全管理制度和操作规程&#xff0c;落实安全保护责任。监测、记录并保留日志&#xff1a;采取监测、记录网络运行状态、网络安全事件的技术措施&#xff0c;并按照规定留存相关网络日志不少于六个月。采取…

反序列化漏洞

php反序列化 1.什么是序列化和反序列化 office word是程序 doc/docx是数据 保存word文件&#xff1a;程序--保存(序列化)-->数据文件 打开word文件&#xff1a;程序--加载数据文件-->还原(反序列化) 游戏存档&#xff1a;角色等级&#xff0c;任务&#xff0c;人物坐…

Lecture 4 Mixture of experts课程笔记

什么是MoE?用&#xff08;多个&#xff09;大型前馈网络和一个选择器层取代大型前馈网络。你可以在不影响浮点运算次数的情况下增加专家数量。 MoE受欢迎的原因 相同的浮点运算次数&#xff0c;更多的参数表现更好训练混合专家模型&#xff08;MoEs&#xff09;速度更快训练混…

微服务架构的演进:从 Spring Cloud Netflix 到云原生新生态

过去十年,Spring Cloud 凭借 Netflix 全家桶(Eureka、Ribbon、Hystrix、Zuul 等)几乎成为 Java 微服务的事实标准。但随着这些核心组件逐步停止更新或进入维护模式,微服务架构正经历一场深刻的演进。新的微服务架构更加注重 云原生兼容性、社区活跃度、企业级稳定性和低运维…

网络流量分析——基础知识

文章目录所需技能和知识TCP/IP 堆栈和 OSI 模型基本网络概念常用端口和协议IP 数据包和子层的概念协议传输封装环境与设备常见的流量分析工具BPF 语法执行网络流量分析NTA工作流程NTA工作流程网络 - 第 1-4 层OSI / TCP-IP 模型寻址机制MAC地址IP 寻址IPv4IPv6IPv6 寻址类型IPv…

ansible playbook 实战案例roles | 实现基于 IHS 的 AWStats 访问监控系统

文章目录一、核心功能描述二、roles内容2.1 文件结构2.2 主配置文件2.3 tasks文件内容三、files文件内容四、关键价值免费个人运维知识库&#xff0c;欢迎您的订阅&#xff1a;literator_ray.flowus.cn 一、核心功能描述 这个 Ansible Role 的核心功能是&#xff1a;​实现 ​…

DELL服务器 R系列 IPMI的配置

1、iDRAC功能默认都是关闭&#xff0c;需要在BIOS面启用&#xff0c;首先重启计算机&#xff0c;按F2然后进入BIOS&#xff0c;选择iDRAC Setting进行iDRAC配置 2、重置一下idrac卡-重置才能恢复默认密码 3、进入iDRAC Setting之后&#xff0c;选择设置网络Network 4、启用iDRA…