【设计模式——学习笔记】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.rhkb.cn/news/93245.html

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

相关文章

长胜证券:宁德时代发布神行超充电池 信创利好政策陆续出台

昨日&#xff0c;两市股指早盘震动回升&#xff0c;午后再度回落&#xff0c;尾盘加快下行。截至收盘&#xff0c;沪指跌0.82%报3150.13点&#xff0c;深成指跌0.94%报10579.56点&#xff0c;创业板指跌0.73%报2132.97点&#xff0c;科创50指数跌1.71%&#xff1b;两市算计成交…

最新Ubuntu LVGL SDL模拟器安装

前言 本文主要说明Ubuntu 23.4安装LVGL 9.0以及基于SDL的模拟环境。 代码下载 访问lv_port_pc_eclipse可以看到相信信息&#xff0c;官方已经打包好了整个代码环境。 安装CMAKE。 sudo apt install cmake安装SDL。 sudo apt-get update && sudo apt-get install …

云原生 envoy xDS 动态配置 java控制平面开发 支持restful grpc实现 EDS 动态endpoint配置

envoy xDS 动态配置 java控制平面开发 支持restful grpc 动态endpoint配置 大纲 基础概念Envoy 动态配置API配置方式动静结合的配置方式纯动态配置方式实战 基础概念 Envoy 的强大功能之一是支持动态配置&#xff0c;当使用动态配置时&#xff0c;我们不需要重新启动 Envoy…

安科瑞变电所运维平台在电力系统中应用分析

摘要&#xff1a;现代居民生活、工作对电力资源的需求量相对较多&#xff0c;给我国的电力产业带来了良好的发展机遇与挑战。探索电力系统基本构成&#xff0c; 将变电运维安全管理以及相应的设备维护工作系统性开展&#xff0c;能够根据项目实践工作要求&#xff0c;将满足要求…

hbase 报错 Master passed us a different hostname to use; was=

原因 wsl2的 /etc/hosts 配置的不兼容,我这里是ubuntu22 命令行输入hostname 看输出什么,比如输出 aaa 那么替换/etc/hosts 127.0.0.1 aaa

Mysql之explain详解

1. explain作用 使用explain可以展示出sql语句的执行计划&#xff0c;再根据sql的执行计划去判断这条sql有哪些点可以进行优化&#xff0c;从而让sql的效率达到最大化。 2. 执行计划各列含义 &#xff08;1&#xff09;id&#xff1a;id列是select的序列号&#xff0c;这个…

【BASH】回顾与知识点梳理(三十二)

【BASH】回顾与知识点梳理 三十二 三十二. SELinux 初探32.1 什么是 SELinux当初设计的目标&#xff1a;避免资源的误用传统的文件权限与账号关系&#xff1a;自主式访问控制, DAC以政策规则订定特定进程读取特定文件&#xff1a;委任式访问控制, MAC 32.2 SELinux 的运作模式安…

nvm管理node版本

nvm是什么&#xff1f; NVM全名叫做 nodejs version manage,即Node的版本管理工具。 使用NVM&#xff0c;可以通过命令很方便地在多个NodeJS版本之间进行切换。 nvm的下载与安装 下载地址&#xff1a;Releases coreybutler/nvm-windows (github.com) windows系统下载nvm-setup…

【自动化测试】接口自动化01

文章目录 一、熟悉若requests库以及底层方法的调用逻辑二、接口自动化以及正则和Jsonpath提取器的应用6. 高频面试题&#xff1a;9. 示例&#xff1a;接口关联13. 文件上传示例14. cookie关联的接口 努力经营当下 直至未来明朗 一、熟悉若requests库以及底层方法的调用逻辑 接…

RTT(RT-Thread)ADC设备(RTT保姆级介绍)

目录 ADC设备 前言 ADC相关参数说明 访问ADC设备 配置ADC设备 ADC实例 硬件设计 软件设计 ADC设备 前言 ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。 对于ADC的详细介绍和在STM32中的裸机应用可参考以下…

在CentOS 7上使用kubeadm部署Kubernetes集群

如有错误&#xff0c;敬请谅解&#xff01; 此文章仅为本人学习笔记&#xff0c;仅供参考&#xff0c;如有冒犯&#xff0c;请联系作者删除&#xff01;&#xff01; 前言&#xff1a; Kubernetes是一个开源的容器编排平台&#xff0c;用于管理和自动化部署容器化的应用程序。…

软文发布问题解答:高效宣传与推广指南

以下是一秒推小编针对软文发布的20个常见问题及回答&#xff1a; 1. 什么是软文&#xff1f; 答&#xff1a;软文是指用文学手法、写作技巧撰写的宣传文章&#xff0c;以实现对特定受众的陈述、说明和推销。 2. 发布软文的目的是什么&#xff1f; 答&#xff1a;发布软文的目…

出现ffmpeg.dll丢失的修复方法分享,教你快速修复ffmpeg.dll文件

当你使用或尝试运行与FFmpeg相关的应用程序时&#xff0c;可能会遇到一个常见的问题&#xff0c;ffmpeg.dll文件丢失。这个动态链接库文件对于正常运行FFmpeg应用程序至关重要。在本文中&#xff0c;我们将详细探讨为什么会出现ffmpeg.dll丢失的情况&#xff0c;并提供一些修复…

奥威BI数据可视化工具:360度呈现数据,告别枯燥表格

随着企业数据量的不断增加&#xff0c;如何有效地进行数据分析与决策变得越来越重要。奥威BI数据可视化工具作为一款强大的数据分析工具&#xff0c;在帮助企业深入挖掘数据价值方面具有显著优势。 奥威BI数据可视化工具是一款基于数据仓库技术的数据分析工具&#xff0c;具有…

海外应用ASO优化的小技巧3

编写准确的应用程序描述&#xff0c;有4000个字符限制。用户可以访问应用程序的主要功能和信息&#xff0c;要说服并推动用户去下载我们的应用&#xff0c;需要使用自然流利的语言。 1、填写应用描述的方案。 我们需要知道该应用有什么用&#xff0c;能够解决什么问题&#xf…

在医疗行业数字孪生能做些什么?

数字孪生技术随着发展正在多行业遍地开花&#xff0c;在之前的文章中也为大家介绍过数字孪生的行业应用&#xff0c;今天带大家一起探讨一下数字孪生在医疗行业的表现。其实数字孪生在医疗行业已有很多应用案例&#xff0c;从医疗诊断到手术模拟&#xff0c;再到药物研发&#…

基于Redis实现点赞及排行榜功能

微信公众号访问地址&#xff1a;基于Redis实现点赞及排行榜功能 推荐文章&#xff1a; 1、SpringBoot整合多数据源&#xff0c;并支持动态新增与切换&#xff08;详细教程&#xff09; 2、​​​​​​Redis中使用lua脚本 3、SpringBoot统一标准响应格式及异常处理 4、Spr…

linux两台服务器互相备份文件(sshpass + crontab)

crontab crontab是linux系统自带的定时调度软件&#xff0c;可用于设置周期性被执行的指令&#xff0c;一般用在每天的非高峰负荷时间段运行作业&#xff0c;可在无需人工干预的情况下运行作业。支持在一周或一月中的不同时段运行。 crontab命令允许用户提交、编辑或删除相应的…

MongoDB:数据库初步应用

一.连接MongoDB 1.MongoDBCompass连接数据库 连接路径:mongodb://用户名:密码localhost:27017/ 2.创建数据库(集合) MongoDB中数据库被称为集合. MongoDBCompass连接后,点击红色框加号创建集合,点击蓝色框加号创建文档(数据表) 文档中的数据结构(相当于表中的列)设计不用管…

安装chromedriver 115,对应chrome版本115(经检验,116也可以使用)

目录 1. 查看Chrome浏览器的版本2. 找到对应的chromedriver3. 安装ChromeDriver 1. 查看Chrome浏览器的版本 点进这个网站查看&#xff1a;chrome://settings/help &#xff08;真是的&#xff0c;上一秒还是115版本&#xff0c;更新后就是116版本了&#xff0c;好在chromedi…