《一天一模式》— 解釋器模式

1、解釋器模式的概念

給定一個語言,定義一個文法的一種表示, 並定義一個解釋器, 這個解釋器使用該表示來解釋語言中的句子。 java

2、何時使用解釋器模式

解釋器模式是23種設計模式中惟一一種處理語法解析的設計模式。node

當須要求是,須要解釋某種語法來執行業務,而且你可將該語言中的句子表示爲一個抽象語法樹,可使用解釋器模式。設計模式

向《圖解設計模式》裏在解釋器模式這章節提到的例子,使用BNF式描述語法,例子是經過一種語法來移動小車:bash

<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left

這種語法就能夠作成抽象語法樹,好比要控制小車走路,能夠輸入語法:program repeat 3 go right end end。框架

意思爲重複執行3次go和right。工具

下面看看如何使用Java程序實現解釋器模式的。性能

3、怎麼使用解釋器模式

3.1 實現方式

// 語法樹節點的抽象類
public abstract class Node {

    abstract void parse(Context context);

}

// <program> ::= program <command list>
public class ProgramNode extends Node {

    private Node commandListNode;

    void parse(Context context) {
        // 解析程序命令,必須是program才能夠向下執行
        // 等於從命令中跳過program
        context.skipToken("program");
        // 將後面的命令交給<command list>去解析
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    public String toString() {
        return "[program " + commandListNode + "]";
    }

}

// <command list> ::= <command>* end
public class CommandListNode extends Node {

    ArrayList list = new ArrayList();

    void parse(Context context) {
        // 命令是由<command>* end組成的,因此要循環把*都解析,直到end
        while (true) {
            if (context.currentToken() == null) { // 缺乏end報錯
                throw new IllegalArgumentException("missing end");
            } else if (context.currentToken().equals("end")) { // 解析end,跳出循環
                context.skipToken("end");
                break;
            } else { // 將命令交給<command>解析
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                // 便於打印結果定義了一個集合存放命令
                list.add(commandNode);
            }
        }
    }

    public String toString() {
        return list.toString();
    }

}

// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {

    private Node node;

    void parse(Context context) {
        // 判斷是<repeat command> 仍是 <primitive command>,而後找到想對應的解析對象
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

    public String toString() {
        return node.toString();
    }

}

// <repeat command> ::= repeat <number> <command list>
public class RepeatCommandNode extends Node {

    int number;
    private Node commandListNode;

    void parse(Context context) {
        // 解析repeat,在得到number用於打印,而後把number也解析(用的nextToken)
        context.skipToken("repeat");
        number = context.currentNumber();
        context.nextToken();
        // 接着將命令交給<command list>
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    public String toString() {
        return "[repeat +" + number + " " + commandListNode + "]";
    }

}

// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {

    String name;

    void parse(Context context) {
        // 解析這個命令
        name = context.currentToken();
        context.skipToken(name);
        // 若是不識別就報錯
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new IllegalArgumentException("token is undefined.");
        }
    }

    public String toString() {
        return name;
    }

}

public class Context {

    StringTokenizer tokenizer;
    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) {
        if (!token.equals(currentToken)) {
            throw new IllegalArgumentException("token is expected, but currentToken is found.");
        }
        nextToken();
    }

    public int currentNumber() {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("number is bad param.");
        }
        return number;
    }

}

使用方法以下:spa

public class Client {

    public static void main(String[] args) {
        String command5 = "program repeat 4 repeat 3 go right go right end right end end";
        Node node = new ProgramNode();
        node.parse(new Context(command5));
        System.out.println(node);
    }

}

// 輸出
// [program [[repeat +4 [[repeat +3 [go, right, go, right]], right]]]]

3.2 解釋器模式的好處

  • 能夠很容易地改變和擴展方法, 由於該模式使用類來表示方法規則, 你可使用繼承來改變或擴展該方法;
  • 也比較容易實現方法, 由於定義抽象語法樹總各個節點的類的實現大致相似, 這些類都易於直接編寫;
  • 解釋器模式就是將一句話,轉變爲實際的命令程序執行而已。 而不用解釋器模式自己也能夠分析, 但經過繼承抽象表達式的方式, 因爲依賴轉置原則, 使得文法的擴展和維護都帶來的方便;

3.3 解釋器模式須要注意的地方

解釋器模式爲方法中的每一條規則至少定義了一個類, 所以包含許多規則的方法可能難以管理和維護。 所以當方法很是複雜時, 使用其餘的技術如 語法分析程序 或 編譯器生成器來處理。設計

4、總結

解釋器模式的關鍵在於如何將需求抽象成語法樹。code

其業務實現很簡單,用類來解析每一個命令。

解釋器模式在實際的系統開發中使用的很是少,由於它會引發效率、性能以及維護等問題,通常在大中型的框架型項目可以找到它的身影,好比一些數據分析工具、報表設計工具、科學計算工具等等

以上就是我對解釋器模式的一些理解,有不足之處請你們指出,謝謝。

相關文章
相關標籤/搜索