title: 解释器模式
author: rack-leen
avatar: /images/favicon.png
authorDesc: 脱发典范
comments: true
copyright: true
date: 2019-5-19 22:13:50
tags:


解释器模式

定义

Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

解释器模式示意图

解释器模式

流程

  1. 定义一个总的抽象表达式类,是所有表达式的父类。表达式又分为变量表达式和符号表达式。
  2. 变量表达式需要输入公式变量与数字的映射关系,通过映射关系用公式变量取出对应的具体数字,将具体数字套入公式。
  3. 抽象符号表达式类是所有符号运算类的父类,符号运算包括加减乘除的运算规则。
  4. 通过客户端提供公式以及公式变量与数字的映射关系,将这两个输入计算器对象中,按多态性来根据公式符号选择需要的符号解释器,将公式变量与数字的映射关系输入变量解释器,将对应的数字套入结果表达式中,获取最终结果。

代码实现

表达式

  1. 抽象表达式Expression
package org.example.interpreter;

import java.util.HashMap;

/**
 * 抽象表达式,用来组合不同种类的解释器,形成一个表达式。
 * 每种解释器就是一个算术因子,是加减乘除需要的算术因子。
 */
public abstract class Expression {
    /**
     * @param var String是表达式中的值,Integer是表达式中值的索引值
     * @return 返回表达式计算出的值
     */
    public abstract int interpreter(HashMap<String , Integer> var);
}
  1. 变量表达式VarExpression
package org.example.interpreter;

import java.util.HashMap;

public class VarExpression extends Expression {
    private String key ;

    public VarExpression(String key){
        this.key = key ;
    }
    /**
     * 用来从输入的变量中获取变量在表达式中的位置
     * @param var String是表达式中的值,Integer是表达式中值的索引值
     * @return
     */
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(key);
    }
}
  1. 抽象符号表达式
package org.example.interpreter;

import java.util.HashMap;

/**
 * 符号解释器,用来解释表达式中的运算符号
 */
public abstract class SymbolExpression extends Expression {
    protected Expression left ;
    protected Expression right ;

    public SymbolExpression(Expression left , Expression right){
        this.left = left ;
        this.right = right ;
    }
}
  1. 加法表达式
package org.example.interpreter;

import java.util.HashMap;

public class AddExpression extends SymbolExpression {
    /**
     * 从抽象符号表达式中继承计算的左值和右值
     * @param left
     * @param right
     */
    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) + super.right.interpreter(var);
    }
}
  1. 减法表达式
package org.example.interpreter;

import java.util.HashMap;

/**
 * 减法解释器
 */
public class SubExpression extends SymbolExpression{

    /**
     * 从抽象符号表达式中继承计算的左值和右值
     * @param left
     * @param right
     */
    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }
}
  1. 乘法表达式
package org.example.interpreter;

import java.util.HashMap;

/**
 * 乘法解释器
 */
public class MulExpression extends SymbolExpression {

    /**
     * 从抽象符号表达式中继承计算的左值和右值
     * @param left
     * @param right
     */
    public MulExpression(Expression left, Expression right) {
        super(left, right);
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) * super.right.interpreter(var) ;
    }
}
  1. 除法表达式
package org.example.interpreter;

import java.util.HashMap;

public class DivExpression extends SymbolExpression {

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

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) / super.right.interpreter(var) ;
    }
}

计算器

package org.example.interpreter;

import java.util.HashMap;
import java.util.Stack;

public class Calculator {
    private Expression expression ;
    public Calculator(String expStr){
        Stack<Expression> stack = new Stack<>();
        Expression left = null ;
        Expression right = null ;

        System.out.println(expStr);
        char[] array = expStr.toCharArray();
        for (int i=0 ; i<array.length ; i++){
            switch (array[i]){
                case '+' :
                    left = stack.pop(); // 如果现在是+这个符号,表示前面有一个数已经入栈,这个已经入栈的数就是左值
                    right = new VarExpression(String.valueOf(array[++i])); //获取符号后面的数值作为右值
                    stack.push(new AddExpression(left , right)); // 将计算结果压栈
                    break;
                case '-' :
                    left = stack.pop();
                    right = new VarExpression(String.valueOf(array[++i]));
                    stack.push(new SubExpression(left , right)) ;
                    break;
                case '*' :
                    left = stack.pop();
                    right = new VarExpression(String.valueOf(array[++i]));
                    stack.push(new MulExpression(left , right)) ;
                    break;
                case '/' :
                    left = stack.pop() ;
                    right = new VarExpression(String.valueOf(array[++i]));
                    stack.push(new DivExpression(left , right)) ;
                    break;
                default:
                    stack.push(new VarExpression(String.valueOf(array[i]))) ;
                    break;
            }
        }
        this.expression = stack.pop() ; // 将最终的运算结果赋值
    }

    public int run(HashMap<String , Integer> var){
        return this.expression.interpreter(var) ;
    }
}

场景实现

public class App {
    /**
     * 解释器模式
     * @param args
     */
    public static void main(String[] args) {
        /* 公式变量与数字的映射关系 */
        HashMap<String, Integer> map = new HashMap<>();
        map.put("a" , 100);
        map.put("b" , 20);
        map.put("c" , 30);
        /* 需要的公式模板 */
        String expStr = "a+b-c";
        Calculator calculator = new Calculator(expStr);
        System.out.println(calculator.run(map));
    }
}

应用场景

  1. 重复发生的问题,规则不同,但是有相同的元素,久可以使用解释器模式。
  2. 一个简单语法需要解释。如果是复杂语法,会导致类膨胀。

解释器模式的优缺点

优点

  1. 解释器是一个简单的语法分析工具,优点是扩展性,修改语法规则只需要修改对应的符号表达式就行了。

缺点

  1. 会引起类膨胀,每种语法都会产生一个规则类,语法规则很复杂时会产生很多规则类。
  2. 解释器模式采用递归调用,使用栈。调试非常麻烦。
  3. 解释器模式使用递归和栈,效率很低。

注意事项

  1. 不要在重要模块使用解释器模式,这样会导致很难维护。
  2. 解释器模式在实际中很少使用,主要在数据分析工具、报表设计工具、科学计算工具使用。
  3. 可以使用Expression4J,MESP,Jep开源解析工具来代替自己写解释器模式。