关注「Java视界」公众号,获取更多技术干货

解释器模式 Interpreter —— 让你拥有最终解释权!

给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释文法中的句子

一、什么是解释器模式?

 解释器这个名词想必大家都不会陌生,比如编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。诸如此类的例子也有很多,比如编译器、正则表达式等等。

如正则表达式,它就是解释器模型的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

UML结构图如下:

重点就是给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

  • AbstractExpression:抽象解释器
    声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。

  • TerminalExpression:终结解释器
    实现与文法中的终结符相关联的解释操作。一个句子中的每一个终结符需要该类的一个实例。

  • NonterminalExpression:非终结解释器
    对文法中的规则的解释操作。

  • Context:环境角色
    包含解释器之外的一些全局信息。

  • Client:客户端
    构建表示该语法定义的语言中一个特定的句子的抽象语法树,并调用解释操作。

代码框架如下:

public abstract class Expression {
    public abstract String interpret(Context context);
}
public class TerminalExpression extends Expression {

    @Override
    public String interpret(Context context) {
        return "终端解释器";
    }
}
public class NonTerminalExpression extends Expression {

    @Override
    public String interpret(Context context) {
        return "非终端解释器";
    }
}
public class Context {
    private Map<String, Expression> map;

    public String getValue(String key){
        return map.get(key).interpret(this);
    }
}
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        List<Expression> list = new ArrayList<>();

        list.add(new TerminalExpression());
        list.add(new NonTerminalExpression());
        list.add(new TerminalExpression());
        list.add(new TerminalExpression());

        for (Expression abstractExpression : list) {
            Console.log(abstractExpression.interpret(context));
        }
    }
}
终端解释器
非终端解释器
终端解释器
终端解释器

其中list为一个语法容器,容纳一个具体的表达式。通常Client是一个封装类,封装的结果就是传递进来一个规范语法文件,解析器分析后产生结果并返回,避免了调用者与语法分析器的耦合关系。

二、代码实例

 通过解释器模式来实现四则运算,如计算a+b的值。UML图如下:

public abstract class Expression {
    // 解析公式和数值,key是公式中的参数,value是具体的数值
    public abstract int interpreter(HashMap<String, Integer> var);
}

 变量解析器:

@AllArgsConstructor
public class VarAnalysis extends Expression {
    private String key;

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

 抽象运算符号解析器:

@AllArgsConstructor
public class SymbolAnalysis extends Expression {
    protected Expression left;
    protected Expression right;

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return 0;
    }
}

加法解析器: 

public class AddAnalysis extends SymbolAnalysis {
    public AddAnalysis(Expression left, Expression right) {
        super(left, right);
    }

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

 减法解析器:

public class SubAnalysis extends SymbolAnalysis {

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

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

 解析器封装类:

public class Calculator {

    //定义表达式
    private Expression expression;

    //构造函数传参,并解析
    public Calculator(String expStr) {
        //安排运算先后顺序
        Stack<Expression> stack = new Stack<>();
        //表达式拆分为字符数组
        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 VarAnalysis(String.valueOf(charArray[++i]));
                    stack.push(new AddAnalysis(left, right));
                    break;
                case '-':    //减法
                    left = stack.pop();
                    right = new VarAnalysis(String.valueOf(charArray[++i]));
                    stack.push(new SubAnalysis(left, right));
                    break;
                default:    //公式中的变量
                    stack.push(new VarAnalysis(String.valueOf(charArray[i])));
                    break;
            }
        }
        this.expression = stack.pop();
    }

    //计算
    public int run(HashMap<String, Integer> var) {
        return this.expression.interpreter(var);
    }
}
public class Client {
    public static void main(String[] args) throws IOException {
        String expStr = getExpStr();
        HashMap<String, Integer> var = getValue(expStr);
        Calculator calculator = new Calculator(expStr);
        System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
    }

    //获得表达式
    public static String getExpStr() throws IOException {
        System.out.print("请输入表达式:");
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    }

    //获得值映射
    public static HashMap<String, Integer> getValue(String expStr) throws IOException {
        HashMap<String, Integer> map = new HashMap<>();

        for(char ch : expStr.toCharArray()) {
            if(ch != '+' && ch != '-' ) {
                if(! map.containsKey(String.valueOf(ch))) {
                    System.out.print("请输入" + String.valueOf(ch) + "的值:");
                    String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }
        return map;
    }
}
请输入表达式:a-b+c
请输入a的值:3
请输入b的值:2
请输入c的值:1
运算结果:a-b+c=2

Calculator构造函数传参,并解析封装。这里根据栈的“先进后出”来安排运算的先后顺序(主要用在乘除法,这里只写了加减法比较简单)。以加法为例,Calculator构造函数接收一个表达式,然后把表达式转换为char数组,并判断运算符号,如果是‘+’则进行加法运算,把左边的数(left变量)和右边的数(right变量)加起来即可。

例如a+b-c这个表达式,根据for循环,首先被压入栈中的是a元素生成的VarExpression对象,然后判断到加号时,把a元素的对象从栈中pop出来,与右边的数组b进行相加,而b是通过当前的数组游标下移一个单元格得来的(为了防止该元素被再次遍历,通过++i的方式跳过下一遍历)。减法运算同理。

三、适用场景

优点:

  1. 可扩展性比较好,灵活。
  2. 增加了新的解释表达式的方式。
  3. 易于实现简单文法。

缺点:

  1.  可利用场景比较少。
  2. 对于复杂的文法比较难维护。
  3. 解释器模式会引起类膨胀。
  4. 解释器模式采用递归调用方法。

使用场景: 

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 一些重复出现的问题可以用一种简单的语言来进行表达。
  3. 一个简单语法需要解释的场景。

总的来说,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

 

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(93)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货