万字解析设计模式之模板方法与解释器模式

一、模板方法模式

1.1概述

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

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。 

1.2结构

模板方法(Template Method)模式包含以下主要角色:

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成,可以是抽象方法和具体方法。

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。抽象类中的可选步骤,子类可以选择是否实现

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

1.3实现

【例】炒菜

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

 抽象类(Abstract Class)

package com.yanyu.Template;public abstract class AbstractClass {// 模板方法,定义了烹饪的步骤public final void cookProcess() {//第一步:倒油this.pourOil();//第二步:热油this.heatOil();//第三步:倒蔬菜this.pourVegetable();//第四步:倒调味料this.pourSauce();//第五步:翻炒this.fry();}public void pourOil() {System.out.println("倒油");}// 抽象方法,由子类实现,倒蔬菜的步骤public abstract void pourVegetable();// 抽象方法,由子类实现,倒调味料的步骤public abstract void pourSauce();// 具体方法,热油的步骤是一样的,直接实现public void heatOil() {System.out.println("热油");}// 具体方法,翻炒的步骤是一样的,直接实现public void fry(){System.out.println("炒啊炒啊炒到熟啊");}
}

具体子类(Concrete Class)

package com.yanyu.Template;public class ConcreteClass_BaoCai extends AbstractClass {@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是包菜");}@Overridepublic void pourSauce() {System.out.println("下锅的酱料是辣椒");}
}
package com.yanyu.Template;public class ConcreteClass_CaiXin extends AbstractClass {@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是菜心");}@Overridepublic void pourSauce() {System.out.println("下锅的酱料是蒜蓉");}
}

客户端类

package com.yanyu.Template;public class Client {public static void main(String[] args) {//炒手撕包菜ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();baoCai.cookProcess();//炒蒜蓉菜心ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();caiXin.cookProcess();}
}

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

1.4优缺点

优点:

  • 提高代码复用性

    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制

    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

1.5应用场景

  • 当你只希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可使用模板方法模式;

  • 模板方法将整个算法转换为一系列独立的步骤,以便子类能对其进行扩展,同时还可让超类中所定义的结构保持完整;

  • 当多个类的算法除一些细微不同之外几乎完全一样时,你可使用该模式。但其后果就是, 只要算法发生变化,你就可能需要修改所有的类;

  • 在将算法转换为模板方法时,你可将相似的实现步骤提取到超类中以去除重复代码。子类间各不同的代码可继续保留在子类中。

  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

1.6源码解析

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:


public abstract class InputStream implements Closeable {//抽象方法,要求子类必须重写public abstract int read() throws IOException;
​public int read(byte b[]) throws IOException {return read(b, 0, b.length);}
​public int read(byte b[], int off, int len) throws IOException {if (b == null) {throw new NullPointerException();} else if (off < 0 || len < 0 || len > b.length - off) {throw new IndexOutOfBoundsException();} else if (len == 0) {return 0;}
​int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据if (c == -1) {return -1;}b[off] = (byte)c;
​int i = 1;try {for (; i < len ; i++) {c = read();if (c == -1) {break;}b[off + i] = (byte)c;}} catch (IOException ee) {}return i;}
}

从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。

在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。

总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。

二、解释器模式

2.1概述

解释器模式是一种行为型设计模式,它定义了一个语言的语法,并用一个解释器来解释该语言中的句子。通常,解释器模式用于将一个复杂的语言拆分成一些简单的语言元素,使它们易于理解和操作。

2.2结构 

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。定义了一个抽象的解释操作,所有具体的表达式都需要实现这个接口。

  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。它实现了抽象表达式的解释方法。

  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。它们通过递归的方式来解释语言。

  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。环境保存了要解释的语言,它提供了一个接口给表达式来获取和设置环境的状态。

2.3实现

【例】设计实现加减法的软件

抽象表达式

package com.yanyu.Expressioner;//抽象角色AbstractExpression
public abstract class AbstractExpression {//定义了一个解释器方法,接收一个上下文对象,返回解释结果public abstract int interpret(Context context);
}

终结符表达式(Terminal Expression)角色

package com.yanyu.Expressioner;// 终结符表达式角色 变量表达式
// 变量表达式是解释器模式中的一种角色,用于表示语言中的变量。在这里,Variable类表示一个变量,它继承自抽象表达式角色AbstractExpression。
public class Variable extends AbstractExpression {private String name;// 构造函数,用于初始化变量名public Variable(String name) {this.name = name;}// interpret方法用于解释上下文中的表达式,这里是返回变量对应的值@Overridepublic int interpret(Context ctx) {return ctx.getValue(this);}// 重写toString方法,返回变量名的字符串表示@Overridepublic String toString() {return name;}
}
package com.yanyu.Expressioner;// 终结符表达式角色
// 终结符表达式是解释器模式中的一种角色,用于表示语言中的基本元素。在这里,Value类表示一个具体的值,它继承自抽象表达式角色AbstractExpression。
public class Value extends AbstractExpression {private int value;// 构造函数,用于初始化值public Value(int value) {this.value = value;}// interpret方法用于解释上下文中的表达式,这里是返回值本身@Overridepublic int interpret(Context context) {return value;}// 重写toString方法,返回值的字符串表示@Overridepublic String toString() {return Integer.valueOf(value).toString();}
}

非终结符表达式(Nonterminal Expression)角色

package com.yanyu.Expressioner;// 非终结符表达式角色  加法表达式
// 加法表达式是解释器模式中的一种非终结符表达式角色,用于表示语言中的加法操作。在这里,Plus类表示加法表达式,它继承自抽象表达式角色AbstractExpression。public class Plus extends AbstractExpression {private AbstractExpression left;  // 左操作数private AbstractExpression right;  // 右操作数// 构造函数,用于初始化左右操作数public Plus(AbstractExpression left, AbstractExpression right) {this.left = left;this.right = right;}// interpret方法用于解释上下文中的表达式,这里是返回左右操作数的解释结果相加的值@Overridepublic int interpret(Context context) {return left.interpret(context) + right.interpret(context);}// 重写toString方法,返回加法表达式的字符串表示,形式为 (左操作数 + 右操作数)@Overridepublic String toString() {return "(" + left.toString() + " + " + right.toString() + ")";}
}
package com.yanyu.Expressioner;///非终结符表达式角色 减法表达式
public class Minus extends AbstractExpression {private AbstractExpression left;private AbstractExpression right;public Minus(AbstractExpression left, AbstractExpression right) {this.left = left;this.right = right;}@Overridepublic int interpret(Context context) {return left.interpret(context) - right.interpret(context);}@Overridepublic String toString() {return "(" + left.toString() + " - " + right.toString() + ")";}
}

环境(Context)角色

package com.yanyu.Expressioner;import java.util.HashMap;
import java.util.Map;// 环境类
// 环境类用于存储变量和它们的值,在解释器模式中起到承上启下的作用,为解释器提供解释所需的上下文信息。
public class Context {private Map<Variable, Integer> map = new HashMap<Variable, Integer>();// 将变量和对应的值存入map中public void assign(Variable var, Integer value) {map.put(var, value);}// 获取变量对应的值public int getValue(Variable var) {Integer value = map.get(var);return value;}
}

客户端类

package com.yanyu.Expressioner;// 测试类
// 客户端类Client用于测试解释器模式的功能。在这里,我们创建了一个上下文对象context,以及五个变量a、b、c、d、e,并为这些变量赋值。
// 然后,我们构造了一个复杂的表达式,包括加法和减法操作,并通过interpret方法解释这个表达式,输出其计算结果。public class Client {public static void main(String[] args) {Context context = new Context();  // 创建上下文对象Variable a = new Variable("a");  // 创建变量aVariable b = new Variable("b");  // 创建变量bVariable c = new Variable("c");  // 创建变量cVariable d = new Variable("d");  // 创建变量dVariable e = new Variable("e");  // 创建变量econtext.assign(a, 1);  // 为变量a赋值context.assign(b, 2);  // 为变量b赋值context.assign(c, 3);  // 为变量c赋值context.assign(d, 4);  // 为变量d赋值context.assign(e, 5);  // 为变量e赋值// 构造复杂的表达式,包括加法和减法操作AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);// 解释并输出表达式的计算结果System.out.println(expression + "= " + expression.interpret(context));}
}

2.4 优缺点

1,优点:

  • 易于改变和扩展文法。

    由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

  • 实现文法较为容易。

    在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。

  • 增加新的解释表达式较为方便。

    如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 "开闭原则"。

2,缺点:

  • 对于复杂文法难以维护。

    在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。

  • 执行效率较低。

    由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦

2.5应用场景

  • 当语言的文法较为简单,且执行效率不是关键问题时。
  • 当问题重复出现,且可以用一种简单的语言来进行表达时。
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。

三、模板方法模式实验

任务描述

高校网上办事系统中的需要对场地预约、设备维修、教职工请假、车辆登记等申请单进行审核,因此需要与众多子系统进行对接。子系统会将用户填写好的申请单推送到网上办事系统中,处理完后将单号返回给子系统。对接的申请单种类有多个,每种申请单的处理流程是一样的(数据校验-申请单解析-申请单入库-提交审核-自动备份),最大的区别在于不同的申请单,解析方法不同。

本关任务:以场地预约申请单(VenueApplication)和教职工请假申请单(LeaveApplication)为例,模拟实现申请单处理流程。

实现方式

  1. 分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 考虑哪些步骤能够通用, 哪些步骤各不相同;

  2. 创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。 在模板方法中根据算法结构依次调用相应步骤。 可用 final 最终修饰模板方法以防止子类对其进行重写;

  3. 虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法;

  4. 可考虑在算法的关键步骤之间添加钩子;

  5. 为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。

编程要求

根据提示,补充右侧编辑器文件Client.javaBegin-End 内的代码,完成实验。其它文件的代码不需要修改。

测试说明

平台会对你编写的代码进行测试:

测试输入: 张三 LeaveApplication 预期输出: 张三数据校验 教职工请假申请单数据解析 张三申请单入库 张三提交审核 张三自动存档

测试输入: 报告厅 VenueApplication 预期输出: 报告厅数据校验 场地预约申请单数据解析 报告厅申请单入库 报告厅提交审核 报告厅自动存档

抽象类

public abstract class ApplicationTemplate {public boolean execute(String data){this.checker(data);this.dataAnalysis(data);this.proposalSave(data);this.submit(data);this.autoSave(data);return true;}/*** 数据校验*/public void checker(String data){System.out.println(data+"数据校验");}/*** 数据解析*/public abstract void dataAnalysis(String data);/*** 数据入库*/public void proposalSave(String data){System.out.println(data+"申请单入库");}/*** 提交审核*/public void submit(String data){System.out.println(data+"提交审核");}/*** 自动存档*/public void autoSave(String data){System.out.println(data+"自动存档");}}

 具体类

public class LeaveApplication extends ApplicationTemplate{@Overridepublic void dataAnalysis(String data) {System.out.println("教职工请假申请单数据解析");}
}
public class VenueApplication extends ApplicationTemplate{@Overridepublic void dataAnalysis(String data) {System.out.println("场地预约申请单数据解析");}
}

客户端类

import java.util.Scanner;public class Client {public static void main(String[] args) {/********** Begin *********/Scanner scanner = new Scanner(System.in);String applicant = scanner.nextLine();String applicationType = scanner.nextLine();if (applicationType.equals("LeaveApplication")) {LeaveApplication leaveApplication = new LeaveApplication();leaveApplication.execute(applicant);} else if (applicationType.equals("VenueApplication")) {VenueApplication venueApplication = new VenueApplication();venueApplication.execute(applicant);} else {System.out.println("Invalid application type");}/********** End *********/}
}

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

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

相关文章

qt pdf 模块简介

文章目录 1. 技术平台2. Qt pdf 模块3. cmake 使用模块4. 许可证5. 简单示例5.1 CMakeLists.txt5.2 main.cpp 6. 总结 1. 技术平台 项目说明OSwin10 x64Qt6.6compilermsvc2022构建工具cmake 2. Qt pdf 模块 Qt PDF模块包含用于呈现PDF文档的类和函数。 QPdfDocument 类加载P…

监控同一局域网内其它主机上网访问信息

1.先取得网关IP 2.安装IPTABLES路由表 sudo apt-get install iptables 3.启用IP转发 sudo sysctl -p 查看配置是否生效 4.配置路由 iptables -t nat -A POSTROUTING -j MASQUERADE 配置成功后,使用sudo iptables-save查看

[leetCode]257. 二叉树的所有路径(两种方法)

257. 二叉树的所有路径 题目描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例&#xff1a; 输入&#xff1a;root [1,2,3,null,5]输出&#xff1a;["1-&g…

【Spring】Spring事务失效问题

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

基于uniapp+vue微信小程序的健康饮食管理系统 907m6

设计这个微信小程序系统能使用户实现不需出门就可以在手机或电脑前进行网上查询美食信息、 运动视频等功能。 本系统由用户和管理员两大模块组成。用户界面显示在应用程序中&#xff0c;管理员界面显示在后台服务中&#xff0c;通过小程序端与服务端间进行数据交互与数据传输实…

自建CA实战之 《0x03 代码签名》

自建CA实战之 《0x03 代码签名》 本文针对Windows平台&#xff0c;介绍如何使用自建CA来签发代码签名证书。 之前的文章中&#xff0c;我们介绍了如何自建CA&#xff0c;以及如何使用自建CA来签发Web服务器证书、客户端证书。 本文将介绍如何使用自建CA来签发代码签名证书。…

文本转语音:微软语音合成标记语言 (SSML) 文本结构和事件

​ SSML 的语音服务实现基于万维网联合会的语音合成标记语言版本 1.0。 ​ 语音服务支持的元素可能与 W3C 标准不同。 每个 SSML 文档是使用 SSML 元素&#xff08;或标记&#xff09;创建的。 这些元素用于调整语音、风格、音节、韵律、音量等。 下面是 SSML 文档的基本结构…

CANdelaStudio 使用教程5 编辑DID

文章目录 在哪编辑DID的分类编辑快照数据添加 DID 在哪编辑 DID的分类 编辑快照数据 添加 DID

async函数和await关键字

async写在一个函数a前面&#xff0c;该函数变为异步函数&#xff0c;可在里面使用await关键字&#xff0c;await后面一般跟一个promise对象&#xff08;axios函数返回一个promise对象&#xff0c;里面有异步任务&#xff09;&#xff0c;await会原地等待该异步任务结果&#xf…

单细胞seurat入门—— 从原始数据到表达矩阵

根据所使用的建库方法&#xff0c;单细胞的RNA序列&#xff08;也称为读取&#xff08;reads&#xff09;或标签&#xff08;tags&#xff09;&#xff09;将从转录本的3端&#xff08;或5端&#xff09;&#xff08;10X Genomics&#xff0c;CEL-seq2&#xff0c;Drop-seq&…

枚举的第一行

2023年11月26日 问题: 好奇enum的所声明的枚举类的第一行是什么 从java技术卷1中第五章5.6中,了解是枚举类的实例 验证 错误信息: 解释: 此时只有有参构造 在这个枚举类里不能使用空,大概意思是说不能使用空参创建实例 校验 在原有的基础上创建一个无参构造 结果:不再报错,第…

【教学类-06-13】20231126 (55格版)趣味题(一)1-9加法题(10倍)(整十相加)

作品展示 背景需求&#xff1a; 1、会做加法题的孩子5分钟内完成题目&#xff0c;太快了&#xff0c;所以为了拉平差异&#xff0c;需要给这些会做另外的题目&#xff0c;比如提供一些他们没有做过的“趣味题形”。 2、好多次&#xff0c;听见大班孩子在互相“考试”——“老…

CSS常用笔记

1. 脱离文档流&#xff0c;用于微调 {position: relative; top: 10px; right: 0; } 2. flex布局大法 <div class"demo"><div class"demo-1"></div><div class"demo-2"></div><div class"demo-3"&…

从源码重新真正认识RateLimiter(SmoothBursty实现)

前言 相信大家对于谷歌RateLimiter一定并不陌生,在项目中应该也经常拿来进行限流&#xff0c;但是对于其实现原理并不一定能用熟于心&#xff0c;本文带大家从源码探究RateLimiter的设计与具体实现。 RateLimiter的组成 从源码可以看到&#xff0c;RateLimiter由stopwatch与m…

Elasticsearch集群部署,配置head监控插件

Elasticsearch是一个开源搜索引擎&#xff0c;基于Lucene搜索库构建&#xff0c;被广泛应用于全文搜索、地理位置搜索、日志处理、商业分析等领域。它采用分布式架构&#xff0c;可以处理大规模数据集和支持高并发访问。Elasticsearch提供了一个简单而强大的API&#xff0c;可以…

全球SAR卫星大盘点与回波数据处理专栏目录

近年来&#xff0c;随着商业航天的蓬勃发展&#xff0c;商业SAR卫星星座成为美欧等主要航天国家的发展重点&#xff0c;目前已在全球范围内涌现出众多初创公司进军商业SAR领域&#xff0c;开始构建大规模商业微小SAR卫星星座&#xff0c;其所具有的创新服务能力将为传统的商业遥…

uniapp IOS从打包到上架流程(详细简单)

​ uniapp IOS从打包到上架流程&#xff08;详细简单&#xff09; 原创 1.登入苹果开发者网站&#xff0c;打开App Store Connect ​ 2.新App的创建 点击我的App可以进入App管理界面&#xff0c;在右上角点击➕新建App 即可创建新的App&#xff0c;如下图&#xff1a; ​ 3.…

VUE简易计划清单

目录 效果预览图 完整代码 效果预览图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>…

基于YOLOv5的视频计数 — 汽车计数实现

在视频中计数对象可能看起来有挑战性&#xff0c;但借助Python和OpenCV的强大功能&#xff0c;变得令人意外地易于实现。在本文中&#xff0c;我们将探讨如何使用YOLO&#xff08;You Only Look Once&#xff09;目标检测模型在视频流或文件中计数对象。我们将该过程分解为简单…

带你用uniapp从零开发一个仿小米商场_9. 轮播图组件封装及使用

导航栏有了,接下来就是轮播图了,轮播图如下, 因为uniapp 官方自己有轮播图,所以这里就不自己写了,直接使用uniapp的轮播图二次开发就好 uniapp的轮播图组件叫swiper ,感兴趣的朋友可以点击链接,直接去看官方文档,也可以看我这里实操 用hbuilderX编译uniapp的代码有一个好处…