解释器模式(Interpreter Pattern) – 设计模式之行为模式:
目录
解释器模式(Interpreter Pattern)
定义: Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
类似迷你语言,通常当有一个语言需要解释执行,并且你可以将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
类图
解释器模式通用类图:
TerminalExpression 即终结符表达式,没有子表达式,像变量、常量等通常为终结符表达式。
NonTerminalExpression 即非终结符表达式,有子表达式,像加法(plus),减法(minus)等通常包含多个子表达式(包含x, y)
知识点:
用一个简单的算术表达式来说明解释器模式:
x + y - z
在以上表达式中,符号有 “x”,“y”,“z”,“+”和“-”, 一共5个符号,其中x,y,z实际上都是变量,那么我们分为三种语义:变量Variable, 加法Plus, 减法Minus。 其实在计算机中一般是用后缀表达式来进行算数运算的,上面的表达式转换为后缀表达式是:
x y z - +
后缀、中缀、前缀表达式之间转换是有方法的,这边不做详细解释,按照后缀表达式,x + y - z运算的顺序实际上是这样的:
- y - z. --> v
- x + v
即 x + (y - z)
过程:
实现 x + y – z 的操作
类图:
代码:
抽象表达式 Expression
抽象表达式是生成语法集合(也叫做语法树)的关键,每个语法集合完成指定语法解析任务,它是通过递归调用的方式,最终由最小的语法单元进行解析完成。
public abstract class Expression {
public abstract int interpret();
@Override
public abstract String toString();
}
终结符表达式 TerminalExpression
通常,终结符表达式比较简单,主要是处理场景元素和数据的转换。
public class VariableExpression extends Expression {
private final int number;
public VariableExpression(int number) {
this.number = number;
}
public VariableExpression(String str) {
this.number = Integer.parseInt(str);
}
@Override
public int interpret() {
return number;
}
@Override
public String toString() {
return "terminal variable";
}
}
加法表达式 非终结符表达式 PlusExpression
public class PlusExpression extends Expression {
private final Expression left;
private final Expression right;
public PlusExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
@Override
public String toString() {
return "+";
}
}
每个非终结符表达式都代表了一个文法规则,并且每个文法规则都只关心自己周边的文法规则的结果(注意是结果),因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式,然后最终、最小的文法规则就是终结符表达式,终结符表达式的概念就是如此,不能够再参与比自己更小的文法运算了。
减法表达式 非终结符表达式 MinusExpression
public class MinusExpression extends Expression {
private final Expression left;
private final Expression right;
public MinusExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
@Override
public String toString() {
return "-";
}
}
测试:
public class CalculateExpressionTest {
public static void main(String[] args) {
String tokenString = "4 3 1 - +";
Stack<Expression> stack = new Stack<>();
String[] tokenArr = tokenString.split(" ");
for (String str : tokenArr) {
if (whetherOperator(str)) {
Expression right = stack.pop();
Expression left = stack.pop();
System.out.println("popped from stack left: "+left.interpret()+", right: " +right.interpret());
Expression operator = getOperatorInstance(str, left, right);
System.out.println("operator: "+ operator);
int result = operator.interpret();
VariableExpression resultExpression = new VariableExpression(result);
stack.push(resultExpression);
System.out.println("push result to stack: "+ resultExpression.interpret());
} else {
VariableExpression nums = new VariableExpression(str);
stack.push(nums);
System.out.println("push to stack: "+ nums.interpret());
}
}
System.out.println("result: "+ stack.pop().interpret());
}
private static boolean whetherOperator(String str) {
return "+".equals(str) || "-".equals(str);
}
private static Expression getOperatorInstance(String str, Expression left, Expression right) {
switch (str) {
case "+":
return new PlusExpression(left, right);
case "-":
return new MinusExpression(left, right);
default:
return new PlusExpression(left, right);
}
}
}
在运算中,常用stack堆的方式,使用其特点“后进先出”进行处理
结果:
push to stack: 4
push to stack: 3
push to stack: 1
popped from stack left: 3, right: 1
operator: -
push result to stack: 2
popped from stack left: 4, right: 2
operator: +
push result to stack: 6
result: 6
总结:
优点:
1、可扩展性比较好,灵活。
2、增加了新的解释表达式的方式。
3、易于实现简单文法。
缺点:
1、可利用场景比较少。
2、对于复杂的文法比较难维护。
3、解释器模式会引起类膨胀。
4、解释器模式采用递归调用方法。
使用场景
1,重复发生的问题可以使用解释器模式
例如,多个应用服务器,每天产生大量的日志,需要对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,但是非终结符表达式就需要制定了。在这种情况下,可以通过程序来一劳永逸地解决该问题。
2,一个简单语法需要解释的场景
为什么是简单?看看非终结表达式,文法规则越多,复杂度越高,而且类间还要进行递归调用(看看我们例子中的栈)。想想看,多个类之间的调用你需要什么样的耐心和信心去排查问题。因此,解释器模式一般用来解析比较标准的字符集,例如符号处理引擎,SQL语法分析,不过该部分逐渐被专用工具所取代。
在某些特用的商业环境下也会采用解释器模式,我们刚刚的例子就是一个商业环境,而且现在模型运算的例子非常多,目前很多商业机构已经能够提供出大量的数据进行分析。
对于四则运算,如果有碰到,可以用 expression4J 代替