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

一、模板方法模式

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,一经查实,立即删除!

相关文章

ubuntu22.04安装swagboot遇到的问题

一、基本情况 系统&#xff1a;u 22.04 python&#xff1a; 3.10 二、问题描述 swagboot官方提供的安装路径言简意赅:python3 -m pip install --user snagboot 当然安装python3和pip是基本常识&#xff0c;这里就不再赘述。 可是在安装的时候出现如下提示说 Failed buildin…

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…

每日OJ题_算法_双指针⑦力扣15. 三数之和

目录 力扣15. 三数之和 解析代码 力扣15. 三数之和 难度 中等 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三…

【Spring】Spring事务失效问题

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

C/C++---------------LeetCode第229. 多数元素 II

多数元素|| 题目及要求哈希算法 题目及要求 给定一个大小为 n 的整数数组&#xff0c;找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&#xff1a;[3] 示例 2&#xff1a; 输入&#xff1a;nums [1] 输出&#xff1a;…

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

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

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

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

MYSQL中DML、DDL常用语句记录

MYSQL中DML、DDL常用语句记录 DML 在 MySQL 中&#xff0c;DML (Data Manipulation Language) 是一类用于查询和操作数据的 SQL 语句。以下是常用的 DML 语句&#xff1a; 1、SELECT SELECT 语句用于查询数据库中的数据。语法如下&#xff1a; SELECT column1, column2, .…

Kafka(一)在WSL单机搭建Kafka伪集群

目录 1 运行Kafka单实例1.1 Windws1.1.1 安装包下载1.1.2 修改环境变量1.1.3 修改配置文件1.1.4 启动Kafka单机版 1.2 Linux1.2.1 安装包下载1.2.2 创建目录1.2.3 添加环境变量1.2.4 修改配置文件1.2.5 运行Kafka1.2.6 停止Kafka 2 搭建Kafka集群2.1 搭建Zookeeper集群2.2 搭建…

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

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

【Linux常用命令】-文件写入相关

一、rm命令&#xff0c;文件删除 1.相关参数 -f&#xff08;–force&#xff09;&#xff1a;强制删除文件或目录&#xff0c;无需确认。 -r&#xff08;–recursive&#xff09;&#xff1a;递归地删除目录及其内容。 -i&#xff08;–interactive&#xff09;&#xff1a;交…

ultrascale FPGA

1.工艺从mos到FIN,查了半天资料&#xff0c;不如bili的intel介绍视频&#xff0c;其实是把DS做成3D结构&#xff0c;减小DS漏电流&#xff1b; 2.型号的尾数是以百万门为标定的&#xff1b; 3.slice&#xff08;切片&#xff09;是CLB的组成单元&#xff0c;slice又包含LUT&a…

CANdelaStudio 使用教程5 编辑DID

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

Linux C语言 26-可变参数

Linux C语言 26-可变参数 本节关键字&#xff1a;可变参数、va_list、va_arg、va_end 相关C库函数&#xff1a;va_list、va_arg、va_end 什么是可变参数&#xff1f; C语言中的可变参数是指函数能够接受不定数量的参数。在不确定函数参数时&#xff0c;使用“char *format, …

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中,了解是枚举类的实例 验证 错误信息: 解释: 此时只有有参构造 在这个枚举类里不能使用空,大概意思是说不能使用空参创建实例 校验 在原有的基础上创建一个无参构造 结果:不再报错,第…

c语言练习12周(11~15)

编写double fun(int a[],int n)函数&#xff0c;计算返回评分数组a中&#xff0c;n个评委打分&#xff0c;去掉一个最高分去掉一个最低分之后的平均分 题干编写double fun(int a[],int n)函数&#xff0c;计算返回评分数组a中&#xff0c;n个评委打分&#xff0c;去掉一…