设计模式——解释器模式

274 阅读5分钟

一、概述

概念:给定一种语言,定义它的文法的一种表示,并定义一个解释器,使用该解释器表示来解释语言中句子。解释器模式它也是一种行为型的设计模式。

解释器模式主要有4个核心的角色:

  • 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作。具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成。
  • 终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一半是文法中的运算单元,比如有一个简单的公式R = R1 + R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
  • 非终结符表达式:文法中的每条规则对应于一个非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R = R1 + R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
  • 环境角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R = R1 + R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。

概念看起来非常难懂,还是用具体的例子来做说明。举一个加减乘除的例子吧,实现思路来自于《java与模式》中的例子。每个角色的功能按照上面提到的规范来实现。

二、使用

首先创建的是抽象表达式,里面只有一个interpret()方法,并会引用上下文的对象。

/**
 * 抽象表达式角色(这里也可以用抽象类)
 */
public interface Expression {
    int interpret(MyContext con);
}

然后是当前的上下文角色对象。里面用到了Map作为存储相关的对象

/**
 * 上下文(环境)角色
 */
public class MyContext {

    //使用HashMap来存储变量对应的数值
    private Map<Variable,Integer> valueMap = new HashMap<>();

    public void addValue(Variable x, int y) {
        Integer yi = y;
        valueMap.put(x, yi);
    }

    public int lookupValue(Variable x) {
        return valueMap.get(x);
    }
}

再者就是终结符表达式角色。

/**
 * 终结符表达式角色
 */
public class Constant implements Expression {

    private int i;

    public Constant(int i) {
        this.i = i;
    }

    public int interpret(MyContext con) {
        return i;
    }
}
/**
 * 终结符表达式角色
 */
public class Variable implements Expression {

    public int interpret(MyContext con) {
        //this为调用interpret方法的Variable对象
        return con.lookupValue(this);
    }
}

最后,就是非终结符表达式角色,这里我们创建了加、减、乘、除4个类。

/**
 * 非终结符表达式角色——加法
 */
public class Addition implements Expression {

    private Expression left, right;

    public Addition(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(MyContext con) {
        return left.interpret(con) + right.interpret(con);
    }
}
/**
 * 非终结符表达式角色——减法
 */
public class Subtraction implements Expression {

    private Expression left, right;

    public Subtraction(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(MyContext con) {
        return left.interpret(con) - right.interpret(con);
    }
}
/**
 * 非终结符表达式角色——乘法
 */
public class Multiplication implements Expression {

    private Expression left, right;

    public Multiplication(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(MyContext con) {
        return left.interpret(con) * right.interpret(con);
    }
}
/**
 * 非终结符表达式角色——除法
 */
public class Division implements Expression {

    private Expression left, right;
    private static final String TAG = "XXX";

    public Division(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(MyContext con) {
        try {
            return left.interpret(con) / right.interpret(con);
        } catch (ArithmeticException ae) {
            Log.e(TAG, "被除数为0!");
            return -1;
        }
    }
}

来进行代码测试,打印输出如下图所示。

MyContext context = new MyContext();
//设置变量、常量
Variable variableA = new Variable();
Variable variableB = new Variable();
Constant constant = new Constant(16);

//为变量赋值
context.addValue(variableA,8);
context.addValue(variableB,4);

//运算,对句子的结构由我们自己来分析,构造
Expression expression = new Addition(new Subtraction(variableA,variableB),new Multiplication(new Division(variableA,variableB),constant));
Log.e(TAG, "运算结果:" + expression.interpret(context));

三、总结

解释器是一个简单的语法分析工具,它最显著的优点就是扩展性,修改语法规则只需要修改相应的非终结符就可以了,若扩展语法,只需要增加非终结符类就可以了。

但是,解释器模式会引起类的膨胀,每个语法都需要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来非常多的麻烦。同时,由于采用递归调用方法,每个非终结符表达式只关心与自己相关的表达式,每个表达式需要知道最终的结果,必须通过递归方式,无论是面向对象的语言还是面向过程的语言,递归都是一个不推荐的方式。由于使用了大量的循环和递归,效率是一个不容忽视的问题。特别是用于解释一个解析复杂、冗长的语法时,效率是难以忍受的。

由于解释器模式是设计模式中很少使用到的,我们只需要做一些了解就行。

github地址:github.com/leewell5717…

四、参考

转载自:解析Java的设计模式编程之解释器模式的运用