美文网首页
设计模式之旅24--解释器模式

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

作者: 小楠总 | 来源:发表于2018-08-14 11:14 被阅读119次

    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. 缺点

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

    相关文章

      网友评论

          本文标题:设计模式之旅24--解释器模式

          本文链接:https://www.haomeiwen.com/subject/vprumftx.html