【设计模式——学习笔记】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),进而综合两者的内容,让知识点更加全面
相关文章:

【设计模式——学习笔记】23种设计模式——解释器模式Interpreter(原理讲解+应用场景介绍+案例介绍+Java代码实现)
案例引入 通过解释器模式来实现四则运算,如计算ab-c的值,具体要求 先输入表达式的形式,比如abc-de,要求表达式的字母不能重复在分别输入a,b,c,d,e的值最后求出结果 传统方案 编写一个方法,接收表达式的形式…...

【计算机网络】——数据链路层
二、组帧 1、字符计数法 帧头部使用一个字符来表示帧的大小(包括第一个计数字符) (此处一字符一个字节) 2、字符填充收尾定界法 特定字符来定界帧的首和尾。若帧中数据段出现等同于特定字符的字符内容,前置一个转义字符。(类似于正则表达…...

数据结构:栈和队列(超详细)
目录 编辑 栈: 栈的概念及结构: 栈的实现: 队列: 队列的概念及结构: 队列的实现: 扩展知识: 以上就是个人学习线性表的个人见解和学习的解析,欢迎各位大佬在评论区探讨&#…...

AI项目二:基于mediapipe的虚拟鼠标控制
若该文为原创文章,转载请注明原文出处。 一、项目介绍 由于博主太懒,mediapipe如何实现鼠标控制的原理直接忽略,最初的想法是想控制摄像头识别手指控制鼠标,达到播放电影的效果。基本上效果也是可以的。简单的说是使用mediapipe检…...

EVE-NG 隐藏没有镜像的模板
eve-ng 默认情况下,在添加node时,会列出所有的模板,这样用着很不方便。 通过以下方式,可以使没有设备的模板不可见 cp /opt/unetlab/html/includes/config.php.distribution /opt/unetlab/html/includes/config.php 打开 config…...

机器学习理论笔记(一):初识机器学习
文章目录 1 前言:蓝色是天的机器学习笔记专栏1.1 专栏初衷与定位1.2 本文主要内容 2 机器学习的定义2.1 机器学习的本质2.2 机器学习的分类 3 机器学习的基本术语4 探索"没有免费的午餐"定理(NFL)5 结语 1 前言:蓝色是天…...
Programming abstractions in C阅读笔记: p114-p117
《Programming Abstractions in C》学习第48天,p114-p117,总结如下: 一、技术总结 主要通过random number介绍了随机数的相关用法,interface示例(random.h),client program示例(craps.c)。 #include <stdio…...

分布式应用:Zabbix监控Tomcat
目录 一、理论 1.Zabbix监控Tomcat 二、实验 1.Zabbix监控Tomcat 三、问题 1.获取软件包失败 2.tomcat 配置 JMX remote monitor不生效 3.Zabbix客户端日志报错 一、理论 1.Zabbix监控Tomcat (1)环境 zabbix服务端:192.168.204.214 …...

《起风了》C++源代码
使用方法 Visual Studio、Dev-C、Visual Studio Code等C/C创建一个 .cpp 文件,直接粘贴赋值即可。 #include <iostream> #include <Windows.h> #pragma comment(lib,"winmm.lib") using namespace std; enum Scale {Rest 0, C8 108, B7 …...

Grafana展示k8s中pod的jvm监控面板/actuator/prometheus
场景 为保障java服务正常运行,对服务的jvm进行监控,通过使用actuator组件监控jvm情况,使用prometheus对数据进行采集,并在Grafana展现。 基于k8s场景 prometheus数据收集 配置service的lable,便于prometheus使用labl…...

实例038 设置窗体在屏幕中的位置
实例说明 在窗体中可以设置窗体居中显示,本例通过设置窗体的Left属性和Top属性可以准确设置窗体的位置。运行本例,效果如图1.38所示。 技术要点 设置窗体在屏幕中的位置,可以通过设置窗体的属性来实现。窗体的Left属性表示窗体距屏幕左侧的…...

合成数据及其在AI领域中的作用
什么是合成数据? 合成数据是由人工创建而非从现实生活中获得的数据,它从机器学习对数据的需求发展而来。最初,为了精确训练AI模型,必须获得涵盖所有可能场景的训练数据。如果某个场景没有发生或未被获得,就没有相应的…...

Java内存区域(运行时数据区域)和内存模型(JMM)
Java 内存区域和内存模型是不一样的东西,内存区域是指 Jvm 运行时将数据分区域存储,强调对内存空间的划分。 而内存模型(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 …...
【HDFS】hdfs的count命令的参数详解
Usage: hadoop fs -count [-q] [-h] [-v] [-x] [-t [<storage type>]] [-u] [-e] [-s] <paths...
Lombok注解在JSON化中,JSON生成额外生成字段问题
问题描述: 定义如下对象 Dataclass A{private String A;public String getC() {return "abab";}} 执行如下逻辑 Autowiredprivate ObjectMapper objectMapper;Testpublic void test4() throws Exception {A a new A();a.setA("a");System.ou…...

docker中的jenkins之流水线构建
docker中的jenkins之流水线构建项目 1、用node这种方式(因为我用pipeline方式一直不执行,不知道为什么) 2、创建项目 创建两个参数,一个是宿主端口号,一个是docker中的端口号 3、使用git项目中的Jenkinsfile 4、编写…...

ES中倒排索引机制
在ES的倒排索引机制中有四个重要的名词:Term、Term Dictionary、Term Index、Posting List。 Term(词条):词条是索引里面最小的存储和查询单元。一段文本经过分析器分析以后就会输出一串词条。一般来说英文语境中词条是一个单词&a…...

一生一芯4——使用星火应用商店在ubuntu下载QQ、微信、百度网盘
星火应用商店可以非常方便的完成一些应用的下载,下面是官方网址 http://spark-app.store/download 我使用的是intel处理器,无需下载依赖项,直接点击软件本体 我这里下载amd64,根据自己的处理器下载对应版本 sudo apt install ./spark-stor…...

编程练习(1)
目录 一.选择题 第一题: 第二题: 第三题: 第四题: 第五题: 编辑 二.编程题 第一题: 第二题: 1.暴力方法: 2.数组法: 一.选择题 第一题: 解析&…...

pytorch安装VAE项目详解
安装VAE项目 一、 基本环境二、代码来源三、搭建conda环境四、下载数据集五、启动项目六、其他相关问题 一、 基本环境 工具版本号OSwin 11pycharm2020.1GPU3050 二、代码来源 github地址为: https://github.com/AntixK/PyTorch-VAE/blob/8700d245a9735640dda458d…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...