解釋器模式-Interpreter

解釋器模式提供了一種評估計算語言語法或表達式的方法。 這種類型的模式屬於行爲模式。 這種設計模式涉及實現一個表達式接口,它告訴解釋一個指定的上下文。 此模式用於SQL解析,符號處理引擎等。node

1、類圖

 解釋器模式包含如下主要角色。設計模式

  • 抽象表達式(Abstract Expression)角色:定義解釋器的接口,約定解釋器的解釋操做,主要包含解釋方法 interpret()。
  • 終結符表達式(Terminal    Expression)角色:是抽象表達式的子類,用來實現文法中與終結符相關的操做,文法中的每個終結符都有一個具體終結表達式與之相對應。
  • 非終結符表達式(Nonterminal Expression)角色:也是抽象表達式的子類,用來實現文法中與非終結符相關的操做,文法中的每條規則都對應於一個非終結符表達式。
  • 環境(Context)角色:一般包含各個解釋器須要的數據或是公共的功能,通常用來傳遞被全部解釋器共享的數據,後面的解釋器能夠從這裏獲取這些值。
  • 客戶端(Client):主要任務是將須要分析的句子或表達式轉換成使用解釋器對象描述的抽象語法樹,而後調用解釋器的解釋方法,固然也能夠經過環境角色間接訪問解釋器的解釋方法。

2、示例

//環境類:用於存儲和操做須要解釋的語句,在本實例中每個須要解釋的單詞能夠稱爲一個動做標記(Action Token)或命令
class Context {
    private StringTokenizer tokenizer; //StringTokenizer類,用於將字符串分解爲更小的字符串標記(Token),默認狀況下以空格做爲分隔符
    private String currentToken; //當前字符串標記

    public Context(String text) {
        tokenizer = new StringTokenizer(text); //經過傳入的指令字符串建立StringTokenizer對象
        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)) {
            System.err.println("錯誤提示:" + currentToken + "解釋錯誤!");
        }
        nextToken();
    }

    //若是當前的標記是一個數字,則返回對應的數值
    public int currentNumber() {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken); //將字符串轉換爲整數
        } catch (NumberFormatException e) {
            System.err.println("錯誤提示:" + e);
        }
        return number;
    }
}

//抽象節點類:抽象表達式
abstract class Node {
    public abstract void interpret(Context text); //聲明一個方法用於解釋語句

    public abstract void execute(); //聲明一個方法用於執行標記對應的命令
}

//表達式節點類:非終結符表達式
class ExpressionNode extends Node {
    private ArrayList<Node> list = new ArrayList<Node>(); //定義一個集合用於存儲多條命令

    public void interpret(Context context) {
        //循環處理Context中的標記
        while (true) {
            //若是已經沒有任何標記,則退出解釋
            if (context.currentToken() == null) {
                break;
            }
            //若是標記爲END,則不解釋END並結束本次解釋過程,能夠繼續以後的解釋
            else if (context.currentToken().equals("END")) {
                context.skipToken("END");
                break;
            }
            //若是爲其餘標記,則解釋標記並將其加入命令集合
            else {
                Node commandNode = new CommandNode();
                commandNode.interpret(context);
                list.add(commandNode);
            }
        }
    }

    //循環執行命令集合中的每一條命令
    public void execute() {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            ((Node) iterator.next()).execute();
        }
    }
}

//語句命令節點類:非終結符表達式
class CommandNode extends Node {
    private Node node;

    public void interpret(Context context) {
        //處理LOOP循環命令
        if (context.currentToken().equals("LOOP")) {
            node = new LoopCommandNode();
            node.interpret(context);
        }
        //處理其餘基本命令
        else {
            node = new PrimitiveCommandNode();
            node.interpret(context);
        }
    }

    public void execute() {
        node.execute();
    }
}

//循環命令節點類:非終結符表達式
class LoopCommandNode extends Node {
    private int number; //循環次數
    private Node commandNode; //循環語句中的表達式

    //解釋循環命令
    public void interpret(Context context) {
        context.skipToken("LOOP");
        number = context.currentNumber();
        context.nextToken();
        commandNode = new ExpressionNode(); //循環語句中的表達式
        commandNode.interpret(context);
    }

    public void execute() {
        for (int i = 0; i < number; i++)
            commandNode.execute();
    }
}

//基本命令節點類:終結符表達式
class PrimitiveCommandNode extends Node {
    private String name;
    private String text;

    //解釋基本命令
    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();
        }
    }

    public void execute() {
        if (name.equals("PRINT"))
            System.out.print(text);
        else if (name.equals("SPACE"))
            System.out.print(" ");
        else if (name.equals("BREAK"))
            System.out.println();
    }
}

客戶端測試代碼工具

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();
    }
}

在本實例代碼中,環境類 Context 相似一個工具類,它提供了用於處理指令的方法,如 nextToken()、currentToken()、skipToken() 等,同時它存儲了須要解釋的指令並記錄了每一次解釋的當前標記(Token),而具體的解釋過程交給表達式解釋器類來處理。咱們還能夠將各類解釋器類包含的公共方法移至環境類中,更好地實現這些方法的重用和擴展。oop

3、優缺點

優勢:測試

解釋器是一個簡單語法分析工具,它最顯著的優勢就是擴展性,修改語法規則只要修改相應的非終結符表達式就能夠了,若擴展語法,則只要增長非終結符類就能夠了。spa

缺點:設計

每一個語法都要產生一個非終結符表達式,語法規則比較複雜時,就可能產生大量的類文件,爲維護帶來了很是多的麻煩。調試

解釋器模式採用遞歸調用方法,若是要排查一個語法錯誤,要一個一個斷點的調試下去,會很麻煩。code

解釋器模式使用了大量的循環和遞歸,特別是用於解析複雜、冗長的語法時,效率會很低。orm

 

……更多設計模式的內容,能夠訪問Refactoring.Guru

相關文章
相關標籤/搜索