【设计模式——学习笔记】23种设计模式——解释器模式Interpreter(原理讲解+应用场景介绍+案例介绍+Java代码实现)

案例引入

通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求

  • 先输入表达式的形式,比如a+b+c-d+e,要求表达式的字母不能重复
  • 在分别输入a,b,c,d,e的值
  • 最后求出结果

在这里插入图片描述

传统方案

  • 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果

【分析】

如果加入新的运算符,比如*或/等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰

【改进】

可以考虑使用解释器模式,即表达式->解释器(可以有多种解释器)->结果

介绍

基本介绍

  • 在解释器模式中,程序要解决的问题会被用非常简单的“迷你语言”表述出来,即用“迷你语言”编写的迷你程序把具体的问题表述出来。迷你程序是无法单独工作的,我们还需要用Java语言编写一个负责“翻译”(interpreter)的程序。翻译程序会理解迷你语言并解释迷你语言,最后运行迷你程序。这段翻译程序也被称为解释器。这样,当需要解决的问题发生变化时,不需要修改 Java语言程序,只需要修改迷你语言程序即可应对
  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,然后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器

应用场景

  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树,一些重复出现的问题可以用一种简单的语言来表达,比如下列场景:编译器、运算表达式计算、正则表达式、机器人指令……

登场角色

在这里插入图片描述

  • AbstractExpression(抽象表达式):抽象表达式,声明一个抽象的解释操作(定义了语法树节点的共同接口),这个方法为抽象语法树中所有的节点所共享,方法可以取名为parse/interpreter,译为解析/翻译
  • TerminalExpression(终结符表达式):为终结符表达式,实现与文法中的终结符相关的解释操作
  • NonTermialExpression(非终结符表达式):为非终结符表达式,为文法中的非终结符实现解释操作
  • Context(上下文):是环境角色,含有解释器之外的全局信息,为解释器进行语法解析提供了必要的信息
  • Client(请求者):调用TerminalExpression和NonTermialExpression来推导语法树

案例实现

案例一

类图

在这里插入图片描述

实现

【Expression】

package com.atguigu.interpreter;import java.util.HashMap;/*** 抽象类表达式,通过HashMap键值对, 可以获取到变量的值** @author Administrator**/
public abstract class Expression {/*** 如表达式是:a + b - c ,key就是公式(表达式)的参数a、b、c, value就是就是具体值* 实例:HashMap {a=10, b=20}* @param var* @return*/public abstract int interpreter(HashMap<String, Integer> var);
}

【变量解析器】

package com.atguigu.interpreter;import java.util.HashMap;/*** 变量的解释器* @author Administrator**/
public class VarExpression extends Expression {/*** key=a,key=b,key=c*/private String key;public VarExpression(String key) {this.key = key;}/*** var 就是{a=10, b=20}* interpreter的功能就是根据变量名称来返回对应值* @param var* @return*/@Overridepublic int interpreter(HashMap<String, Integer> var) {return var.get(this.key);}
}

【抽象的运算符号解释器】

package com.atguigu.interpreter;import java.util.HashMap;/*** 抽象运算符号解析器* 每个运算符号,都只和自己左右两个数字有关系,* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类** @author Administrator**/
public class SymbolExpression extends Expression {protected Expression left;protected Expression right;public SymbolExpression(Expression left, Expression right) {this.left = left;this.right = right;}/*** 因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现* @param var* @return*/@Overridepublic int interpreter(HashMap<String, Integer> var) {// 默认实现return 0;}
}

【具体的运算符号解释器:加法解释器】

package com.atguigu.interpreter;import java.util.HashMap;/*** 加法解释器* @author Administrator**/
public class AddExpression extends SymbolExpression  {public AddExpression(Expression left, Expression right) {super(left, right);}/*** 处理相加* var 仍然是 {a=10,b=20}..* @param var* @return*/public int interpreter(HashMap<String, Integer> var) {// super.left.interpreter(var):返回 left 表达式对应的值 a = 10// super.right.interpreter(var): 返回 right 表达式对应值 b = 20// 将运算左表达式的值和右表达式相加return super.left.interpreter(var) + super.right.interpreter(var);}
}

【具体的运算符号解释器:减法解释器】

package com.atguigu.interpreter;import java.util.HashMap;/*** 减法解释器*/
public class SubExpression extends SymbolExpression {public SubExpression(Expression left, Expression right) {super(left, right);}/*** 求出left 和 right 表达式相减后的结果** @param var* @return*/public int interpreter(HashMap<String, Integer> var) {return super.left.interpreter(var) - super.right.interpreter(var);}
}

【计算器】

package com.atguigu.interpreter;import java.util.HashMap;
import java.util.Stack;public class Calculator {/*** 定义表达式*/private Expression expression;/*** 构造函数传参,解析字符串生成表达式* @param expStr*/public Calculator(String expStr) {// 如 expStr = a+b// 安排运算先后顺序Stack<Expression> stack = new Stack<>();// 表达式拆分成字符数组,变成[a, +, b]char[] charArray = expStr.toCharArray();Expression left = null;Expression right = null;//遍历我们的字符数组, 即遍历  [a, +, b]//针对不同的情况,做处理for (int i = 0; i < charArray.length; i++) {switch (charArray[i]) {case '+':// 从stack取出左表达式 "a"left = stack.pop();// 取出右表达式 "b"right = new VarExpression(String.valueOf(charArray[++i]));// 然后根据得到left和right构建AddExpresson加入stackstack.push(new AddExpression(left, right));break;case '-':left = stack.pop();right = new VarExpression(String.valueOf(charArray[++i]));stack.push(new SubExpression(left, right));break;default://如果是一个 Var 就创建要给 VarExpression 对象,并push到stackstack.push(new VarExpression(String.valueOf(charArray[i])));break;}}//当遍历完整个charArray数组后,stack就得到最终的Expressionthis.expression = stack.pop();}public int run(HashMap<String, Integer> var) {//最后将表达式 a+b 和 var={a=10,b=20}//然后传递给expression的interpreter进行解释执行return this.expression.interpreter(var);}
}

【客户端】

package com.atguigu.interpreter;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;public class ClientTest {public static void main(String[] args) throws IOException {// a+bString expStr = getExpStr();// var {a=10, b=20}HashMap<String, Integer> var = getValue(expStr);Calculator calculator = new Calculator(expStr);System.out.println("运算结果:" + expStr + "=" + calculator.run(var));}/*** 获得表达式** @return* @throws IOException*/public static String getExpStr() throws IOException {System.out.print("请输入表达式:");return (new BufferedReader(new InputStreamReader(System.in))).readLine();}/*** 获得值映射** @param expStr* @return* @throws IOException*/public static HashMap<String, Integer> getValue(String expStr) throws IOException {HashMap<String, Integer> map = new HashMap<>();for (char ch : expStr.toCharArray()) {if (ch != '+' && ch != '-') {if (!map.containsKey(String.valueOf(ch))) {System.out.print("请输入" + String.valueOf(ch) + "的值:");String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();map.put(String.valueOf(ch), Integer.valueOf(in));}}}return map;}
}

【运行】

Connected to the target VM, address: '127.0.0.1:4322', transport: 'socket'
请输入表达式:a+b
请输入a的值:10
请输入b的值:20
运算结果:a+b=30
Disconnected from the target VM, address: '127.0.0.1:4322', transport: 'socket'Process finished with exit code 0

【执行过程】

  • 第一次循环:将变量解析器a放入到栈中
  • 第二次循环: 从stack取出左表达式"a",接着中数组中获取并生成新的表达式"b",最后构建加法表达式"a+b"存储到栈中

在这里插入图片描述

在这里插入图片描述

案例二

说明

有一辆小车,需要编写一个简单的小程序控制小车的移动,比如program go right go right go right go right end,小车收到指令之后,就会走出如下的轨迹

在这里插入图片描述

类图

在这里插入图片描述

实现

【抽象表达式:Node】

package com.atguigu.interpreter.Sample;/*** 语法树中各个部分(节点)中最顶层的类*/
public abstract class Node {/*** 进行语法解析处理** @param context 语法解析上下文的类* @throws ParseException*/public abstract void parse(Context context) throws ParseException;
}

【自定义的 解析异常】

package com.atguigu.interpreter.Sample;public class ParseException extends Exception {public ParseException(String msg) {super(msg);}
}

【终结符表达式:PrimitiveCommandNode】

终结符表达式:不会进一步展开继续调用parse方法

package com.atguigu.interpreter.Sample;// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {/*** 记录 指令的名字 如 go left right*/private String name;/*** PrimitiveCommandNode 的 parse 方法没有调用其他类的parse方法* @param context 语法解析上下文的类* @throws ParseException*/public void parse(Context context) throws ParseException {// 记录指令的名字name = context.currentToken();context.skipToken(name);if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {throw new ParseException(name + " is undefined");}}public String toString() {return name;}
}

【非终结符表达式:ProgramNode】

package com.atguigu.interpreter.Sample;// <program> ::= program <command list>
public class ProgramNode extends Node {private Node commandListNode;public void parse(Context context) throws ParseException {// 迷你语法最开始会出现单词program,这行代码可以跳过 program 这个标记// 比如一开始context的值是program end,那么currentToken的值就是program,执行context.skipToken("program")后,currentToken的值变成endcontext.skipToken("program");commandListNode = new CommandListNode();commandListNode.parse(context);}public String toString() {// 等效于  return "[program " + commandListNode.toString() + "]";return "[program " + commandListNode + "]";}
}

【非终结符表达式:CommandNode】

package com.atguigu.interpreter.Sample;// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {private Node node;public void parse(Context context) throws ParseException {if (context.currentToken().equals("repeat")) {// 使用repeat解析器node = new RepeatCommandNode();node.parse(context);} else {// 使用指令解释器,解析go left right等指令node = new PrimitiveCommandNode();node.parse(context);}}public String toString() {return node.toString();}
}

【非终结符表达式:CommandListNode】

package com.atguigu.interpreter.Sample;import java.util.ArrayList;// <command list> ::= <command>* end
public class CommandListNode extends Node {/*** 保存多个命令*/private ArrayList list = new ArrayList();public void parse(Context context) throws ParseException {while (true) {if (context.currentToken() == null) {// 如果context.currentToken() == null,表示后面没有任何标记了(即已经解析到迷你程序的末尾),说明缺少了end,抛出异常throw new ParseException("Missing 'end'");} else if (context.currentToken().equals("end")) {// 如果当前的标记是end,表示已经解析至末尾,end不需要执行,直接跳过即可context.skipToken("end");// 到了end,解析已经完成了,退出循环即可break;} else {// 当前标记不是end,则是其他需要解析的标记Node commandNode = new CommandNode();// 解析标记commandNode.parse(context);list.add(commandNode);}}}public String toString() {return list.toString();}
}

【非终结符表达式:RepeatCommandNode】

package com.atguigu.interpreter.Sample;// <repeat command> ::= repeat <number> <command list>
public class RepeatCommandNode extends Node {/*** 循环调用的次数*/private int number;private Node commandListNode;public void parse(Context context) throws ParseException {context.skipToken("repeat");number = context.currentNumber();context.nextToken();commandListNode = new CommandListNode();commandListNode.parse(context);}public String toString() {return "[repeat " + number + " " + commandListNode + "]";}
}

【Context】

package com.atguigu.interpreter.Sample;import java.util.StringTokenizer;/*** 该类提供语法解析需要的方法*/
public class Context {/*** 使用java.util.stringTokenizer类来简化程序,它会将接收到的字符串分割为标记。* 在分割字符串时使用的分隔符是空格“”、制表符“\t”、换行符“\n”回车符“\r”、换页符“\f”*/private StringTokenizer tokenizer;private String currentToken;public Context(String text) {tokenizer = new StringTokenizer(text);nextToken();}/*** 获取下一个标记** @return*/public String nextToken() {// 当判断还有下一个标记时,就获取下一个标记if (tokenizer.hasMoreTokens()) {currentToken = tokenizer.nextToken();} else {currentToken = null;}return currentToken;}/*** 返回当前的标记* @return*/public String currentToken() {return currentToken;}/*** 跳过标记** @param token* @throws ParseException*/public void skipToken(String token) throws ParseException {if (!token.equals(currentToken)) {throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");}nextToken();}/*** 读取数字** @return* @throws ParseException*/public int currentNumber() throws ParseException {int number = 0;try {number = Integer.parseInt(currentToken);} catch (NumberFormatException e) {throw new ParseException("Warning: " + e);}return number;}
}

【Client:Main】

package com.atguigu.interpreter.Sample;import java.io.BufferedReader;
import java.io.FileReader;public class Main {public static void main(String[] args) {try {BufferedReader reader = new BufferedReader(new FileReader("src/com/atguigu/interpreter/Sample/program.txt"));String text;while ((text = reader.readLine()) != null) {System.out.println("迷你程序 = \"" + text + "\"");Node node = new ProgramNode();node.parse(new Context(text));System.out.println("语法解析结果 = " + node);System.out.println();}} catch (Exception e) {e.printStackTrace();}}
}

【program.txt】

program end
program go end
program go right go right go right go right end
program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end

【运行】

迷你程序 = "program end"
语法解析结果 = [program []]迷你程序 = "program go end"
语法解析结果 = [program [go]]迷你程序 = "program go right go right go right go right end"
语法解析结果 = [program [go, right, go, right, go, right, go, right]]迷你程序 = "program repeat 4 go right end end"
语法解析结果 = [program [[repeat 4 [go, right]]]]迷你程序 = "program repeat 4 repeat 3 go right go left end right end end"
语法解析结果 = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]Process finished with exit code 0

在这里插入图片描述

拓展

  • 上面的程序的功能只是将迷你程序解析出来,并没有真正执行其中的指令,下面将继续完善这个程序,让小车可以真正根据指令执行起来
  • 下面的代码属实有点绕,代码不只是使用了解释器模式,还使用了外观模式来让解释器更加便于使用,除此之外,还使用工厂方法模式来提供createExecutor(String name)方法来根据指令名称生成相应的执行器,请大伙们慢慢欣赏

【ParseException】

package com.atguigu.interpreter.A1.language;public class ParseException extends Exception {public ParseException(String msg) {super(msg);}
}

【ExecuteException】

package com.atguigu.interpreter.A1.language;public class ExecuteException extends Exception {public ExecuteException(String msg) {super(msg);}
}

【Node】

package com.atguigu.interpreter.A1.language;/*** 实现Executor执行器接口*/
public abstract class Node implements Executor {public abstract void parse(Context context) throws ParseException;
}

【ProgramNode】

package com.atguigu.interpreter.A1.language;public class ProgramNode extends Node {private Node commandListNode;public void parse(Context context) throws ParseException {context.skipToken("program");commandListNode = new CommandListNode();commandListNode.parse(context);}public void execute() throws ExecuteException {// 连续执行多个指令 的 execute方法commandListNode.execute();}public String toString() {return "[program " + commandListNode + "]";}
}

【CommandNode】

package com.atguigu.interpreter.A1.language;public class CommandNode extends Node {private Node node;public void parse(Context context) throws ParseException {if (context.currentToken().equals("repeat")) {node = new RepeatCommandNode();node.parse(context);} else {node = new PrimitiveCommandNode();node.parse(context);}}/*** 直接调用 RepeatCommandNode 和 PrimitiveCommandNode 的执行器* @throws ExecuteException*/public void execute() throws ExecuteException {node.execute();}public String toString() {return node.toString();}
}

【CommandListNode】

package com.atguigu.interpreter.A1.language;import java.util.ArrayList;
import java.util.Iterator;public class CommandListNode extends Node {private ArrayList list = new ArrayList();public void parse(Context context) throws ParseException {while (true) {if (context.currentToken() == null) {throw new ParseException("Missing 'end'");} else if (context.currentToken().equals("end")) {context.skipToken("end");break;} else {Node commandNode = new CommandNode();commandNode.parse(context);list.add(commandNode);}}}/*** 使用迭代器来自动执行指令** @throws ExecuteException*/public void execute() throws ExecuteException {Iterator it = list.iterator();while (it.hasNext()) {((CommandNode) it.next()).execute();}}public String toString() {return list.toString();}
}

【PrimitiveCommandNode】

package com.atguigu.interpreter.A1.language;
public class PrimitiveCommandNode extends Node {private String name;private Executor executor;public void parse(Context context) throws ParseException {name = context.currentToken();context.skipToken(name);// 根据指令名称来找工厂获取相应的执行器executor = context.createExecutor(name);}public void execute() throws ExecuteException {if (executor == null) {throw new ExecuteException(name + ": is not defined");} else {executor.execute();}}public String toString() {return name;}
}

【RepeatCommandNode】

package com.atguigu.interpreter.A1.language;public class RepeatCommandNode extends Node {private int number;private Node commandListNode;public void parse(Context context) throws ParseException {context.skipToken("repeat");number = context.currentNumber();context.nextToken();commandListNode = new CommandListNode();commandListNode.parse(context);}public void execute() throws ExecuteException {// 循环执行指令for (int i = 0; i < number; i++) {commandListNode.execute();}}public String toString() {return "[repeat " + number + " " + commandListNode + "]";}
}

【Context】

package com.atguigu.interpreter.A1.language;import java.util.StringTokenizer;public class Context implements ExecutorFactory {/*** 组合工厂类*/private ExecutorFactory factory;private StringTokenizer tokenizer;private String currentToken;public Context(String text) {tokenizer = new StringTokenizer(text);nextToken();}public String nextToken() {if (tokenizer.hasMoreTokens()) {currentToken = tokenizer.nextToken();} else {currentToken = null;}return currentToken;}public String currentToken() {return currentToken;}public void skipToken(String token) throws ParseException {if (!token.equals(currentToken)) {throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");}nextToken();}public int currentNumber() throws ParseException {int number = 0;try {number = Integer.parseInt(currentToken);} catch (NumberFormatException e) {throw new ParseException("Warning: " + e);}return number;}/*** 设置工厂* @param factory*/public void setExecutorFactory(ExecutorFactory factory) {this.factory = factory;}/*** 使用工厂的方法来创建具体的执行器* @param name* @return*/public Executor createExecutor(String name) {// 后面的终结符return factory.createExecutor(name);}
}

【Executor】

package com.atguigu.interpreter.A1.language;/*** 外观对象的窗口接口*/
public interface Executor {/*** 向系统外部提供一个接口* @throws ExecuteException*/public abstract void execute() throws ExecuteException;
}

【InterpreterFacade】

package com.atguigu.interpreter.A1.language;public class InterpreterFacade implements Executor {private ExecutorFactory factory;private Context context;private Node programNode;public InterpreterFacade(ExecutorFactory factory) {this.factory = factory;}/*** 提供给外层访问的解析接口* @param text* @return*/public boolean parse(String text) {boolean ok = true;this.context = new Context(text);this.context.setExecutorFactory(factory);this.programNode = new ProgramNode();try {// 开始解析programNode.parse(context);System.out.println(programNode.toString());} catch (ParseException e) {e.printStackTrace();ok = false;}return ok;}public void execute() throws ExecuteException {try {// 开始执行程序programNode.execute();} catch (ExecuteException e) {e.printStackTrace();}}
}

【ExecutorFactory】

package com.atguigu.interpreter.A1.language;public interface ExecutorFactory {/*** 创建一个执行器* @param name* @return*/public abstract Executor createExecutor(String name);
}

【TurtleCanvas】

package com.atguigu.interpreter.A1.turtle;import com.atguigu.interpreter.A1.language.ExecuteException;
import com.atguigu.interpreter.A1.language.Executor;
import com.atguigu.interpreter.A1.language.ExecutorFactory;import java.awt.*;public class TurtleCanvas extends Canvas implements ExecutorFactory {/*** 前进时的长度单位*/final static int UNIT_LENGTH = 30;/*** 上方*/final static int DIRECTION_UP = 0;/*** 右方*/final static int DIRECTION_RIGHT = 3;/*** 下方*/final static int DIRECTION_DOWN = 6;/*** 左方*/final static int DIRECTION_LEFT = 9;/*** 右转*/final static int RELATIVE_DIRECTION_RIGHT = 3;/*** 左转*/final static int RELATIVE_DIRECTION_LEFT = -3;/*** 半径*/final static int RADIUS = 3;/*** 移动方向*/private int direction = 0;/*** 小车的定位*/private Point position;private Executor executor;public TurtleCanvas(int width, int height) {// 设置画布尺寸setSize(width, height);initialize();}public void setExecutor(Executor executor) {this.executor = executor;}/*** 修改小车的行驶方向** @param relativeDirection*/void setRelativeDirection(int relativeDirection) {setDirection(direction + relativeDirection);}void setDirection(int direction) {if (direction < 0) {direction = 12 - (-direction) % 12;} else {direction = direction % 12;}this.direction = direction % 12;}/*** 让小车移动** @param length*/void go(int length) {int newx = position.x;int newy = position.y;switch (direction) {case DIRECTION_UP:newy -= length;break;case DIRECTION_RIGHT:newx += length;break;case DIRECTION_DOWN:newy += length;break;case DIRECTION_LEFT:newx -= length;break;default:break;}Graphics g = getGraphics();if (g != null) {g.drawLine(position.x, position.y, newx, newy);g.fillOval(newx - RADIUS, newy - RADIUS, RADIUS * 2 + 1, RADIUS * 2 + 1);}position.x = newx;position.y = newy;}/*** 使用工厂模式根据指令名称创建一个对应的执行器,并将其赋值给Executor** @param name* @return*/public Executor createExecutor(String name) {if (name.equals("go")) {return new GoExecutor(this);} else if (name.equals("right")) {return new DirectionExecutor(this, RELATIVE_DIRECTION_RIGHT);} else if (name.equals("left")) {return new DirectionExecutor(this, RELATIVE_DIRECTION_LEFT);} else {return null;}}/*** 初始化*/public void initialize() {Dimension size = getSize();// 将小车的初始位置放在画布的中心position = new Point(size.width / 2, size.height / 2);direction = 0;// 设置路径的颜色setForeground(Color.red);// 设置画布的背景颜色setBackground(Color.white);Graphics g = getGraphics();if (g != null) {// 清空画布g.clearRect(0, 0, size.width, size.height);}}/*** 绘制图像** @param g the specified Graphics context*/public void paint(Graphics g) {initialize();if (executor != null) {try {// 执行 执行器的方法 控制小车运动executor.execute();} catch (ExecuteException e) {}}}
}abstract class TurtleExecutor implements Executor {protected TurtleCanvas canvas;public TurtleExecutor(TurtleCanvas canvas) {this.canvas = canvas;}public abstract void execute();
}/*** 具体执行器:前进*/
class GoExecutor extends TurtleExecutor {public GoExecutor(TurtleCanvas canvas) {super(canvas);}public void execute() {// 调用前进方法在画布中绘制小车的前进路径canvas.go(TurtleCanvas.UNIT_LENGTH);}
}/*** 具体执行器:切换方向*/
class DirectionExecutor extends TurtleExecutor {private int relativeDirection;public DirectionExecutor(TurtleCanvas canvas, int relativeDirection) {super(canvas);this.relativeDirection = relativeDirection;}public void execute() {// 修改小车的方向canvas.setRelativeDirection(relativeDirection);}
}

【Main】

package com.atguigu.interpreter.A1;import com.atguigu.interpreter.A1.language.InterpreterFacade;
import com.atguigu.interpreter.A1.turtle.TurtleCanvas;import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;public class Main extends Frame implements ActionListener {private TurtleCanvas canvas = new TurtleCanvas(400, 400);private InterpreterFacade facade = new InterpreterFacade(canvas);/*** 默认的迷你程序*/private TextField programTextField = new TextField("program repeat 3 go right go left end end");/*** 构造函数** @param title*/public Main(String title) {super(title);canvas.setExecutor(facade);setLayout(new BorderLayout());programTextField.addActionListener(this);this.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);}});// 将文本输入框添加到布局的上部分add(programTextField, BorderLayout.NORTH);// 将画布放在布局的中心add(canvas, BorderLayout.CENTER);pack();parseAndExecute();show();}/*** 供ActionListener用,监听用户的输入,当用户输入完成并按下回车之后,方法被执行** @param e*/public void actionPerformed(ActionEvent e) {if (e.getSource() == programTextField) {parseAndExecute();}}/*** 解析迷你程序成指令,并执行指令*/private void parseAndExecute() {// 获取用户输入的迷你程序String programText = programTextField.getText();System.out.println("programText = " + programText);// 直接调用外观对象所提供的上层接口来使用解释器模式来解析迷你程序facade.parse(programText);// 重新绘制结果canvas.repaint();}public static void main(String[] args) {new Main("Interpreter Pattern Sample");}
}

【运行】

在这里插入图片描述

解释器模式在Spring框架中的应用

package com.atguigu.spring.test;import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;public class Interpreter {public static void main(String[] args) {//创建一个 Parser 对象SpelExpressionParser parser = new SpelExpressionParser();//通过 Parser 对象 获取到一个Expression对象//会根据不同的 Parser 对象 ,返回不同的 Expression 对象Expression expression = parser.parseExpression("10 * (2 + 1) * 1 + 66"); //结果:96int result = (Integer) expression.getValue();System.out.println(result);}}

在这里插入图片描述

Expression子类

在这里插入图片描述

在这里插入图片描述

【说明】

  • Expression接口是表达式接口,其下面有不同的实现类,比如SpelExpression或者CompositeStringExpression
  • 使用的时候,根据你创建的不同的Parser对象,返回不同的Expression对象

在这里插入图片描述

  • 最后使用得到的Expression对象,调用其getValue解释执行表达式,来得到结果

总结

【优点】

  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性

【缺点】

  • 解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面

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

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

相关文章

请解释一下CSS中的rem和em单位有什么不同,分别如何使用?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ CSS中的rem和em单位的区别和使用⭐ em单位使用示例&#xff1a; ⭐ rem 单位使用示例&#xff1a; ⭐ 区别和适用场景⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何…

Nginx常见的三个漏洞

目录 $uri导致的CRLF注入漏洞 两种常见场景 表示uri的三个变量 案例 目录穿越漏洞 案例 Http Header被覆盖的问题 案例 $uri导致的CRLF注入漏洞 两种常见场景 用户访问http://example.com/aabbcc&#xff0c;自动跳转到https://example.com/aabbcc 用户访问http://exa…

MySQL存储过程 、存储函数、以及优缺点

存储过程 VS 存储函数&#xff08;函数&#xff09; | | 关键字 |调用语法 | 返回值 | 应用场景 | |-存储过程-|-procedure-|-call 存储过程()-|-理解为0个或多个-|-一般用于更新-| | 存储函数 | function | select 函数() | 只能是一个 | 一般用于查询结构为一个值并返回时| …

讯飞星火、文心一言和通义千问同时编“贪吃蛇”游戏,谁会胜出?

同时向讯飞星火、文心一言和通义千问三个国产AI模型提个相同的问题&#xff1a; “python 写一个贪吃蛇的游戏代码” 看哪一家AI写的程序直接能用&#xff0c;谁就胜出&#xff01; 讯飞星火 讯飞星火给出的代码&#xff1a; import pygame import sys import random# 初…

步入React正殿 - React组件设计模式

目录 扩展学习资料 高阶组件 /src/components/hoc/withTooltip.js /src/components/hoc/itemA.jsx /src/components/hoc/itemB.jsx /src/App.js 函数作为子组件【Render pprops】 函数作为子组件 /src/components/rp/itemC.jsx【父组件】 /src/components/rp/withToo…

214、仿真-基于51单片机温度甲醛一氧化碳(co)电机净化报警Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…

Qt+Pyhton实现麒麟V10系统下word文档读写功能

目录 前言1.C调用python1.1 安装Python开发环境1.2 修改Qt工程配置1.3 初始化Python环境1.4 C 调用Python 函数1.5 常用的Python接口 2.python虚拟环境2.1Python虚拟环境简介2.2 virtualenv 安装及使用2.3 在C程序中配置virtualenv 虚拟环境 3.python-docx库的应用4.总结 前言 …

网络安全 Day29-运维安全项目-iptables防火墙

iptables防火墙 1. 防火墙概述2. 防火墙2.1 防火墙种类及使用说明2.2 必须熟悉的名词2.3 iptables 执行过程※※※※※2.4 表与链※※※※※2.4.1 简介2.4.2 每个表说明2.4.2.1 filter表 :star::star::star::star::star:2.4.2.2 nat表 2.5 环境准备及命令2.6 案例01&#xff1a…

ChatGLM2-6B安装部署(详尽版)

1、环境部署 安装Anaconda3 安装GIT 安装GUDA 11.8 安装NVIDIA 图形化驱动 522.25版本&#xff0c;如果电脑本身是更高版本则不用更新 1.1、检查CUDA 运行cmd或者Anaconda&#xff0c;运行以下命令 nvidia-smi CUDA Version是版本信息&#xff0c;Dricer Version是图形化…

LeetCode 160.相交链表

文章目录 &#x1f4a1;题目分析&#x1f4a1;解题思路&#x1f6a9;步骤一&#xff1a;找尾节点&#x1f6a9;步骤二&#xff1a;判断尾节点是否相等&#x1f6a9;步骤三&#xff1a;找交点&#x1f344;思路1&#x1f344;思路2 &#x1f514;接口源码 题目链接&#x1f449;…

C++之map的emplace与pair插入键值对用例(一百七十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

213、仿真-基于51单片机智能电表电能表用电量电费报警Proteus仿真设计(程序+Proteus仿真+原理图+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、原理图 五、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选…

java-JVM内存区域JVM运行时内存

一. JVM 内存区域 JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 HotspotVM 内, 每个线程都与操作系统的本地线程直接映…

SwiftUI 动画进阶:实现行星绕圆周轨道运动

0. 概览 SwiftUI 动画对于优秀 App 可以说是布帛菽粟。利用美妙的动画我们不仅可以活跃界面元素,更可以单独打造出一整套生动有机的世界,激活无限可能。 如上图所示,我们用动画粗略实现了一个小太阳系:8大行星围绕太阳旋转,而卫星们围绕各个行星旋转。 在本篇博文中,您将…

【第二讲---初识SLAM】

SLAM简介 视觉SLAM&#xff0c;主要指的是利用相机完成建图和定位问题。如果传感器是激光&#xff0c;那么就称为激光SLAM。 定位&#xff08;明白自身状态&#xff08;即位置&#xff09;&#xff09;建图&#xff08;了解外在环境&#xff09;。 视觉SLAM中使用的相机与常见…

Flink之Task解析

Flink之Task解析 对Flink的Task进行解析前,我们首先要清楚几个角色TaskManager、Slot、Task、Subtask、TaskChain分别是什么 角色注释TaskManager在Flink中TaskManager就是一个管理task的进程,每个节点只有一个TaskManagerSlotSlot就是TaskManager中的槽位,一个TaskManager中可…

数据结构单链表

单链表 1 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。 在我们开始讲链表之前&#xff0c;我们是写了顺序表&#xff0c;顺序表就是类似一个数组的东西&#xff0…

上海虚拟展厅制作平台怎么选,蛙色3DVR 助力行业发展

引言&#xff1a; 在数字化时代&#xff0c;虚拟展厅成为了企业宣传的重要手段。而作为一家位于上海的实力平台&#xff0c;上海蛙色3DVR凭借其卓越的功能和创新的技术&#xff0c;成为了企业展示和宣传的首选。 一、虚拟展厅的优势 虚拟展厅的崛起是指随着科技的进步&#x…

手机商城网站的分析与设计(论文+源码)_kaic

目录 摘 要 1 1 绪论 2 1.1选题背景意义 2 1.2国内外研究现状 2 1.2.1国内研究现状 2 1.2.2国外研究现状 3 1.3研究内容 3 2 网上手机商城网站相关技术 4 2.1.NET框架 4 2.2Access数据库 4 2.3 JavaScript技术 4 3网上手机商城网站分析与设…

Grafana+Prometheus技术文档-进阶使用-监控spring-boot项目

阿丹&#xff1a; 之前已经实现了使用Prometheus来对服务器进行了监控和仪表盘的创建&#xff0c;现在就需要对这些监控方法使用在spring-boot中去。 实现思路&#xff1a; 1、集成Actuator 2、加入Prometheus的依赖 3、配置开放端口、以及开放监控 4、配置Prometheus中的配置…