设计模式-模版方法模式
生活中处处存在模版,模版定义了大的框架,具体内容由使用者填充即可,这给很多人的生活、工作带来了很大的遍历。比如:
- PPT模版:好的PPT模版提供了更全面的叙述框架,更优美的UI画面&图标,提升用户的PPT制作水平
- 技术方案模版:通过使用技术方案模版,确定需求应用的各个方面,如需求背景、上下游业务方、存储层设计、接口设计、接口性能评估等。通过完善的模版,技术方案制作时能够考虑的更加全面。
- …
你也可以想象生活中处处可见的其他“模版”,这是一个标准化的过程。在开发技术栈中,spring、mybatic等框架其实也是模版的体现,通过标准化模版式的约定简化开发成本,提升开发效率。因此,我们该怎么通过面向对象语言来体现这种标准化的过程(模版)呢?答案就是模版方法模式。
一、模版方法模式
先思考下实现这种标准化过程的模板类应该具有哪些职责?① 明确指定了标准化过程都有哪些子元素?② 这些子元素在模版中如何组织 。这两点也是我们去评价是否使用这套模版的核心问题,如Python项目不会去考虑使用含有"JVM内存分析"的技术方案模版、产品在写文档时也不会去考虑开发技术方案等。
再考虑下,模板类中“子元素”应该有模板类指定吗?这种具体问题具体分析,有些模版会给个默认示例,有的模版会置之为空,完全由用户来实现。当然,即使是前者,模版肯定也是允许用户重写或覆盖的。所以,这么一说,在代码实现上是否非常适合抽象类呢。
总结下抽象模版类的设计:
- 明确了一组标准化过程(或算法框架)-模版方法(templateMethod)
- 给出默认或要求用户实现的过程子元素(或步骤)-基本方法([action1(), action2(),…])
抽象模板类结构确定之后,那其具体实现类,即其子类又该注意哪些问题。对于模板类的基本方法而言,实现类必须实现其所有的抽象方法(子类必须实现),非抽象方法视业务要求而定是否需要重写。那问题在于子类(具体实现类)是否应该重写抽象模板类已经确定的模版方法(标准化过程)?我觉得可以通过下面两个方面分析:
- 模版含义:从模版的含义上来看,其实际是一套标准化过程,具体实现应该尽可能遵守这套流程,才能达到模版效果及预期。同时,结合实际情况看,也是允许具体实现时删除或更改流程顺序,毕竟任何东西都不能是死的要灵活应用。但一定要注意,这里的灵活也仅是小范围的改动,否则,就不用使用这套模版了。
- 设计原则:如果子类重写了模版方法,更改了标准化流程,子类重写方法与模板类的模版方法业务含义不一致了,因此不满足里氏替换原则。
结合上面两点来看,子类不应该重写模版方法(模版方法通常使用final关键字),但是如何满足具体实现类可控范围内的灵活性呢?子类不允许修改模版方法的逻辑,但是模板类可以向子类提供修改模版方法的能力,即钩子方法。模板类可以通过由子类实现或重写的钩子方法来改变标准化过程。
总结下具体子类的设计:
- 必须实现模板类所有的抽象基本方法
- 不允许重写模板类中的模版方法,但可通过模板类的钩子方法来控制模版方法的标准化流程。
到目前我们已经可以给出模板类的大致类图如下:

二、应用实践
模版方法的应用场景非常广泛,只要你能够对一件事情总结出一套标准化的流程,那就可以使用模版方法来实现之。我这里就给出一个开发过程中比较贴近的场景:随着业务的增长需要,服务常会根据职责被拆分为各个单服务,这产生了上下游依赖关系。上下游可通过RPC调用来进行数据交换,这里RPC调用的就是我们通常说的接口。我们试着给出接口对rpc请求大概处理流程:
步骤1:验证接口权限
步骤2:处理请求线程上下文信息
步骤3:解析接口请求参数
步骤4:校验接口请求参数
步骤5:执行具体的接口业务处理操作
步骤6:返回接口响应结果
步骤7:清空上下文信息
注:这里仅是示例,实际上不同业务的接口处理逻辑大多不同,标准化流程也存在差异,即模版之间是会存在差异的。
定义了这样一套标准化流程后,该服务的任何接口均可通过这套流程处理RPC请求。模版及其具体实现代码如下:
/*** 处理RPC请求模版类*/
public abstract class AbstractRPCHandler {// 验证接口权限protected abstract boolean checkPermission();// 处理请求上下文protected abstract void processContext();// 解析接口请求参数protected abstract RequestData parseRequest();// 校验接口请求参数protected abstract boolean validateRequest(RequestData requestData);// 执行具体的接口处理操作protected abstract ResponseData process(RequestData requestData);// 返回接口响应结果protected abstract void sendResponse(ResponseData responseData);// 是否清空上下文protected boolean toClearContext() {return false;}// 清空上下文protected abstract void clearContext();// 模板方法,定义rpc接口处理的流程public final void processInterface() {if (!checkPermission()) { // 1System.out.println("无权限访问接口");return;}processContext(); // 2RequestData requestData = parseRequest(); // 3if (!validateRequest(requestData)) { // 4System.out.println("请求参数校验失败");return;}ResponseData responseData = process(requestData); // 5sendResponse(responseData); // 6if(toClearContext()) {clearContext(); // 7}}
}
/*** 处理RPC具体实现类*/
public class ConcreteRPCHandler extends AbstractRPCHandler{@Overrideprotected boolean checkPermission() {// 验证权限的具体实现...return false;}@Overrideprotected void processContext() {// 处理上下文的具体实现}@Overrideprotected RequestData parseRequest() {// 解析接口请求参数的具体实现return null;}@Overrideprotected boolean validateRequest(RequestData requestData) {// 校验接口请求参数的具体实现return false;}@Overrideprotected ResponseData process(RequestData requestData) {// 执行具体的接口处理操作的具体实现return null;}@Overrideprotected void sendResponse(ResponseData responseData) {// 返回接口响应结果的具体实现}@Overrideprotected void clearContext() {// 清空上下文的具体实现}
}

在如上示例中,抽象模版类的clearContext()方法实际上就是钩子方法,用于控制标准化流程processInterface()方法中是否执行清空上下文操作。关于模版方法模式有两个主要的问题:
模版方法模式是否符合开闭原则?从类的扩展角度看,新增具体子类不会影响原有业务逻辑,因此是符合开闭原则的。但是,在一些参考资料中认为模版类中增加基础方法,就需要所有子类跟随修改,所以不符合开闭原则。我在前面的开闭原则讲解中说过,有些改动不能算违背开闭原则,需要看改动背后的业务需要是否本身就对原有业务产生变动了,即需求变动了,代码改动是正常的【那我们得要求产品不改动需求】。因此,我们得先知道“模板类增加基础方法”的背后的业务改动的属性,若属于业务改动,那如前所述,代码改动是符合预期的【除非你跟产品说我不做】。相反,若属于业务扩展,那么扩展的方法似乎又不该放入标准化流程中,因为标准化流程是模板类所负责的职责,职责都变了,那怎么说是业务扩展呢。所以,“模板类增加基础方法”这件事情本身就属于业务改动,就是会影响原有业务代码逻辑-所有子类均需要适配修改。
模版方法模式是否符合里氏替换原则?很多人认为由于模版方法中子类的行为影响了父类的行为,所以不符合开闭原则。这种说法我认为还是没有理解历史替换原则的本质,被很多书籍、定义、条件弄混以至于无法真正理解。我在前面的相关设计原则文章说过这个原则的本质就是子类方法的业务含义必须与基类保持一致或兼容。我们首先看子类实现抽象方法算不算破坏里氏替换原则?如果算的话,那你告诉我该原则还怎么和依赖倒置原则兼容。如果不算,那子类实现的业务逻辑父类可是并没有呀,所以问题不能停留在表面,抽象方法指的是业务含义的定义,约束子类的实现【注:子类实现抽象方法也必须符合抽象定义,否则就是强行违背里氏替换原则】。我们再看模版方法,模版方法是具体业务逻辑还是业务含义?实际上就是业务逻辑,但是这个业务逻辑(标准化流程)是所有子类和父类所共有。这就要求所有子类的标准化流程必须和父类定义的一样,不允许修改,因此模版方法符合里氏替换原则有两个条件:① 模版方法需使用final关键字修饰 ② 不允许有钩子方法。满足这两个条件即满足里氏替换原则,否则就是不满足了。【钩子方法这个条件其实也好理解,子类要是重写了父类方法,那不就是违背了里氏替换原则么,所以确实不能有】
模版方法模式优点:
- 将标准化流程封装起来供实现类使用,本身就是开闭原则的体现
模版方法模式缺点:
- 子类的可定制性受限,必须符合标准化流程
- 采用钩子方法的模版方法模式不符合里氏替换原则,使用多态时会造成不必要的误解,降低代码可读性。
相关文章:
设计模式-模版方法模式
生活中处处存在模版,模版定义了大的框架,具体内容由使用者填充即可,这给很多人的生活、工作带来了很大的遍历。比如: PPT模版:好的PPT模版提供了更全面的叙述框架,更优美的UI画面&图标,提升…...
Linux 学习记录59(ARM篇)
Linux 学习记录59(ARM篇) 本文目录 Linux 学习记录59(ARM篇)一、IIC总线1. 概念2. IIC总线硬件连接 二、系统框图三、IIC时序1. 起始信号 / 停止信号2. 数据传输信号3. 应答信号 / 非应答信号4. 寻址信号 四、IIC协议1. 主机给从机发送一个字节(写)2. 主机给从机发送多个连续字…...
TypeScript -- 函数
文章目录 TypeScript -- 函数JS -- 函数的两种表现形式函数声明函数的表达式es6 箭头函数 TS -- 定义一个函数TS -- 函数声明使用接口(定义)ts 定义参数可选参数写法 -- ?的使用TS函数 -- 设置剩余参数函数重载 TypeScript – 函数 JS – 函数的两种表现形式 我们熟知js有两…...
网页开发基础——HTML
一、flask框架 Flask是一种轻量级的Python web应用程序框架,可以帮助使用者快速构建Web应用程序和API。由于其简洁、灵活和易于上手的特点,Flask被广泛用于开发小型到中型的Web应用程序和后端API。本次我们主要是使用flask框架,进行一个小型w…...
C# 继承,封装,多态等知识点
一:面向对象的三大特征:继承性,封装性,多态性 1:继承性:继承主要描述是类与类之间的关系,通过继承可以在无需重新编写原有的类的情况下,对原有的类的功能进行扩展。 2:封…...
决策树概述
文章目录 决策树介绍1.介绍**决策树API:**构建决策树的三个步骤决策树的优缺点通过sklearn实现决策树分类并进一步认识决策树2. ID3 决策树1. 信息熵2. 信息增益**定义:****根据信息增益选择特征方法是:****算法:**3. ID3算法步骤4. 例子:3. C4.5 决策树1. 信息增益率计算…...
青枫壁纸小程序V1.4.0(后端SpringBoot)
引言 那么距离上次的更新已经过去了5个多月,期间因为忙着毕业设计的原因,更新的速度变缓了许多。所以,这次的更新无论是界面UI、用户功能、后台功能都有了非常大的区别。希望这次更新可以给用户带来更加好的使用体验 因为热爱,更…...
Error: unknown flag: --export 【k8s,kubernets报错】
报错情况如下: [rootk8smaster ~]# kubectl get deploy nginx -oyaml --export > my2.yaml Error: unknown flag: --export See kubectl get --help for usage.原因: --export在所使用的版本中已被移除 解决:去除--export即可,…...
进入linux系统中修改网段-ip
第一步 :开启虚拟机 cd 到 /etc/sysconfig/network-scripts 目录下,输入命令给ls,展示这个目录下文件和文件夹 第二步:进入到以ifcfg开头的文件 # ifcfg开头的文件,如果有多个网卡,有多个ifcfg-ensxx文件 命令…...
通过REST API接口上传Nexus仓库
一、Nexus API文档 API文档链接:Components API 二、上传API接口说明 在Nexus中可以直接调试api接口,url参考:http://localhost:8081/#admin/system/api 三、上传请求案例 $ curl -X POST "http://localhost:8081/service/rest/v1/c…...
Docker镜像端口映射简介及配置指南
目录 引言:什么是端口映射?配置端口映射的步骤:1. 创建Docker镜像:2. 选择要映射的端口:3. 运行容器并进行端口映射:4. 验证端口映射: 示例:结论: 引言: Doc…...
Excel双向柱状图的绘制
Excel双向柱状图在绘制增减比较的时候经常用到,叫法繁多,双向柱状图、上下柱状图、增减柱状图都有。 这里主要介绍一下Excel的基础绘制方法和复杂一点的双向柱状图的绘制 基础双向柱状图的绘制 首先升降的数据如下: 月份上升下降20220359-…...
Linux6.17 Docker 安全及日志管理
文章目录 计算机系统5G云计算第四章 LINUX Docker 安全及日志管理一、Docker 容器与虚拟机的区别1.隔离与共享2.性能与损耗 二、Docker 存在的安全问题1.Docker 自身漏洞2.Docker 源码问题 三、Docker 架构缺陷与安全机制1.容器之间的局域网攻击2.DDoS 攻击耗尽资源3.有漏洞的系…...
学好Elasticsearch系列-索引的CRUD
本文已收录至Github,推荐阅读 👉 Java随想录 文章目录 创建索引删除索引查询数据添加 & 更新数据cat命令公共参数 常用命令aliases 显示别名allocation 显示每个节点的分片数和磁盘使用情况count 显示整个集群或者索引的文档个数fielddata 显示每个节…...
Python - OpenCV机器视觉库的简单使用经验
OpenCV是一个开源的计算机视觉库,它支持多种编程语言,包括Python。下面是Python 3中OpenCV的详细解析: 安装OpenCV 在Python 3中安装OpenCV,可以使用pip命令来安装。例如,在终端中输入以下命令: pip ins…...
【计算机网络 01】说在前面 信息服务 因特网 ISP RFC技术文档 边缘与核心 交换方式 定义与分类 网络性能指标 计算机网络体系结构 章节小结
第一章--概述 说在前面1.1 计算机网络 信息时代作用1.2 因特网概述1.3 三种交换方式1.4 计算机网络 定义与分类1.5 计算机网络的性能指标1.6 计算机网络体系结构1 常见的计算机网络体系结构2 计算机网络体系结构分层的必要性3 计算机网络体系结构分层思想举例4 计算机网络体系结…...
POI信息点的diPointX、diPointY转化成经纬度
需求:接口返回某个地点的数据(diPointX、diPointY),前端需把该地点转化成经纬度形式在地图上进行Marker标记。 实现:(查找百度地图开发文档) 代码验证: console.log(new BMap.Merca…...
虚拟机(VMware)安装Linux(Ubuntu)安装教程
清华大学开源网站镜像站网址:清华大学开源软件镜像站 | Tsinghua Open Source Mirror 进入之后在搜索框中搜索“ubuntu” 直接点击箭头所指的蓝色字体“ubuntu-20.04.1-desktop-amd64.iso”即可下载...
linux系统下(centos7.9)安装Jenkins全流程
一、卸载历史版本 # rpm卸载 rpm -e jenkins# 检查是否卸载成功 rpm -ql jenkins# 彻底删除残留文件 find / -iname jenkins | xargs -n 1000 rm -rf二、环境依赖安装 yum -y install epel-releaseyum -y install daemonize三、安装Jenkins Jenkins官网传送带: …...
Java版知识付费源码 Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台
提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含:录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署,免费售后,专业技术指导,支持PC、APP、H5、小程序多终端同步,支持二次开发…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
