设计模式之旅24--解释器模式

1. 定义

定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少。

解释器模式

2. 使用场景

  • 在项目中可以使用Shell、JRuby、Groovy等脚本语言代替解释器模式,弥补Java编译型语言的不足。一定要使用解释器的时候,可以使用Expression4J、MESP、Jep等开源分析工具包代替自己实现。
  • 重复发生的问题可以使用解释器模式。例如对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,但是非终结符表达式就需要制定。
  • 一个简单语法需要解释的场景。解释器模式一般用来解析比较标准的字符集,例如SQL语法分析,但是该部分逐渐被专用工具所取代。
  • 复杂语法的解释不推荐使用解释器模式,会带来类膨胀、调试困难、运行效率低等问题。
  • 在某些特用的商业环境下也会采用解释器模式。例如金融模型、实验数据的计算等。
  • Android中,对清单文件的解释就使用了解释器模式。

3. 实现

由于解释器模式在实际项目中使用使用得比较少,而且又不太好理解,所以通过具体的一个加减法计算器例子来实现一次。

抽象解释器(AbstractExpression):

/**
 * 抽象表达式(解释器)(AbstractExpression)
 * 具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression完成
 */
public abstract class Expression {

    //每个表达式必须有一个解析任务
    public abstract int interpreter(HashMap<String, Integer> var);
}

终结符表达式(TerminalExpression):

/**
 * 终结符表达式(TerminalExpression)
 * 终结符表达式又叫做运算元素、运算变量,也叫做终结符号,运算元素就是指a、b、c等符号,需要具体赋值的对象
 * 终结符:这些元素除了需要赋值外,不需要做任何处理。所有运算元素都对应一个具体的业务参数,这是语法中最小的单元逻辑,不可再拆分
 * 通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
 */
public class VarExpression extends Expression {

    private String mKey;

    public VarExpression(String key) {
        this.mKey = key;
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(this.mKey);
    }
}

非终结符表达式(NonterminalExpression):

/**
 * 非终结符表达式(NonterminalExpression)
 * 非终结符表达式又叫运算符号、做非终结符号
 * 运算符号就是加减符号,需要我们编写算法进行处理,每个运算符号都要对应处理单元,否则公式无法运行
 * 非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式
 */
public abstract class SymbolExpression extends Expression {

    //每个非终结符表达式都会对其他表达式产生依赖
    protected Expression mLeft;
    protected Expression mRight;

    public SymbolExpression(Expression left, Expression right) {
        this.mLeft = left;
        this.mRight = right;
    }
}

/**
 * 加法
 */
public class AddExpression extends SymbolExpression{

    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return this.mLeft.interpreter(var) + this.mRight.interpreter(var);
    }
}

/**
 * 减法
 */
public class SubExpression extends SymbolExpression {

    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return this.mLeft.interpreter(var) - this.mRight.interpreter(var);
    }
}

封装类:

/**
 * 封装类
 * 将需要的操作封装到一个类中,符合单一职责原则
 */
public class Calculator {

    private Expression mExpression;

    //通过递归的方式来解释表达式
    public Calculator(String expStr) {
        Stack<Expression> stack = new Stack<>();
        char[] charArray = expStr.toCharArray();

        Expression left;
        Expression right;

        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])));
                    break;
            }
        }
        //解析结果赋值给成员变量,通过calculate方法进行运算、得到计算结果
        this.mExpression = stack.pop();
    }

    //计算结果
    public int calculate(HashMap<String, Integer> var) {
        return this.mExpression.interpreter(var);
    }

    //获取表达式
    public static String getExpStr() throws Exception {
        System.out.println("请输入表达式:");
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        return reader.readLine();
    }

    //为表达式中的运算符号赋值
    public static HashMap<String, Integer> getValues(String expStr) throws Exception {
        HashMap<String, Integer> values = new HashMap<>();

        for (char ch : expStr.toCharArray()) {
            if (ch != '+' && ch != '-') {
                String key = String.valueOf(ch);

                //这里需要防止同样的元素被重复赋值导致值被覆盖
                if (!values.containsKey(key)) {
                    System.out.println("请输入" + key + "的值:");
                    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                    Integer value = Integer.valueOf(reader.readLine());
                    values.put(key, value);
                }
            }
        }

        return values;
    }

}

场景类:

public class Client {

    public static void main(String[] args) throws Exception {
        //获取表达式
        String expStr = Calculator.getExpStr();
        //为表达式中的元素赋值,这里的HashMap充当了Context上下文角色
        HashMap<String, Integer> values = Calculator.getValues(expStr);
        //构造Calculator,解释表达式
        Calculator calculator = new Calculator(expStr);
        //运算
        int result = calculator.calculate(values);

        System.out.println("计算结果为:" + result);
    }
}

运行结果:

请输入表达式:
a+b-c
请输入a的值:
1
请输入b的值:
2
请输入c的值:
3
计算结果为:0

4. 优点

  • 公式可以运行时编辑。
  • 高扩展性。修改语法规则只要修改相应的非终结符表达式就可以了;若扩展语法,则只要增加非终结符类就可以了。

5. 缺点

  • 语法规则比较复杂时,解释器模式会引起类膨胀,维护困难。
  • 解释器模式采用了循环、递归调用等方法,会带来效率、调试困难等问题。

推荐阅读更多精彩内容