【設計模式——學習筆記】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.pswp.cn/news/40774.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40774.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40774.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于Pan-Tompkins的實時QRS檢測算法:便攜式ANSI-C實現深入解析

引言 隨著醫學工程和移動設備技術的進步&#xff0c;實時QRS檢測算法在心電圖分析中變得越來越重要。其中&#xff0c;Pan-Tompkins算法由于其高效性和準確度&#xff0c;在許多應用中都受到廣泛認可。本文將深入探討此算法的ANSI-C實現&#xff0c;并提供詳細的代碼實例。 1…

【kubernetes】配置資源管理

目錄 Secret 創建 Secret 1、用kubectl create secret命令創建Secret 2、內容用 base64 編碼&#xff0c;創建Secret 使用方式 1、將 Secret 掛載到 Volume 中&#xff0c;以 Volume 的形式掛載到 Pod 的某個目錄下 2、將 Secret 導出到環境變量中 ConfigMap 創建 Co…

請解釋一下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…

[英語單詞] compat; compatibility;compact;entry_SYSENTER_compat

簡介 這個詞compat&#xff0c;馬上就會被簡寫形式所替代。所以一定不要和compact混淆。第一次看到還以為是個新詞來&#xff0c;后來發現是一個縮寫形式。就是兼容的意思&#xff0c;就如同兼容以往的就有事物。 syscall: 32bit: 兼容 entry_SYSENTER_compat 這個是32位程序…

MySQL存儲過程 、存儲函數、以及優缺點

存儲過程 VS 存儲函數&#xff08;函數&#xff09; | | 關鍵字 |調用語法 | 返回值 | 應用場景 | |-存儲過程-|-procedure-|-call 存儲過程()-|-理解為0個或多個-|-一般用于更新-| | 存儲函數 | function | select 函數() | 只能是一個 | 一般用于查詢結構為一個值并返回時| …

三、python Django ORM postgresql[數據定時備份、數據恢復]

一、數據定時備份 解釋&#xff1a;備份指定數據庫&#xff0c;能有效在發生錯誤時&#xff0c;預防錯誤&#xff0c;進行恢復 1.基本備份 #!/bin/bash sudo -u postgres pg_dump -U postgres -d dbname -Fc > /home/postgres/backup/backup.dump # sudo -u postgres&…

訊飛星火、文心一言和通義千問同時編“貪吃蛇”游戲,誰會勝出?

同時向訊飛星火、文心一言和通義千問三個國產AI模型提個相同的問題&#xff1a; “python 寫一個貪吃蛇的游戲代碼” 看哪一家AI寫的程序直接能用&#xff0c;誰就勝出&#xff01; 訊飛星火 訊飛星火給出的代碼&#xff1a; import pygame import sys import random# 初…

Android 13 開啟關閉飛行模式

一.背景 由于客戶定制的Settings里面需要開啟和關閉飛行模式,所以需要實現此功能。 二.前提條件 首先應用肯定要是系統應用,并且導入framework.jar包,具體可以參考: Android 應用自動開啟輔助(無障礙)功能并使用輔助(無障礙)功能_android 自動開啟無障礙服務_龔禮鵬的博客…

步入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…

C#調用C++ DLL傳參byte[]數組字節值大于127時會變為0x3f的問題解決

最近做了一個網絡編程的DLL給C#調用&#xff0c;DLL中封裝了一個TCP Client的函數接口&#xff0c;如下所示 //C TCP報文發送接口 int TcpClient_send(unsigned char* buffSend, unsigned int nLen) {unsigned char buff[1024];int len StringToHex(buffSend, buff);int nRet…

stable diffusion安裝包和超火使用文檔,數字人制作網址

一&#xff1a;文生圖、圖生圖 1&#xff1a;stable diffusion&#xff1a;對喜歡二次元、美女小姐姐、大眼萌妹的人及其友好哈哈(o^^o) 1&#xff09;&#xff1a;秋葉大神安裝包和模型包&#xff1a; 鏈接&#xff1a;https://pan.baidu.com/s/11_kguofh76gwhTBPUipepw 提…

機器學習 | Python實現GBDT梯度提升樹模型設計

機器學習 | Python實現GBDT梯度提升樹模型設計 目錄 機器學習 | Python實現GBDT梯度提升樹模型設計基本介紹模型描述模型使用參考資料基本介紹 機器學習 | Python實現GBDT梯度提升樹模型設計。梯度提升樹(Grandient Boosting)是提升樹(Boosting Tree)的一種改進算法,GBDT也…

Java System.arraycopy() 對比 C++ memcpy()

System.arraycopy() java.lang.System類為標準輸入和輸出、加載文件和庫或訪問外部定義的屬性提供了有用的方法。 java.lang.System.arraycopy&#xff08;&#xff09;方法將源數組從特定的起始位置復制到上述位置的目標數組。要復制的參數的數量由一個參數決定。 source_Pos…

前端文件下載通用方法

zip文件和xlsx文件 import axios from axios import { getToken } from /utils/authconst mimeMap {xlsx: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,zip: application/zip }const baseUrl process.env.VUE_APP_BASE_API // zip下載 export functi…

214、仿真-基于51單片機溫度甲醛一氧化碳(co)電機凈化報警Proteus仿真設計(程序+Proteus仿真+配套資料等)

畢設幫助、開題指導、技術解答(有償)見文未 目錄 一、硬件設計 二、設計功能 三、Proteus仿真圖 四、程序源碼 資料包括&#xff1a; 需要完整的資料可以點擊下面的名片加下我&#xff0c;找我要資源壓縮包的百度網盤下載地址及提取碼。 方案選擇 單片機的選擇 方案一&a…

It‘s likely that neither a Result Type nor a Result Map was specified.

問題&#xff1a; org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were 原因&#xff1a; 由于傳遞參數給 mapper 映射文件&#xff0c;所以必須要指定參數數據格式 如…

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.總結 前言 …

神經網絡基礎-神經網絡補充概念-23-神經網絡的梯度下降法

概念 神經網絡的梯度下降法是訓練神經網絡的核心優化算法之一。它通過調整神經網絡的權重和偏差&#xff0c;以最小化損失函數&#xff0c;從而使神經網絡能夠逐漸逼近目標函數的最優值。 步驟 1損失函數&#xff08;Loss Function&#xff09;&#xff1a; 首先&#xff0c…

Springboot多路數據源

1、多路數據源配置 &#xff08;1&#xff09;SpringBootMyBatis-PlusOracle實現多數據源配置 https://blog.csdn.net/weixin_44812604/article/details/127386828 &#xff08;2&#xff09;SpringBootMybatis搭建Oracle多數據源配置簡述 https://blog.csdn.net/HJW_233/arti…