当前位置: 首页 > news >正文

设计模式-模版方法模式

生活中处处存在模版,模版定义了大的框架,具体内容由使用者填充即可,这给很多人的生活、工作带来了很大的遍历。比如:

  • PPT模版:好的PPT模版提供了更全面的叙述框架,更优美的UI画面&图标,提升用户的PPT制作水平
  • 技术方案模版:通过使用技术方案模版,确定需求应用的各个方面,如需求背景、上下游业务方、存储层设计、接口设计、接口性能评估等。通过完善的模版,技术方案制作时能够考虑的更加全面。

你也可以想象生活中处处可见的其他“模版”,这是一个标准化的过程。在开发技术栈中,spring、mybatic等框架其实也是模版的体现,通过标准化模版式的约定简化开发成本,提升开发效率。因此,我们该怎么通过面向对象语言来体现这种标准化的过程(模版)呢?答案就是模版方法模式。

一、模版方法模式

先思考下实现这种标准化过程的模板类应该具有哪些职责?① 明确指定了标准化过程都有哪些子元素?② 这些子元素在模版中如何组织 。这两点也是我们去评价是否使用这套模版的核心问题,如Python项目不会去考虑使用含有"JVM内存分析"的技术方案模版、产品在写文档时也不会去考虑开发技术方案等。
再考虑下,模板类中“子元素”应该有模板类指定吗?这种具体问题具体分析,有些模版会给个默认示例,有的模版会置之为空,完全由用户来实现。当然,即使是前者,模版肯定也是允许用户重写或覆盖的。所以,这么一说,在代码实现上是否非常适合抽象类呢。
总结下抽象模版类的设计:

  1. 明确了一组标准化过程(或算法框架)-模版方法(templateMethod)
  2. 给出默认或要求用户实现的过程子元素(或步骤)-基本方法([action1(), action2(),…])

抽象模板类结构确定之后,那其具体实现类,即其子类又该注意哪些问题。对于模板类的基本方法而言,实现类必须实现其所有的抽象方法(子类必须实现),非抽象方法视业务要求而定是否需要重写。那问题在于子类(具体实现类)是否应该重写抽象模板类已经确定的模版方法(标准化过程)?我觉得可以通过下面两个方面分析:

  • 模版含义:从模版的含义上来看,其实际是一套标准化过程,具体实现应该尽可能遵守这套流程,才能达到模版效果及预期。同时,结合实际情况看,也是允许具体实现时删除或更改流程顺序,毕竟任何东西都不能是死的要灵活应用。但一定要注意,这里的灵活也仅是小范围的改动,否则,就不用使用这套模版了。
  • 设计原则:如果子类重写了模版方法,更改了标准化流程,子类重写方法与模板类的模版方法业务含义不一致了,因此不满足里氏替换原则。

结合上面两点来看,子类不应该重写模版方法(模版方法通常使用final关键字),但是如何满足具体实现类可控范围内的灵活性呢?子类不允许修改模版方法的逻辑,但是模板类可以向子类提供修改模版方法的能力,即钩子方法。模板类可以通过由子类实现或重写的钩子方法来改变标准化过程。
总结下具体子类的设计:

  1. 必须实现模板类所有的抽象基本方法
  2. 不允许重写模板类中的模版方法,但可通过模板类的钩子方法来控制模版方法的标准化流程。

到目前我们已经可以给出模板类的大致类图如下:
在这里插入图片描述

二、应用实践

模版方法的应用场景非常广泛,只要你能够对一件事情总结出一套标准化的流程,那就可以使用模版方法来实现之。我这里就给出一个开发过程中比较贴近的场景:随着业务的增长需要,服务常会根据职责被拆分为各个单服务,这产生了上下游依赖关系。上下游可通过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即可&#xff0c…...

进入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简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求&#xff…...

2023赣州旅游投资集团

单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

抽象类和接口(全)

一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...