17.解釋器模式(行為型)

解釋器模式(行為型)

解釋器模式很難學(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ī)則的解釋過程跟啤。

  1. 解釋器模式:定義一個語言的文法,并且建立一個解釋器來解釋該語言中的句子唉锌,這里的“語言”是指使用規(guī)定格式和語法的代碼隅肥。解釋器模式是一種類行為型模式。

  2. 結(jié)構(gòu)圖

由于表達(dá)式可分為終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式袄简,因此解釋器模式的結(jié)構(gòu)與組合模式的結(jié)構(gòu)有些類似腥放,但在解釋器模式中包含更多的組成元素


解釋器模式.png

2). 相關(guān)角色

  1. AbstractExpression(抽象表達(dá)式):在抽象表達(dá)式中聲明了抽象的解釋操作,它是所有終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式的公共父類绿语。
  2. TerminalExpression(終結(jié)符表達(dá)式):終結(jié)符表達(dá)式是抽象表達(dá)式的子類秃症,它實現(xiàn)了與文法中的終結(jié)符相關(guān)聯(lián)的解釋操作,在句子中的每一個終結(jié)符都是該類的一個實例吕粹。通常在一個解釋器模式中只有少數(shù)幾個終結(jié)符表達(dá)式類种柑,它們的實例可以通過非終結(jié)符表達(dá)式組成較為復(fù)雜的句子。
  3. NonterminalExpression(非終結(jié)符表達(dá)式):非終結(jié)符表達(dá)式也是抽象表達(dá)式的子類匹耕,它實現(xiàn)了文法中非終結(jié)符的解釋操作聚请,由于在非終結(jié)符表達(dá)式中可以包含終結(jié)符表達(dá)式,也可以繼續(xù)包含非終結(jié)符表達(dá)式泌神,因此其解釋操作一般通過遞歸的方式來完成良漱。
  4. Context(環(huán)境類):環(huán)境類又稱為上下文類舞虱,它用于存儲解釋器之外的一些全局信息,通常它臨時存儲了需要解釋的語句母市。

3). 典型代碼

  1. 抽象表達(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á)式類

  1. 終結(jié)符表達(dá)式
/**
 * 終結(jié)符表達(dá)式
 */
class TerminalExpression extends AbstractExpression {

    @Override
    public void interpret(Context context) {

    }
}

終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式類都是抽象表達(dá)式類的子類返帕,對于終結(jié)符表達(dá)式,其代碼很簡單篙挽,主要是對終結(jié)符元素的處理荆萤。

  1. 非終結(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á)式類敞峭。

  1. 上下文環(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ī)則和抽象語法樹

  1. 文法規(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肠缔。

  1. 抽象語法樹

除了使用文法規(guī)則來定義一個語言,在解釋器模式中還可以通過一種稱之為抽象語法樹(Abstract Syntax Tree, AST)的圖形方式來直觀地表示語言的構(gòu)成哼转,每一棵抽象語法樹對應(yīng)一個語言實例桩砰,如加法/減法表達(dá)式語言中的語句“1+ 2 + 3 – 4 + 1”,可以通過如圖18-2所示抽象語法樹來表示:


加法抽象語法樹.png

在該抽象語法樹中释簿,可以通過終結(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)

  1. 抽象表達(dá)式

import java.util.Stack;

/**
 * 抽象表達(dá)式
 */
interface CalculateNode {
    public abstract double interpret();
}

  1. 非終結(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("操作符格式不正確");
        }
    }
}
  1. 終結(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);
    }
}

  1. 指令處理器
/**
 * 指令處理器:工具類
 */
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();
    }
}
  1. 客戶端測試
/**
 * @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ī)則描述

  1. 文法規(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é)符。

  1. 抽象語法樹舉例
    “down run 10 and left move 20”

機(jī)器人抽象語法樹.png

3). 類圖設(shè)計


機(jī)器人類圖.png

4). 完整代碼

  1. 抽象表達(dá)式
package interpreterpattern;

import java.util.Stack;

/**
 * 抽象表達(dá)式
 */
abstract class AbstractNode {
    public abstract String interpret();
}
  1. 非終結(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();
    }
}

  1. 終結(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;
    }
}

  1. 指令處理器
/**
 * 指令處理器:工具類
 */
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();
    }
}
  1. 客戶端測試類
/**
 * @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.png

Context充當(dāng)環(huán)境角色眼虱,Node充當(dāng)抽象表達(dá)式角色,ExpressionNode席纽、CommandNode和LoopCommandNode充當(dāng)非終結(jié)符表達(dá)式角色捏悬,PrimitiveCommandNode充當(dāng)終結(jié)符表達(dá)式角色。

4). 完整代碼

  1. 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ò)展蚁袭。

  1. 抽象表達(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();
}
  1. 非終結(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();
        }
    }
}
  1. 終結(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;
        }
    }
}
  1. 客戶端測試類
/**
 * @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)點

  1. 易于改變和擴(kuò)展文法它掂。由于在解釋器模式中使用類來表示語言的文法規(guī)則巴帮,因此可以通過繼承等機(jī)制來改變或擴(kuò)展文法溯泣。
  2. 每一條文法規(guī)則都可以表示為一個類,因此可以方便地實現(xiàn)一個簡單的語言榕茧。
  3. 實現(xiàn)文法較為容易垃沦。在抽象語法樹中每一個表達(dá)式節(jié)點類的實現(xiàn)方式都是相似的,這些類的代碼編寫都不會特別復(fù)雜用押,還可以通過一些工具自動生成節(jié)點類代碼肢簿。
  4. 增加新的解釋表達(dá)式較為方便。如果用戶需要增加新的解釋表達(dá)式只需要對應(yīng)增加一個新的終結(jié)符表達(dá)式或非終結(jié)符表達(dá)式類蜻拨,原有表達(dá)式類代碼無須修改池充,符合“開閉原則”。

2). 缺點

  1. 對于復(fù)雜文法難以維護(hù)缎讼。在解釋器模式中收夸,每一條規(guī)則至少需要定義一個類,因此如果一個語言包含太多文法規(guī)則休涤,類的個數(shù)將會急劇增加咱圆,導(dǎo)致系統(tǒng)難以管理和維護(hù),此時可以考慮使用語法分析程序等方式來取代解釋器模式功氨。
  2. 執(zhí)行效率較低序苏。由于在解釋器模式中使用了大量的循環(huán)和遞歸調(diào)用,因此在解釋較為復(fù)雜的句子時其速度很慢捷凄,而且代碼的調(diào)試過程也比較麻煩忱详。

3). 適用場景

  1. 可以將一個需要解釋執(zhí)行的語言中的句子表示為一個抽象語法樹。
  2. 一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進(jìn)行表達(dá)跺涤。
  3. 一個語言的文法較為簡單匈睁。
  4. 執(zhí)行效率不是關(guān)鍵問題。【注:高效的解釋器通常不是通過直接解釋抽象語法樹來實現(xiàn)的桶错,而是需要將它們轉(zhuǎn)換成其他形式航唆,使用解釋器模式的執(zhí)行效率并不高≡旱螅】
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末糯钙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子退腥,更是在濱河造成了極大的恐慌任岸,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狡刘,死亡現(xiàn)場離奇詭異享潜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嗅蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門剑按,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疾就,“玉大人,你說我怎么就攤上這事吕座∨耙耄” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵吴趴,是天一觀的道長漆诽。 經(jīng)常有香客問我,道長锣枝,這世上最難降的妖魔是什么厢拭? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮撇叁,結(jié)果婚禮上供鸠,老公的妹妹穿的比我還像新娘。我一直安慰自己陨闹,他們只是感情好楞捂,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趋厉,像睡著了一般寨闹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上君账,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天繁堡,我揣著相機(jī)與錄音,去河邊找鬼乡数。 笑死椭蹄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的净赴。 我是一名探鬼主播绳矩,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玖翅!你這毒婦竟也來了翼馆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烧栋,失蹤者是張志新(化名)和其女友劉穎写妥,沒想到半個月后拳球,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體审姓,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年祝峻,在試婚紗的時候發(fā)現(xiàn)自己被綠了魔吐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扎筒。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酬姆,靈堂內(nèi)的尸體忽然破棺而出嗜桌,到底是詐尸還是另有隱情,我是刑警寧澤辞色,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布骨宠,位于F島的核電站,受9級特大地震影響相满,放射性物質(zhì)發(fā)生泄漏层亿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一立美、第九天 我趴在偏房一處隱蔽的房頂上張望匿又。 院中可真熱鬧,春花似錦建蹄、人聲如沸碌更。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痛单。三九已至,卻和暖如春拢蛋,著一層夾襖步出監(jiān)牢的瞬間桦他,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工谆棱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留快压,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓垃瞧,卻偏偏與公主長得像蔫劣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子个从,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容