解釋器模式(行為型)
解釋器模式很難學(xué)俱萍,使用率很低饶氏!
一矫废、相關(guān)概念
1). 解釋器模式概述
解釋器模式是一種使用頻率相對較低但學(xué)習(xí)難度較大的設(shè)計模式绽快,它用于描述如何使用面向?qū)ο笳Z言構(gòu)成一個簡單的語言解釋器。在某些情況下刁绒,為了更好地描述某一些特定類型的問題,我們可以創(chuàng)建一種新的語言烤黍,這種語言擁有自己的表達(dá)式和結(jié)構(gòu)知市,即文法規(guī)則,這些問題的實例將對應(yīng)為該語言中的句子速蕊。此時嫂丙,可以使用解釋器模式來設(shè)計這種新的語言。對解釋器模式的學(xué)習(xí)能夠加深我們對面向?qū)ο笏枷氲睦斫夤嬲埽⑶艺莆站幊陶Z言中文法規(guī)則的解釋過程跟啤。
解釋器模式:定義一個語言的文法,并且建立一個解釋器來解釋該語言中的句子唉锌,這里的“語言”是指使用規(guī)定格式和語法的代碼隅肥。解釋器模式是一種類行為型模式。
結(jié)構(gòu)圖
由于表達(dá)式可分為終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式袄简,因此解釋器模式的結(jié)構(gòu)與組合模式的結(jié)構(gòu)有些類似腥放,但在解釋器模式中包含更多的組成元素
2). 相關(guān)角色
- AbstractExpression(抽象表達(dá)式):在抽象表達(dá)式中聲明了抽象的解釋操作,它是所有終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式的公共父類绿语。
- TerminalExpression(終結(jié)符表達(dá)式):終結(jié)符表達(dá)式是抽象表達(dá)式的子類秃症,它實現(xiàn)了與文法中的終結(jié)符相關(guān)聯(lián)的解釋操作,在句子中的每一個終結(jié)符都是該類的一個實例吕粹。通常在一個解釋器模式中只有少數(shù)幾個終結(jié)符表達(dá)式類种柑,它們的實例可以通過非終結(jié)符表達(dá)式組成較為復(fù)雜的句子。
- NonterminalExpression(非終結(jié)符表達(dá)式):非終結(jié)符表達(dá)式也是抽象表達(dá)式的子類匹耕,它實現(xiàn)了文法中非終結(jié)符的解釋操作聚请,由于在非終結(jié)符表達(dá)式中可以包含終結(jié)符表達(dá)式,也可以繼續(xù)包含非終結(jié)符表達(dá)式泌神,因此其解釋操作一般通過遞歸的方式來完成良漱。
- Context(環(huán)境類):環(huán)境類又稱為上下文類舞虱,它用于存儲解釋器之外的一些全局信息,通常它臨時存儲了需要解釋的語句母市。
3). 典型代碼
- 抽象表達(dá)式
import java.util.HashMap;
/**
* 抽象表達(dá)式
*/
abstract class AbstractExpression {
public abstract void interpret(Context context);
}
在解釋器模式中矾兜,每一種終結(jié)符和非終結(jié)符都有一個具體類與之對應(yīng),正因為使用類來表示每一條文法規(guī)則患久,所以系統(tǒng)將具有較好的靈活性和可擴(kuò)展性椅寺。對于所有的終結(jié)符和非終結(jié)符,我們首先需要抽象出一個公共父類蒋失,即抽象表達(dá)式類
- 終結(jié)符表達(dá)式
/**
* 終結(jié)符表達(dá)式
*/
class TerminalExpression extends AbstractExpression {
@Override
public void interpret(Context context) {
}
}
終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式類都是抽象表達(dá)式類的子類返帕,對于終結(jié)符表達(dá)式,其代碼很簡單篙挽,主要是對終結(jié)符元素的處理荆萤。
- 非終結(jié)符表達(dá)式
/**
* 非終結(jié)符表達(dá)式
*/
class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public void interpret(Context context) {
// 遞歸調(diào)用每一個組成部分的interpret方法
// 在遞歸調(diào)用時指定組成部分的連接方式,即非終結(jié)符的功能铣卡。
}
}
對于非終結(jié)符表達(dá)式链韭,其代碼相對比較復(fù)雜,因為可以通過非終結(jié)符將表達(dá)式組合成更加復(fù)雜的結(jié)構(gòu)煮落,對于包含兩個操作元素的非終結(jié)符表達(dá)式類敞峭。
- 上下文環(huán)境
/**
* 上下文環(huán)境類
*/
class Context {
private HashMap<String, String> map = new HashMap<String, String>();
public void assign(String key, String value) {
// 往環(huán)境中設(shè)值
map.put(key, value);
}
public String lookup(String key) {
// 獲取存儲在環(huán)境類中的值
return map.get(key);
}
}
除了上述用于表示表達(dá)式的類以外,通常在解釋器模式中還提供了一個環(huán)境類Context蝉仇,用于存儲一些全局信息旋讹,通常在Context中包含了一個HashMap或ArrayList等類型的集合對象(也可以直接由HashMap等集合類充當(dāng)環(huán)境類),存儲一系列公共信息轿衔,如變量名與值的映射關(guān)系(key/value)等沉迹,用于在進(jìn)行具體的解釋操作時從中獲取相關(guān)信息。
當(dāng)系統(tǒng)無須提供全局公共信息時可以省略環(huán)境類害驹,可根據(jù)實際情況決定是否需要環(huán)境類胚股。
二、解釋器模式設(shè)計步驟
1). 案例需求描述
提供一個簡單的加法/減法解釋器裙秋,只要輸入一個加法/減法表達(dá)式琅拌,它就能夠計算出表達(dá)式結(jié)果
2). 解析為文法規(guī)則和抽象語法樹
- 文法規(guī)則
expression ::= value | operation
operation ::= expression '+' expression | expression '-' expression
value ::= an integer //一個整數(shù)值
在解釋器模式中每一個文法規(guī)則語句都將對應(yīng)一個類,擴(kuò)展摘刑、改變文法以及增加新的文法規(guī)則都很方便进宝。
該文法規(guī)則包含三條語句,第一條表示表達(dá)式的組成方式枷恕,其中value和operation是后面兩個語言單位的定義党晋,每一條語句所定義的字符串如operation和value稱為語言構(gòu)造成分或語言單位,符號“::=”表示“定義為”的意思,其左邊的語言單位通過右邊來進(jìn)行說明和定義未玻,語言單位對應(yīng)終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式灾而。如本規(guī)則中的operation是非終結(jié)符表達(dá)式,它的組成元素仍然可以是表達(dá)式扳剿,可以進(jìn)一步分解旁趟,而value是終結(jié)符表達(dá)式,它的組成元素是最基本的語言單位庇绽,不能再進(jìn)行分解锡搜。
在文法規(guī)則定義中可以使用一些符號來表示不同的含義,如使用“|”表示或瞧掺,使用“{”和“}”表示組合耕餐,使用“*”表示出現(xiàn)0次或多次等,其中使用頻率最高的符號是表示“或”關(guān)系的“|”辟狈,如文法規(guī)則“boolValue ::= 0 | 1”表示終結(jié)符表達(dá)式boolValue的取值可以為0或者1肠缔。
- 抽象語法樹
除了使用文法規(guī)則來定義一個語言,在解釋器模式中還可以通過一種稱之為抽象語法樹(Abstract Syntax Tree, AST)的圖形方式來直觀地表示語言的構(gòu)成哼转,每一棵抽象語法樹對應(yīng)一個語言實例桩砰,如加法/減法表達(dá)式語言中的語句“1+ 2 + 3 – 4 + 1”,可以通過如圖18-2所示抽象語法樹來表示:
在該抽象語法樹中释簿,可以通過終結(jié)符表達(dá)式value和非終結(jié)符表達(dá)式operation組成復(fù)雜的語句,每個文法規(guī)則的語言實例都可以表示為一個抽象語法樹硼莽,即每一條具體的語句都可以用類似圖18-2所示的抽象語法樹來表示庶溶,在圖中終結(jié)符表達(dá)式類的實例作為樹的葉子節(jié)點,而非終結(jié)符表達(dá)式類的實例作為非葉子節(jié)點懂鸵,它們可以將終結(jié)符表達(dá)式類的實例以及包含終結(jié)符和非終結(jié)符實例的子表達(dá)式作為其子節(jié)點偏螺。抽象語法樹描述了如何構(gòu)成一個復(fù)雜的句子,通過對抽象語法樹的分析匆光,可以識別出語言中的終結(jié)符類和非終結(jié)符類套像。
3). 代碼實現(xiàn)
- 抽象表達(dá)式
import java.util.Stack;
/**
* 抽象表達(dá)式
*/
interface CalculateNode {
public abstract double interpret();
}
- 非終結(jié)表達(dá)式
/**
* 非終結(jié)符表達(dá)式:符號解釋
*/
class SymbolNode implements CalculateNode {
// SymbolNode的左表達(dá)式
private CalculateNode left;
// SymbolNode的右表達(dá)式
private CalculateNode right;
// Symbol符號 對應(yīng) + -
private String symbol;
public SymbolNode(CalculateNode left, CalculateNode right, String symbol) {
this.left = left;
this.right = right;
this.symbol = symbol;
}
// 符號表達(dá)式解釋操作
@Override
public double interpret() {
switch (this.symbol) {
case "+" : return left.interpret() + right.interpret();
case "-" : return left.interpret() - right.interpret();
default: throw new RuntimeException("操作符格式不正確");
}
}
}
- 終結(jié)表達(dá)式
/**
* 終結(jié)表達(dá)式:數(shù)字類
*/
class NumberNode implements CalculateNode {
private String number;
public NumberNode(String number) {
this.number = number;
}
// 簡單句子的解釋操作
@Override
public double interpret() {
return Double.parseDouble(this.number);
}
}
- 指令處理器
/**
* 指令處理器:工具類
*/
class InstructionHandlerForNumber {
private String instruciton;
private CalculateNode node;
public void handle(String instruciton) {
this.instruciton = instruciton;
CalculateNode left = null;
CalculateNode right = null;
// 聲明一個棧對象用于存儲抽象語法樹
Stack<CalculateNode> stack = new Stack<>();
// 以空格分隔指令字符串
String[] words = instruciton.split(" ");
// 將第一個數(shù)字入棧
stack.push(new NumberNode(words[0]));
// 從第二個開始解析
for (int i = 1; i < words.length;) {
// 彈出棧頂表達(dá)式作為左語句
left = stack.pop();
String symbol = words[i++];
right = new NumberNode(words[i++]);
stack.push(new SymbolNode(left, right, symbol));
}
// 將整個解析的指令全部取出
this.node = stack.pop();
}
public double output() {
return node.interpret();
}
}
- 客戶端測試
/**
* @author Liucheng
* @since 2019-09-03
*/
public class ClientThree {
public static void main(String[] args) {
String instruciton = "1 + 6 - 2 + 3";
InstructionHandlerForNumber handler = new InstructionHandlerForNumber();
handler.handle(instruciton);
double output = handler.output();
System.out.println(output);
}
}
三、復(fù)雜案例演示
1). 案例需求描述
Sunny軟件公司欲為某玩具公司開發(fā)一套機(jī)器人控制程序终息,在該機(jī)器人控制程序中包含一些簡單的英文控制指令夺巩,每一個指令對應(yīng)一個表達(dá)式(expression),該表達(dá)式可以是簡單表達(dá)式也可以是復(fù)合表達(dá)式周崭,每一個簡單表達(dá)式由移動方向(direction)柳譬,移動方式(action)和移動距離(distance)三部分組成,其中移動方向包括上(up)续镇、下(down)美澳、左(left)、右(right);移動方式包括移動(move)和快速移動(run)制跟;移動距離為一個正整數(shù)舅桩。兩個表達(dá)式之間可以通過與(and)連接,形成復(fù)合(composite)表達(dá)式雨膨。例如:“down run 10 and left move 20” 可以解釋為 “向下快速移動10個單位再向左移動20個單位”
2). 文法規(guī)則描述
- 文法規(guī)則描述
expression ::= direction action distance | composite //表達(dá)式
composite ::= expression 'and' expression //復(fù)合表達(dá)式
direction ::= 'up' | 'down' | 'left' | 'right' //移動方向
action ::= 'move' | 'run' //移動方式
distance ::= an integer //移動距離
上述語言一共定義了五條文法規(guī)則擂涛,對應(yīng)五個語言單位(解釋器模式中每一個文法規(guī)則都將對應(yīng)一個類,擴(kuò)展哥放、改變文法以及增加新的文法規(guī)則都很方便)歼指,這些語言單位可以分為兩類,一類為終結(jié)符(也稱為終結(jié)符表達(dá)式)甥雕,例如direction踩身、action和distance,它們是語言的最小組成單位社露,不能再進(jìn)行拆分挟阻;另一類為非終結(jié)符(也稱為非終結(jié)符表達(dá)式),例如expression和composite峭弟,它們都是一個完整的句子附鸽,包含一系列終結(jié)符或非終結(jié)符。
- 抽象語法樹舉例
“down run 10 and left move 20”
3). 類圖設(shè)計
4). 完整代碼
- 抽象表達(dá)式
package interpreterpattern;
import java.util.Stack;
/**
* 抽象表達(dá)式
*/
abstract class AbstractNode {
public abstract String interpret();
}
- 非終結(jié)符表達(dá)式
/**
* 非終結(jié)符表達(dá)式:And解釋
*/
class AndNode extends AbstractNode {
// And的左表達(dá)式
private AbstractNode left;
// And的右表達(dá)式
private AbstractNode right;
public AndNode(AbstractNode left, AbstractNode right) {
this.left = left;
this.right = right;
}
// And表達(dá)式解釋操作
@Override
public String interpret() {
return left.interpret() + "再" + right.interpret();
}
}
/**
* 非終結(jié)符表達(dá)式:簡單句子解釋
*/
class SentenceNode extends AbstractNode {
private AbstractNode direction;
private AbstractNode action;
private AbstractNode distance;
public SentenceNode(AbstractNode direction, AbstractNode action, AbstractNode distance) {
this.direction = direction;
this.action = action;
this.distance = distance;
}
// 簡單句子的解釋操作
@Override
public String interpret() {
StringBuilder sb = new StringBuilder();
sb.append(direction.interpret())
.append(action.interpret())
.append(distance.interpret());
return sb.toString();
}
}
- 終結(jié)符表達(dá)式
/**
* 終結(jié)符表達(dá)式:方向類
*/
class DirectionNode extends AbstractNode {
private String direction;
public DirectionNode(String direction) {
this.direction = direction;
}
// 方向表達(dá)式的解釋操作
@Override
public String interpret() {
switch (this.direction) {
case "up": return "向上";
case "down": return "向下";
case "left": return "向左";
case "right": return "向右";
default:return "無效指令";
}
}
}
/**
* 終結(jié)符表達(dá)式:動作解釋
*/
class ActionNode extends AbstractNode {
private String action;
public ActionNode(String action) {
this.action = action;
}
// 表達(dá)式解釋
@Override
public String interpret() {
switch (this.action) {
case "move": return "移動";
case "run": return "快速移動";
default:return "無效指令";
}
}
}
/**
* 終結(jié)符表達(dá)式瞒瘸;距離解釋
*/
class DistanceNode extends AbstractNode {
private String distance;
public DistanceNode(String distance) {
this.distance = distance;
}
// 距離解釋
@Override
public String interpret() {
return this.distance;
}
}
- 指令處理器
/**
* 指令處理器:工具類
*/
class InstructionHandler {
private String instruciton;
private AbstractNode node;
public void handle(String instruciton) {
AbstractNode left = null;
AbstractNode right = null;
AbstractNode direction = null;
AbstractNode action = null;
AbstractNode distance = null;
// 聲明一個棧對象用于存儲抽象語法樹
Stack<AbstractNode> stack = new Stack<>();
// 以空格分隔指令字符串
String[] words = instruciton.split(" ");
// 將前三個單詞取出坷备,解析并放入棧中
AbstractNode firstNode = new SentenceNode(
new DirectionNode(words[0]),
new ActionNode(words[1]),
new DistanceNode(words[2]));
stack.push(firstNode);
// 此處有個and連接詞,將其屏蔽掉
for (int i = 4; i < words.length; i++) {
// 本實例采用棧的方式來處理指令情臭,如果遇到"and",
// 則將其后的三個單詞作為三個終結(jié)符表達(dá)式
String word1 = words[i++];
direction = new DirectionNode(word1);
String word2 = words[i++];
action = new ActionNode(word2);
String word3 = words[i++];
distance = new DistanceNode(word3);
// 彈出棧頂表達(dá)式作為左語句
left = stack.pop();
// 創(chuàng)建右語句
right = new SentenceNode(direction, action, distance);
// 將新表達(dá)式壓入棧中
stack.push(new AndNode(left, right));
}
// 將整個解析的指令全部取出
this.node = stack.pop();
}
public String output() {
return node.interpret();
}
}
- 客戶端測試類
/**
* @author liucheng
* @since 2019/9/3
*/
public class Client {
public static void main(String[] args) {
String instruction = "up move 5 and down run 10 and left move 3 and right run 2";
InstructionHandler handler = new InstructionHandler();
handler.handle(instruction);
System.out.println(handler.output());
}
}
四省撑、Context的作用【難點*****】
在解釋器模式中,環(huán)境類Context用于存儲解釋器之外的一些全局信息俯在,它通常作為參數(shù)被傳遞到所有表達(dá)式的解釋方法interpret()中竟秫,可以在Context對象中存儲和訪問表達(dá)式解釋器的狀態(tài),向表達(dá)式解釋器提供一些全局的跷乐、公共的數(shù)據(jù)肥败,此外還可以在Context中增加一些所有表達(dá)式解釋器都共有的功能,減輕解釋器的職責(zé)愕提。接下來以一個案例進(jìn)行演示說明馒稍。
1). 案例需求描述
Sunny軟件公司開發(fā)了一套簡單的基于字符界面的格式化指令,可以根據(jù)輸入的指令在字符界面中輸出一些格式化內(nèi)容浅侨,例如輸入LOOP 2 PRINT 楊過 SPACE SPACE PRINT 小龍女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黃蓉
筷黔,將輸出如下結(jié)果:
楊過 小龍女
楊過 小龍女
郭靖 黃蓉
其中關(guān)鍵詞LOOP表示“循環(huán)”,后面的數(shù)字表示循環(huán)次數(shù)仗颈;PRINT表示“打印”佛舱,后面的字符串表示打印的內(nèi)容椎例;SPACE表示“空格”;BREAK表示“換行”请祖;END表示“循環(huán)結(jié)束”订歪。每一個關(guān)鍵詞對應(yīng)一條命令,計算機(jī)程序?qū)⒏鶕?jù)關(guān)鍵詞執(zhí)行相應(yīng)的處理操作肆捕。
2). 文法規(guī)則設(shè)計
expression ::= command* //表達(dá)式刷晋,一個表達(dá)式包含多條命令
command ::= loop | primitive //語句命令
loop ::= 'loopnumber' expression 'end' //循環(huán)命令,其中number為自然數(shù)
primitive ::= 'printstring' | 'space' | 'break' //基本命令慎陵,其中string為字符串
抽象語法數(shù)舉例略
3). 類圖設(shè)計
根據(jù)4條文法規(guī)則設(shè)計類圖
Context充當(dāng)環(huán)境角色眼虱,Node充當(dāng)抽象表達(dá)式角色,ExpressionNode席纽、CommandNode和LoopCommandNode充當(dāng)非終結(jié)符表達(dá)式角色捏悬,PrimitiveCommandNode充當(dāng)終結(jié)符表達(dá)式角色。
4). 完整代碼
- context環(huán)境類
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
/**
* 環(huán)境類:用于存儲和操作需要解釋的語句润梯,
* 在本實例中每一個需要解釋的單詞可以稱為一個
*/
class Context {
// /StringTokenizer類过牙,用于將字符串分解為更小
private StringTokenizer tokenizer;
// 當(dāng)前字符串標(biāo)記
private String currentToken;
public Context(String text) {
// //通過傳入的指令字符串創(chuàng)建StringTokenizer
tokenizer = new StringTokenizer(text);
nextToken();
}
// 返回下一個標(biāo)記
public String nextToken() {
if (tokenizer.hasMoreTokens()) {
currentToken = tokenizer.nextToken();
} else {
currentToken = null;
}
return currentToken;
}
// 返回當(dāng)前的標(biāo)記
public String currentToken() {
return this.currentToken;
}
// 跳過一個標(biāo)記
public void skipToken(String token) {
if (!token.equals(currentToken)) {
System.out.println("錯誤提示:" + currentToken + "解釋錯誤!");
}
nextToken();
}
// 如果當(dāng)前標(biāo)記是一個數(shù)字纺铭,則返回對應(yīng)的數(shù)值
public int currentNumber() {
int number = 0;
try {
// 將字符串轉(zhuǎn)換為數(shù)字
number = Integer.parseInt(currentToken);
} catch (NumberFormatException e) {
e.printStackTrace();
}
return number;
}
}
環(huán)境類Context類似一個工具類寇钉,它提供了用于處理指令的方法,如nextToken()舶赔、currentToken()扫倡、skipToken()等,同時它存儲了需要解釋的指令并記錄了每一次解釋的當(dāng)前標(biāo)記(Token)竟纳,而具體的解釋過程交給表達(dá)式解釋器類來處理撵溃。我們還可以將各種解釋器類包含的公共方法移至環(huán)境類中,更好地實現(xiàn)這些方法的重用和擴(kuò)展蚁袭。
- 抽象表達(dá)式
/**
* 抽象節(jié)點表達(dá)式
*/
abstract class Node {
//聲明一個方法用于解釋語句,解析的過程就是轉(zhuǎn)換為對應(yīng)的對象
public abstract void interpret(Context context);
//聲明一個方法用于執(zhí)行標(biāo)記對應(yīng)的命令
public abstract void execute();
}
- 非終結(jié)符表達(dá)式
/**
* 表達(dá)式節(jié)點類:非終結(jié)符表達(dá)式
* 對應(yīng)的文法規(guī)則:
* expression ::= command* //表達(dá)式石咬,一個表達(dá)式包含多條命令
*/
class ExpressionNode extends Node {
private List<Node> list = new ArrayList<>();
@Override
public void interpret(Context context) {
// 循環(huán)處理Context中的標(biāo)記
while (true) {
// 如果已經(jīng)沒有任何標(biāo)記揩悄,則退出解釋
if (context.currentToken() == null) {
break;
} else if (context.currentToken().equals("END")) {
// 如果標(biāo)記為END,則不解釋END并結(jié)束本次解釋過程」碛疲可以繼續(xù)之后的解釋
context.skipToken("END");
break;
} else {
//如果為其他標(biāo)記删性,則解釋標(biāo)記并將其加入命令集合
Node commandNode = new CommandNode();
commandNode.interpret(context);
list.add(commandNode);
}
}
}
//循環(huán)執(zhí)行命令集合中的每一條命令
@Override
public void execute() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
((Node)iterator.next()).execute();
}
}
}
/**
* 語句命令節(jié)點類:非終結(jié)符表達(dá)式
* 對應(yīng)的文法規(guī)則:
* command ::= loop | primitive //語句命令
*/
class CommandNode extends Node {
private Node node;
@Override
public void interpret(Context context) {
// 處理LOOP循環(huán)命令
if (context.currentToken().equals("LOOP")) {
node = new LoopCommandNode();
node.interpret(context);
} else {
// 處理其他基本命令
node = new PrimitiveCommandNode();
node.interpret(context);
}
}
@Override
public void execute() {
node.execute();
}
}
/**
* 循環(huán)命令節(jié)點類:非終結(jié)符表達(dá)式
* 對應(yīng)的文法規(guī)則:
* loop ::= 'loopnumber' expression 'end' //循環(huán)命令,其中number為自然數(shù)
*/
class LoopCommandNode extends Node {
//循環(huán)次數(shù)
private int number;
//循環(huán)語句中的表達(dá)式
private Node commandNode;
//解釋循環(huán)命令
@Override
public void interpret(Context context) {
context.skipToken("LOOP");
number = context.currentNumber();
context.nextToken();
//循環(huán)語句中的表達(dá)式
commandNode = new ExpressionNode();
commandNode.interpret(context);
}
@Override
public void execute() {
for (int i = 0; i < number; i++) {
commandNode.execute();
}
}
}
- 終結(jié)符表達(dá)式
/**
* 基本命令節(jié)點類:終結(jié)符表達(dá)式
* 對應(yīng)的文法規(guī)則:
* primitive ::= 'printstring' | 'space' | 'break' //基本命令焕窝,其中string為字符串
*/
class PrimitiveCommandNode extends Node {
private String name;
private String text;
//解釋基本命令
@Override
public void interpret(Context context) {
name = context.currentToken();
context.skipToken(name);
if (!name.equals("PRINT") && !name.equals("BREAK") && !name.equals ("SPACE")) {
System.err.println("非法命令蹬挺!");
}
if (name.equals("PRINT")) {
text = context.currentToken();
context.nextToken();
}
}
@Override
public void execute() {
switch (this.name) {
case "PRINT" : System.out.print(text); break;
case "SPACE" : System.out.print(" "); break;
case "BREAK" : System.out.println(); break;
}
}
}
- 客戶端測試類
/**
* @author liucheng
* @since 2019/9/4
*/
public class Client {
public static void main(String[] args) {
String text = "LOOP 2 PRINT 楊過 SPACE SPACE PRINT 小龍女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黃蓉";
Context context = new Context(text);
Node node = new ExpressionNode();
node.interpret(context);
node.execute();
}
}
五、總結(jié)
1). 優(yōu)點
- 易于改變和擴(kuò)展文法它掂。由于在解釋器模式中使用類來表示語言的文法規(guī)則巴帮,因此可以通過繼承等機(jī)制來改變或擴(kuò)展文法溯泣。
- 每一條文法規(guī)則都可以表示為一個類,因此可以方便地實現(xiàn)一個簡單的語言榕茧。
- 實現(xiàn)文法較為容易垃沦。在抽象語法樹中每一個表達(dá)式節(jié)點類的實現(xiàn)方式都是相似的,這些類的代碼編寫都不會特別復(fù)雜用押,還可以通過一些工具自動生成節(jié)點類代碼肢簿。
- 增加新的解釋表達(dá)式較為方便。如果用戶需要增加新的解釋表達(dá)式只需要對應(yīng)增加一個新的終結(jié)符表達(dá)式或非終結(jié)符表達(dá)式類蜻拨,原有表達(dá)式類代碼無須修改池充,符合“開閉原則”。
2). 缺點
- 對于復(fù)雜文法難以維護(hù)缎讼。在解釋器模式中收夸,每一條規(guī)則至少需要定義一個類,因此如果一個語言包含太多文法規(guī)則休涤,類的個數(shù)將會急劇增加咱圆,導(dǎo)致系統(tǒng)難以管理和維護(hù),此時可以考慮使用語法分析程序等方式來取代解釋器模式功氨。
- 執(zhí)行效率較低序苏。由于在解釋器模式中使用了大量的循環(huán)和遞歸調(diào)用,因此在解釋較為復(fù)雜的句子時其速度很慢捷凄,而且代碼的調(diào)試過程也比較麻煩忱详。
3). 適用場景
- 可以將一個需要解釋執(zhí)行的語言中的句子表示為一個抽象語法樹。
- 一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進(jìn)行表達(dá)跺涤。
- 一個語言的文法較為簡單匈睁。
- 執(zhí)行效率不是關(guān)鍵問題。【注:高效的解釋器通常不是通過直接解釋抽象語法樹來實現(xiàn)的桶错,而是需要將它們轉(zhuǎn)換成其他形式航唆,使用解釋器模式的執(zhí)行效率并不高≡旱螅】