设计模式——2_9 模版方法(Template Method)

人们往往把任性也叫做自由,但是任性只是非理性的自由,人性的选择和自决都不是出于意志的理性,而是出于偶然的动机以及这种动机对感性外在世界的依赖

——黑格尔

文章目录

  • 定义
  • 图纸
  • 一个例子:从文件中获取信息分几步?
    • Reader
          • Reader
    • 读取一个文件分几步?
          • Reader
  • 碎碎念
    • 模板方法和好莱坞原则
      • 好莱坞原则
      • 依赖腐败
    • 模板方法和钩子
    • 模板方法和框架
    • 模板方法和策略
          • Handler
    • 模板方法和生成器
    • 写在后面

定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤


其实这个系列的文章本身就是一个模板方法的体现

您可能发现了,在这个系列里每篇文章都是以 定义-图纸-例子-碎碎念 这样的格式来编写的,只不过每一篇的各个模块的里面的内容有所不同

这就是模板方法,模板方法定义骨架,由子类填充血肉,从而变成不同的个体




图纸

在这里插入图片描述




一个例子:从文件中获取信息分几步?

假定在你们公司的对外网站上有一个允许用户上传文件的接口,你会通过这个接口解析用户上传的文件,并把解析到的数据存到数据库中,用于共享供应商和你们自己的信息。但是由于经理坚信客户就是上帝,于是乎诡异的需求出现了,你需要从 word、excel还有xml文件中读取数据

准备好了吗?这次的例子开始了:



Reader

看到这样的题目你肯定会说,那很简单啊。肯定是要建一个 Reader 类簇,搞一个 WordReaderExcelReaderXMLReader。然后根据需要解析哪种文件去调用对应的 Reader 不就万事大吉了吗?


非常好,赶紧去吧 Reader 建出来,就像这样:

在这里插入图片描述

Reader
/*** 读取器*/
public interface Reader<E> {List<E> read(File file) throws IOException;
}/*** word 文件的信息读取器*/
public class WordReader<E> implements Reader<E> {@Overridepublic List<E> read(File file) throws IOException {if (file.exists()) {//文件必须存在 打开流try (FileInputStream is = new FileInputStream(file)) {System.out.println("使用 is 进行word信息读取");return data;//返回最终用户要的数据}}return null;}
}/*** excel 文件的信息读取器*/
public class ExcelReader<E> implements Reader<E> {@Overridepublic List<E> read(File file) throws IOException {if (file.exists()) {//文件必须存在 打开流try (FileInputStream is = new FileInputStream(file)) {System.out.println("使用 is 进行excel信息读取");return data;//返回最终用户要的数据}}return null;}
}/*** xml 文件的信息读取器*/
public class XMLReader<E> implements Reader<E> {@Overridepublic List<E> read(File file) throws IOException {if (file.exists()) {//文件必须存在 打开流try (FileInputStream is = new FileInputStream(file)) {System.out.println("使用 is 进行xml信息读取");return data;//返回最终用户要的数据}}return null;}
}

然后我们的问题才刚刚开始

我们发现这个 Reader 里面大量的代码都是重复的,我们判断要读取的文件是否存在,然后需要开启一个文件流,并且保证无论如何他都会被正确关闭,而这些操作 无论我将来读取任何类型的文件,他们都应该是不变的


在我们的实现里出现了重复,那他们一定可以像被提取公因式一样被提取出来简化



读取一个文件分几步?

问:把大象塞冰箱分几步?

答:三步,打开冰箱门、把大象塞进去、关上冰箱门


其实这个脑筋急转弯是个标准的偷换概念,如果你不了解他的套路,一定会纠结答案里的第二步。正确答案其实并没有解决问题,而是给正确答案加了层包装,事实上无论你塞任何东西进冰箱,都需要打开和关上冰箱门

沿着这个思路,我们再来看看上面那个问题

读取一个文件分几步?

其实也就三步,打开文件流,读出数据,关闭文件流


也就是说我们可以考虑拆分刚刚的实现,就像这样:

在这里插入图片描述

Reader
/*** 读取器*/
public abstract class Reader<E> {public List<E> read(File file) throws IOException {if (haveFile(file)) {FileInputStream fis = getFileInStream(file);try {return resolution(fis);} catch (IOException e) {e.printStackTrace();} finally {endWork(fis);}}return null;}protected boolean haveFile(File file) {return file.exists();}protected FileInputStream getFileInStream(File file) throws FileNotFoundException {return new FileInputStream(file);}protected abstract List<E> resolution(FileInputStream fis) throws IOException;protected void endWork(FileInputStream fis) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}
}/*** word 文件的信息读取器*/
public class WordReader<E> extends Reader<E> {@Overridepublic List<E> resolution(FileInputStream fis) throws IOException {//word 文件的读取方式}
}/*** excel 文件的信息读取器*/
public class ExcelReader<E> extends Reader<E> {@Overridepublic List<E> resolution(FileInputStream fis) throws IOException {//excel 文件的读取方式}
}/*** xml 文件的信息读取器*/
public class XMLReader<E> extends Reader<E> {@Overridepublic List<E> resolution(FileInputStream fis) throws IOException {//xml 文件的读取方式}
}

在这个实现中,我们将读取文件的流程分为 getFileInStream (打开文件流) -> resolution (读出文件信息)-> endWork(关闭文件流),最后把他们集成到 read 方法中并提供给外部代码调用


那你会说,就这?这不就是继承的标准用法吗?

整个模式确实是通过继承来实现的,但是他的核心是定义了骨架的 read 方法。在顶层的 read 方法中,他定义了 Reader 的工作流程,而且他调用了尚未被实现的方法 resolution,而这个方法恰恰是整个 Reader 中最核心的方法,他决定了这个 Reader 的具体工作内容

也就是说,这个实现完成了这样一个壮举,即:由父类(上层)决定调用方式,让子类(下层)决定具体实现

而这正是一个标准的模板方法实现




碎碎念

模板方法和好莱坞原则

好莱坞原则

别调用我们,我们会调用你(单向依赖)


据说模板方法的诞生是受到了好莱坞的运作模式的启发(Head First 设计模式 里写的,不管你信不信,反正我信了 ),书里是这样说的:

好莱坞原则可以给我们一种防止 “依赖腐败” 的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又依赖低层组件的时候,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。

在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但高层组件会决定什么时候和怎么使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你”

——《Head First 设计模式(第一版)》中文版 中国电力出版社版本 P296

依赖腐败

在书上他提出了一个新概念:依赖腐败。这种腐败可不是我们平时说的 权力导致腐败,绝对的权力导致绝对的腐败。恰恰相反,依赖腐败 是上下层之间过于“亲密”导致的,上下层互相依赖,最终导致整个系统纠缠在一起,就像一团打结的毛线球一样

为了解决这种 依赖腐败 ,我们考虑让依赖尽可能变成单向的,更具体一点,让下层组件挂载到上层组件的结构中(在上层组件提前预留出位置的情况下)。上层组件只会知道在某个位置一定有某个下层组件存在,在某时某刻我可以调用他,至于他具体是如何实现的,上层组件从来是漠不关心的。这就是好莱坞原则

就像开车,我只需要知道车有行驶的功能,踩了油门他会走,踩住刹车他要停。至于他烧的是92还是95,用的电池还是油箱,跟我没关系的



模板方法和钩子

钩子,英文名叫 hook

还有个东西叫 钩子方法,比如上例中的 resolution

简单来说他就是指那种 下层可以提供实现,而且一定会在上层实现某种条件的情况下被调用 的抽象方法(空实现也算)


在JavaScript中钩子方法更是随处可见,只不过在那边叫回调函数,其实本质上两者是一样的

更进一步,使用回调函数的 JavaScript 函数其实自身也是一个模板方法的实现

当我使用带有回调函数的函数时,这个具体函数的执行骨架我是无法修改的,我只需要关注于我传进去的回调函数会在符合什么状态下被调用,以及应该执行什么



模板方法和框架

理论上来说,在创建框架的时候,模板方法总是你的好帮手,典型的比如Android里面的几大组件,servlet里面的请求处理流程,甚至是古老的 applet


并不是说这些框架都肯定用了模板方法,模板方法提供的是一种思路,即 上层制定规则,下层具体实现。对于框架来说,其他程序员就是他的客户,他必须保证在每个客户都拥有足够高自由度的情况下,整个框架可以按照预设的方式运作

上层决定出入口,整个框架的起承转合,因为这个执行方式是永远不会变的,这是框架的灵魂。至于具体要怎么起,怎么承,你来决定框架的血肉



模板方法和策略

对模板方法来说,由于上层指定规则,下层具体实现。但是这个“下层”可没有人规定一定是子类来实现的


譬如说在 Reader 的实现中,我完全可以把 resolution 的实现委托出去,创建一个对应的类簇,比如说 Handler 吧,让这个类簇可以专注于根据不同的文件类型读取不同的信息。这时候 ReaderHandler 之间,就会形成一个类似策略模式的实现,Handler 是作为可插拔的 Reader 部分算法来实现的,就像这样:

在这里插入图片描述

Handler
/*** 读取器*/
public class Reader<E> {public List<E> read(File file,Handler<E> handler) throws IOException {if (haveFile(file)) {FileInputStream fis = getFileInStream(file);try {return handler.resolution(fis);} catch (IOException e) {e.printStackTrace();} finally {endWork(fis);}}return null;}protected boolean haveFile(File file) {return file.exists();}protected FileInputStream getFileInStream(File file) throws FileNotFoundException {return new FileInputStream(file);}protected void endWork(FileInputStream fis) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}
}public interface Handler<E> {List<E> resolution(FileInputStream fis) throws IOException;
}/*** word 文件的信息读取器*/
public class WordHandler<E> implements Handler<E> {@Overridepublic List<E> resolution(FileInputStream fis) throws IOException {//word 文件的读取方式}
}/*** excel 文件的信息读取器*/
public class ExcelHandler<E> implements Handler<E> {@Overridepublic List<E> resolution(FileInputStream fis) throws IOException {//excel 文件的读取方式}
}/*** xml 文件的信息读取器*/
public class XMLHandler<E> implements Handler<E> {@Overridepublic List<E> resolution(FileInputStream fis) throws IOException {//xml 文件的读取方式}
}

那你会说,不对啊,这种写法可不就是把变化的部分提取出来的策略模式吗?模板方式的优良传统呢?通过继承修改原有部分实现呢?想了这么久想出来一个违背祖宗的决定是吧?

首先我承认,这就是策略的实现,但不妨碍他也用了模板方法呀。我们把一定会发生变化的部分独立出来,形成策略簇。但是我们原有的 Reader 依然保留拓展的能力呀,假设以后我要包装我的流,或者在读取前或读取后做一些操作,完全可以通过创建 Reader 子类的方式来实现


这种写法在实战中很常见,比如说在 迭代器 一章中就有一个标准的例子,我们讲的外部迭代,其实就是通过这种方式来实现的



模板方法和生成器

你发现了吗?模板方法模式和生成器模式他们的思路是一样的,只是最终的目的不同而已。


模板方法 关注行为,他讲究某个行为必须执行的步骤和顺序,把这些不变的内容固定好后,由子类去确定具体的算法,从而实现算法和执行流程之间的解耦

生成器 就像流水线,流水线上的各个工位要做什么事情在最开始就设计好了,你只需要提供物料,不同的工位就会根据自己的职能对物料进行组装或加工,这是对一个对象不同创建流程的抽象。本质上讲这也是一种对算法的抽象——对一个对象的创建流程的抽象



写在后面

总是有人在纠结,自由到底有没有边界。窃以为,自由一定是有限度的

自由不是为所欲为,而是在一定限制内随心所欲。真正自由是需要对自己所做的事情负责的,只有一个人在有担当所做的事情造成的后果的能力后,才有权利去讲自己的自由

这就像模板方法委托给子类进行实现的那个钩子一样,我给你自由,但是你需要在我制定的框架下。就像做饭,模板方法不管你是做佛跳墙还是蛋炒饭,这是你的自由,但是做完都得把火关上,否则家里会着火,那是你承担不起的后果





万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

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

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

相关文章

为什么用CubeMX配置STM32H7主频只能配到200,但实际配到400没报错,超过400报错,其他深色也要把前边的分频器向小调?

原因&#xff1a; STM32CUBEMX配置STM32H750时钟480M时失败_stm32h750 时钟配置_小李干净又卫生的博客-CSDN博客 STM32CUBEMX默认设置的是VOS1&#xff0c;是不能支持480M运行的&#xff0c;只能400 但还不清楚为什么这里没有更多选项Scale &#xff1f;

BRC20铭文铭刻解析

BRC20铭文铭刻的出现对于智能制造无疑是一个重要的里程碑。随着科技的飞速发展&#xff0c;智能制造已经成为制造业发展的必然趋势&#xff01;智能制造是指通过运用人工智能、物联网、大数据等先进技术&#xff0c;实现生产过程的自动化、智能化和高效化。 1. BRC20铭文的概念…

【Git】从零开始的 Git 基本操作

文章目录 1. 创建 Git 本地仓库2. 配置 Git3. 认识工作区、暂存区、版本库3.1 添加文件 | 场景一3.2 查看 .git 文件3.3 添加文件 | 场景二 4. 修改文件5. 版本回退6. 撤销修改6.1 情况一&#xff1a;对于工作区的代码&#xff0c;还没有 add6.2 情况二&#xff1a;已经 add&am…

实在IDP文档审阅产品导引

实在IDP文档审阅&#xff1a;智能文档处理的革新者 一、引言 在数字化转型的浪潮中&#xff0c;文档处理的智能化成为企业提效的关键。实在智能科技有限公司推出的实在IDP文档审阅&#xff0c;是一款利用AI技术快速理解、处理文档的智能平台&#xff0c;旨在为企业打造专属的…

Qt 6子窗口全屏显示

一、全屏显示效果 二、全屏相关函数 1,全屏显示函数 QWidget::showFullScreen(); // 此方法只对顶级窗口有效&#xff0c;对子窗口无效 2&#xff0c;恢复显示函数 QWidget::showNormal(); // 此方法也只对顶级窗口有效&#xff0c;对子窗口无效 3&#xff0c;最小化显示函…

go语言并发实战——日志收集系统(五) 基于go-ini包读取日志收集服务的配置文件

实现日志收集服务的客户端 前言 从这篇文章开始我们就正式进入了日志收集系统的编写&#xff0c;后面几篇文章我们将学习到如何编写日志收集服务的客户端,话不多说,让我们进入今天的内容吧&#xff01; 需要实现的功能 我们要收集指定目录下的日志文件&#xff0c;将它们发…

12. MyBatis(二)

源码位置&#xff1a;MyBatis_demo 上篇文章我们学习了MyBatis的定义以及增删查改操作&#xff0c;并且学习了如何在xml文件中编写SQL时使用#{}的方式将参数和对象的属性映射到SQL语句中&#xff0c;上篇的内容已经足以应对大部分场景&#xff0c;本篇文章我们就要学习一下MyBa…

基础算法---二分查找

文章目录 基本思想1.数的范围2.搜索旋转排序数组3.搜索插入位置4.x的平方根总结 基本思想 二分查找的必要条件并不是单调&#xff0c;而是当我给定一个边界条件&#xff0c;然后左边满足这个边界条件&#xff0c;右边不满足这个边界条件&#xff0c;然后可以查找这个临界点&am…

华火电燃灶:烹饪艺术与科技创新的完美融合

华火电燃灶&#xff0c;以纯净电力激发明火之美&#xff0c;无须燃气&#xff0c;尽释碳负。每一道佳肴背后&#xff0c;都是对安全与健康的无声誓言&#xff0c;为家庭温馨瞬间添上一抹灿烂。从宝贝初声啼哭到晚年宁静美好&#xff0c;华火见证家的每一次幸福团聚&#xff0c;…

DevOps流程的简单总结

DevOps流程图&#xff1a; DevOps流程包含&#xff1a;计划&#xff08;plan&#xff09;、编码(code)、编译(build)、测试(test)、发布(release)、部署(deploy)、运营(operate)、监控&#xff08;monitor&#xff09;&#xff0c;这是一个循环的过程。DevOps是依托容器、自动化…

流程控制:goto语句,模拟switch语句

示例&#xff1a; /*** brief how about goto-switch? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h>static …

涉密人员离职,如何一键锁定他的电脑屏幕

在任何情况下&#xff0c;保护企业数据的安全性和机密性都是非常重要的。如果涉密人员离职&#xff0c;应该遵循相关的保密协议和规定&#xff0c;确保企业数据的完整性和安全性。同时&#xff0c;也应该对员工进行适当的培训和教育&#xff0c;提高他们的安全意识和技能水平。…

【c++】cpp数学库函数、随机数和时间库函数

&#xff08;1&#xff09;cpp数学库函数 #include <iostream>using namespace std;#include <cmath> //数学函数库的头文件#define pi 3.1415926 //定义一个常量π int main() {cout << "平方数:pow(3, 2) :" << pow(3, 2) << endl;…

selenium‘拟人包装‘设置

1、设置header,proxy 1.1关于user-agent 输入about:version 找到user-agent: import requests # 引用requests库 from selenium import webdriver#载入浏览器驱动#header&#xff0c;proxy设置 optionswebdriver.ChromeOptions()#实例化浏览器参数设置options.add_argument…

水电远程预付费系统:创新与便利的融合

1.系统概述 水电远程预付费系统是一种现代化的管理工具&#xff0c;它通过先进的信息技术实现了水电费用的预先支付和远程管理。这种系统不仅提高了服务效率&#xff0c;也为用户带来了极大的便利&#xff0c;减少了传统抄表和收费过程中的诸多不便。 2.功能特性 2.1实时计量…

pycharm创建的项目

pycharm生成django templates删出 settings.py

nvidia-smi CUDA Version:N/A

问题 nvidia-smi显示&#xff1a;CUDA Version:N/A nvidia-smi -a显示&#xff1a;CUDA Version: Not Found 解决方法 查看Nvidia驱动版本 nvidia-smi如下图&#xff0c;版本为530.41.03 搜索cuda库 apt search libcuda注&#xff1a;不同的源&#xff0c;同一个库的命…

【JavaSE】JDK17的一些特性

前言 从springboot3.0开始&#xff0c;已经不⽀持JDK8了 选⽤Java17&#xff0c;概括起来主要有下⾯⼏个主要原因 JDK17是LTS(⻓期⽀持版)&#xff0c;可以免费商⽤到2029年。⽽且将前⾯⼏个过渡版&#xff08;JDK9-JDK16&#xff09; 去其糟粕&#xff0c;取其精华的版本JDK17…

用友 NC showcontent SQL注入漏洞复现

0x01 产品简介 用友NC是由用友公司开发的一套面向大型企业和集团型企业的管理软件产品系列。这一系列产品基于全球最新的互联网技术、云计算技术和移动应用技术&#xff0c;旨在帮助企业创新管理模式、引领商业变革。 0x02 漏洞概述 用友NC /ebvp/infopub/showcontent 接口处…

AndroidStudio右下角显示内存使用情况

目录 一.具体效果 二.4.0以下版本 三.4.0以上版本 四.增加内存配置 一.具体效果 二.4.0以下版本 1.打开 Android Studio. 2.进入设置界面。点击 Android Studio 左上角的“File”&#xff0c;然后选择“Settings” 3.在设置界面中&#xff0c;选择“Appearance & Beha…