天天看點

設計模式-解析器模式

定義

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

适用性

最典型的應用就是正規表達式

重複發生的問題可以使用解釋器模式:比如根據使用者輸入的公式進行加減乘除四則運算,但是他們輸入的公式每次都不同,有時是a+b-c*d,有時是a*b+c-d,等等等等個,公式千變萬化,但是都是由加減乘除四個非終結符來連接配接的,這時我們就可以使用解釋器模式。

當有一個語言需要解釋執行,并且你可以把該語言中的句子表示為一個抽象的文法樹時,可使用解釋器模式.而當存在以下情況時,該模式的效果最好:

1、該文法簡單,對于複雜的文法,文法的類層次變得龐大而無法管理.此時,文法分析程式生成器這樣得工具時更好得選擇。它們無需建構抽象文法樹即可解釋表達式,這樣可以節省空間而且還可以節省時間;

2、效率不是一個關鍵的問題,最高效的解釋器通常不是通過直接解釋文法分析樹實作的,而是首先把他們轉換成另外一種形式.例如:正規表達式通常被轉換成狀态機。但即使在這種情況下,轉換器仍可用解釋器模式實作,該模式仍是有用的.

模式中的角色

1.AbstractExpression(抽象表達式) 聲明一個抽象的解釋操作,這個接口為抽象文法樹中所有的節點所共享。

2.TerminalExpression(終結符表達式) 實作與文法中的終結符相關聯的解釋操作。 一個句子中的每個終結符需要該類的一個執行個體。

3.NonterminalExpression(非終結符表達式) 為文法中的非終結符實作解釋(Interpret)操作。

4.Context(上下文) 包含解釋器之外的一些全局資訊。

5.Client(客戶) 建構(或被給定)表示該文法定義的語言中一個特定的句子的抽象文法樹。 該抽象文法樹由NonterminalExpression和TerminalExpression的執行個體裝配而成。 調用解釋操作。

角色關系UML

設計模式-解析器模式

以加減運算為例子

UML圖如下:

設計模式-解析器模式

java代碼:

抽象表達式:

package demo;

import java.util.Map;

/**
 * 
 * @ClassName: Expression
 * @Description:抽象表達式
 * @author cheng
 * @date
public interface Expression
    /**
     * 解析公式和數值,其中var中的key是公式的參數,value值是具體的數字
     * 負責對傳遞進來的參數和值進行解析和比對,其中key是表達式a+b+c中的a、b、c,value是運算時取得的值
     * 
     * 如果是終結符表達式,那麼此方法将擷取參數的值 
     * 如果是非終結符表達式,那麼此方法将進行運算,比如加減
     **/
    int      

終結符表達式

package demo;

import java.util.Map;

/**
 * 
 * @ClassName: VarExpression
 * @Description:變量解析器/終結符表達式
 * @author cheng
 * @date
public class VarExpression implements Expression

    // 需要擷取值的變量名
    private String key;

    /**
     * 構造函數
     * 
     * @param
    public VarExpression(String key) {
        this.key = key;
    }

    /**
     * 從map中取值
     */
    @Override
    public int interpreter(Map<String, Integer> var) {
        return      

非終結符表達式

package demo;

import java.util.Map;

/**
 * 
 * @ClassName: AddExpression
 * @Description: 加法解析器/interpreter方法處理加法運算 
 * @author cheng
 * @date
public class AddExpression implements Expression

    // 每個運算符都有左右兩個參數進行運算
    private Expression left;
    private Expression right;

    /**
     * 構造函數
     * 
     * @param left
     * @param
    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    /**
     * 進行加法運算
     */
    @Override
    public int interpreter(Map<String, Integer> var) {
        return      
package demo;

import java.util.Map;

/**
 * 
 * @ClassName: SubExpression
 * @Description:減法解析器/interpreter方法處理減法運算
 * @author cheng
 * @date
public class SubExpression implements Expression

    // 每個運算符都有左右兩個參數進行運算
    private Expression left;
    private Expression right;

    /**
     * 構造函數
     * 
     * @param left
     * @param
    public SubExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    /**
     * 進行減法運算
     */
    @Override
    public int interpreter(Map<String, Integer> var) {
        return      

上下文

package demo;

import java.util.Map;
import java.util.Stack;

/**
 * 
 * @ClassName: Context
 * @Description:對輸入的表達式進行解析,并計算
 * @author cheng
 * @date
public class Context
    /**
     * 定義表達式,最後拿到是一個運算解析器,比如X+Y格式的,其中X可能又是由A+B的運算解析器組成
     * 隻有最底層的解析器才是變量解析器,也就是終結符表達式 此參數最終得到的肯定是非終結表達式
     * */
    private Expression expression;

    /**
     * 分析使用者輸入的表達式
     */
    public void analyse(String expStr) {
        // 定義一個棧,安排運算的先後順序
        Stack<Expression> stack = new Stack<Expression>();
        char[] charArray = expStr.toCharArray();
        Expression left = null;
        Expression right = null;
        for (int i = 0; i < charArray.length; i++) {
            switch (charArray[i]) {
            case '+':
                // 将加法運算加入到棧中
                left = stack.pop();
                right = new VarExpression(String.valueOf(charArray[++i]));
                stack.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:
                // 如果不是運算符,那麼就是終結表達式
                stack.push(new VarExpression(String.valueOf(charArray[i])));
            }
        }
        // 把最終棧的頂層抛出,它即是最後封裝的非終結表達式
        this.expression = stack.pop();
    }

    /**
     * 
     * @Title: run
     * @Description: 将鍵值對輸入給表達式運算
     * @param var
     * @return
    public int run(Map<String, Integer> var) {
        return      

測試

package demo;

import java.util.HashMap;
import java.util.Map;

/**
 * 
 * @ClassName: ClientTest
 * @Description:測試
 * @author cheng
 * @date
public class ClientTest

    public static void main(String[] args) {
        String expStr = "a+b-c+d+e";
        Map<String, Integer> var = new HashMap<String, Integer>();
        var.put("a", 1);
        var.put("b", 2);
        var.put("c", 2);
        var.put("d", 4);
        var.put("e", 3);

        Context context = new Context();
        // 先解析運算表達式
        context.analyse(expStr);
        // 進行運算      

運作結果

8      

優點

解釋器是一個簡單的文法分析工具,它最顯著的優點就是擴充性,修改文法規則隻需要修改相應的非終結符就可以了,若擴充文法,隻需要增加非終結符類就可以了。

缺點

解釋器模式會引起類的膨脹:每個文法都需要産生一個非終結符表達式,文法規則比較複雜時,就可能産生大量的類檔案,為維護帶來非常多的麻煩。

解釋器模式采用遞歸調用方法:每個非終結符表達式隻關心與自己相關的表達式,每個表達式需要知道最終的結果,必須通過一層一層的剝繭,無論是面向對象的語言還是面向過程的語言,遞歸都是一個不推薦的方式(隻在必要條件下使用),它将導緻調試非常複雜。想想看,如果要排查一個錯誤,我們是不是要一個個斷點調試下去,直至最小的文法單元。

解釋器模式使用了大量的循環和遞歸:效率是一個不容忽視的問題。特别是用于解釋一個解析複雜、冗長的文法時,效率是難以忍受的。

注意事項