解释器模式
定义:
构建语法树,定义终结符与非终结符。
在 GOF 的书中指出:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
- 解释器模式(Interpreter Pattern)是指给定一门语言,定义它的文法的一种表示(如:加减乘除表达式和正则表达式等),然后再定义一个解释器,该解释器用来解释我们的文法表示(表达式)。
- 解释器模式是一种按照规定的语法(文法)来进行解析的一种设计模式,属于行为型模式
组成:
构建语法树,定义终结符与非终结符
终结符表达式 和 非终结符表达式:
在解释器模式中有终结符表达式和非终结符表达式这两个概念
- 终结符表达式(Terminal Expression):
实现文法中与终结符有关的解释操作。文法中每一个终结符都有一个具体的终结符表达式与之相对应。比如我们的R=M+N运算,M和N就是终结符,对应的解析M和N的解释器就是终结符表达式。 - 非终符结表达式(Nonterminal Expression):
实现文法中与非终结符有关的解释操作。文法中的每一条规则都对应了一个非终结符表达式。非终结表达式一般是文法中的运算符或者关键字,如上面公示:R=M+N中的“+”号就是非终结符,解析“+”号的解释器就是一个非终结符表达式。
终结符表达式(相当于树的叶子):遇到这个表达式interpreter执行能直接返回结果,不会向下继续调用。
案例 1 :计算器
需求:
模拟简单的加减法表达式
抽象表达式角色: IExpression
public interface IExpression {
int interpret();
}
抽象的非终结表达式: AbstractNonTerminalExpression
/**
* 非终结表达式-抽象表达式
*/
public abstract class AbstractNonTerminalExpression implements IExpression{
protected IExpression leftExpression;
protected IExpression rightExpression;
public AbstractNonTerminalExpression(IExpression leftExpression, IExpression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
}
具体非终结表达式角色: AddExpression , SubExpression
本次示例中只列举加法和减法,所以我们还需要一个加法类和一个减法类:
- AddExpression 加法类
/**
* 非终结表达式-具体表达式-加法表达式
*/
public class AddExpression extends AbstractNonTerminalExpression {
public AddExpression(IExpression leftExpression, IExpression rightExpression) {
super(leftExpression, rightExpression);
}
@Override
public int interpret() {
return this.leftExpression.interpret() + this.rightExpression.interpret();
}
}
- SubExpression 减法类
/**
* 非终结表达式-具体表达式-减法表达式
*/
public class SubExpression extends AbstractNonTerminalExpression {
public SubExpression(IExpression leftExpression, IExpression rightExpression) {
super(leftExpression, rightExpression);
}
@Override
public int interpret() {
return this.leftExpression.interpret() - this.rightExpression.interpret();
}
}
终结表达式角色: NumberExpression
- 终结表达式(如加减法运算中的数值)
- 相当于语法树的叶子节点
/**
* 终结表达式-数值表达式
*/
public class NumberExpression implements IExpression{
private int value;
public NumberExpression(String value) {
this.value = Integer.valueOf(value);
}
@Override
public int interpret() {
return this.value;
}
}
上下文环境角色:ExpressionContext
这里new一个java自带的 stack 来作为我们计算元素的存储容器
public class ExpressionContext {
private Integer currValue;//记录当前运算结果,空表示暂未运算
private Stack<IExpression> stack = new Stack<>();
public ExpressionContext(String expression) {
this.parse(expression);
}
private void parse(String expression) {
String[] elementArr = expression.split(" ");
for (int i=0;i<elementArr.length;i++){
String element = elementArr[i];
if (element.equals("+")){
IExpression leftExpression = stack.pop();//栈内元素出栈
IExpression rightExpression = new NumberExpression(elementArr[++i]);//取出+号后的下一个元素
IExpression addExpression = new AddExpression(leftExpression,rightExpression);
stack.push(new NumberExpression(addExpression.interpret() + ""));//将计算结果入栈
}else if (element.equals("-")){
IExpression leftExpression = stack.pop();//栈内元素出栈
IExpression rightExpression = new NumberExpression(elementArr[++i]);//取出-号后的下一个元素
IExpression subExpression = new SubExpression(leftExpression,rightExpression);
stack.push(new NumberExpression(subExpression.interpret() + ""));//将计算结果入栈
}else{
stack.push(new NumberExpression(element));//如果是数字则直接入栈
}
}
}
public int calcuate(){
return stack.pop().interpret();//经过前面解析,到这里stack内只会剩下唯一一个数字,即运算结果
}
}
测试:
public class TestInterpreter {
public static void main(String[] args) {
ExpressionContext context = new ExpressionContext("666 + 888 - 777");
System.out.println(context.calcuate());
context = new ExpressionContext("123 - 456 + 11");
System.out.println(context.calcuate());
}
}
逆波兰表达式求值问题 线性表–06—栈—常见应用场景
解释器模式在Spring源码中体现
在 Spring 中,ExpressionParser 接口内部采用的也是解释器模式,它封装了字符串表达式的语法,这里面其实也是可以进行加减乘除运算的.
- 源码如下
package org.springframework.expression;
public interface ExpressionParser {
Expression parseExpression(String expressionString) throws ParseException;
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
ExpressionParser 不知道大家有没有用过,这里面也可以进行加减乘除运算。这里我们不深入讲解源码。如下是一个简单的应用示例:
package net.biancheng.c.interpreter;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class Test {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("100*2+400*1+66");
int result = (Integer) expression.getValue();
System.out.println("计算结果是:" + result);
}
}
解释器模式一般在业务开发中用的相对较少,常用的表达式都有人解析好了,直接用就可以了,除非我们从事底层开发需要自己去定义较为复杂的表达式。
总结
应用场景:
- 当我们有一些需要重复解析的问题可以考虑定义一个表达式来进行表达。
- 一些简单的语法如果需要解释的话也可以考虑解释器模式。
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 编译器
- 运算表达式计算、正则表达式
- 机器人
优点:
- 可扩展性比较好,灵活。
- 增加了新的解释表达式的方式
- 易于实现简单文法。
缺点:
- 当文法规则比较复杂时,会引起类膨胀,比较难维护。
- 当文法规则比较复杂时,如果出错了,调试比较困难。
- 执行效率比较低下。因为当表达式比较复杂,结果层层依赖的话会采用递归方式进行解析。