文章目录
【Java设计模式】解释器模式
一、概述
解释器设计模式用于为一种语言定义语法表示,并提供一个解释器来处理这种语法。该模式在需要解释和执行特定规则或语法的场景中非常有用,例如算术表达式或脚本语言。
二、详细解释及实际示例
- 实际示例:
- 考虑一个计算器应用程序,它被设计用来解释和计算用户输入的表达式。该应用程序使用Java中的解释器模式来解析和评估算术表达式,如“5 + 3 * 2”。在这里,解释器将表达式的每个部分转换为表示数字和操作的对象。这些对象遵循定义的语法,使应用程序能够根据算术规则正确理解和计算结果。表达式的每个元素都对应于程序结构中的一个类,简化了对任何输入的算术公式的解析和评估过程。
- 通俗解释:
- 解释器设计模式定义了一种语言语法的表示以及使用该表示来解释语言句子的解释器。
- 维基百科解释:
- 在计算机编程中,解释器模式是一种指定如何评估一种语言中的句子的设计模式。基本思想是为专门计算机语言中的每个符号(终结符或非终结符)都有一个类。该语言句子的语法树是组合模式的一个实例,并用于为客户端评估(解释)该句子。
三、Java中解释器模式的编程示例
为了能够在Java中解释基本的数学运算,我们需要一个表达式的层次结构。Expression
类是基类,像NumberExpression
这样的具体实现处理语法的特定部分。Java中的解释器模式通过将算术表达式转换为应用程序可以处理的结构化格式,简化了对算术表达式的解析和评估。
public abstract class Expression {
public abstract int interpret();
@Override
public abstract String toString();
}
最简单的表达式是NumberExpression
,它只包含一个整数。
public class NumberExpression extends Expression {
private final int number;
public NumberExpression(int number) {
this.number = number;
}
public NumberExpression(String s) {
this.number = Integer.parseInt(s);
}
@Override
public int interpret() {
return number;
}
@Override
public String toString() {
return "number";
}
}
更复杂的表达式是操作,如PlusExpression
、MinusExpression
和MultiplyExpression
。这里是第一个,其他的类似。
public class PlusExpression extends Expression {
private final Expression leftExpression;
private final Expression rightExpression;
public PlusExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
@Override
public int interpret() {
return leftExpression.interpret() + rightExpression.interpret();
}
@Override
public String toString() {
return "+";
}
}
现在,我们能够展示解释器模式在解析一些简单数学运算时的实际应用。
@Slf4j
public class App {
public static void main(String[] args) {
// 半身人小孩正在学校学习一些基本的数学
// 定义我们要解析的数学字符串
final var tokenString = "4 3 2 - 1 + *";
// 栈用于存储解析后的表达式
var stack = new Stack<Expression>();
// 对字符串进行标记化,并逐个处理它们
var tokenList = tokenString.split(" ");
for (var s : tokenList) {
if (isOperator(s)) {
// 当遇到操作符时,我们期望可以从栈顶弹出数字
var rightExpression = stack.pop();
var leftExpression = stack.pop();
LOGGER.info("从栈中弹出的左值:{} 右值:{}",
leftExpression.interpret(), rightExpression.interpret());
var operator = getOperatorInstance(s, leftExpression, rightExpression);
LOGGER.info("操作符:{}", operator);
var result = operator.interpret();
// 操作结果被推到栈顶
var resultExpression = new NumberExpression(result);
stack.push(resultExpression);
LOGGER.info("将结果推到栈中:{}", resultExpression.interpret());
} else {
// 数字被推到栈顶
var i = new NumberExpression(s);
stack.push(i);
LOGGER.info("推到栈中:{}", i.interpret());
}
}
// 最终,最终结果位于栈顶
LOGGER.info("结果:{}", stack.pop().interpret());
}
public static boolean isOperator(String s) {
return s.equals("+") || s.equals("-") || s.equals("*");
}
public static Expression getOperatorInstance(String s, Expression left, Expression right) {
return switch (s) {
case "+" -> new PlusExpression(left, right);
case "-" -> new MinusExpression(left, right);
default -> new MultiplyExpression(left, right);
};
}
}
执行程序会产生以下控制台输出。
13:33:15.437 [main] INFO com.iluwatar.interpreter.App -- 推到栈中:4
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 推到栈中:3
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 推到栈中:2
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 从栈中弹出的左值:3 右值:2
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 操作符:-
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 将结果推到栈中:1
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 推到栈中:1
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 从栈中弹出的左值:1 右值:1
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 操作符:+
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 将结果推到栈中:2
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 从栈中弹出的左值:4 右值:2
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 操作符:*
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 将结果推到栈中:8
13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- 结果:8
四、何时在Java中使用解释器模式
当存在需要解释的语言,并且可以将语言中的语句表示为抽象语法树时,使用解释器设计模式。解释器模式在以下情况下效果最佳:
- 语法简单。对于复杂的语法,语法的类层次结构会变得庞大且难以管理。在这种情况下,诸如解析器生成器之类的工具是更好的选择。它们可以在不构建抽象语法树的情况下解释表达式,这可以节省空间并可能节省时间。
- 效率不是关键问题。最有效的解释器通常不是通过直接解释解析树来实现的,而是首先将它们转换为另一种形式。例如,正则表达式通常被转换为状态机。但即使在这种情况下,翻译器也可以通过解释器模式来实现,因此该模式仍然适用。
五、解释器模式在Java中的实际应用
- java.util.Pattern
- java.text.Normalizer
- 所有java.text.Format的子类
- javax.el.ELResolver
- 各种数据库管理系统中的SQL解析器。
六、解释器模式的好处和权衡
好处:
- 可以轻松添加新的操作来解释表达式,而无需更改语法或数据类。
- 直接在语言中实现语法,使其易于修改或扩展。
权衡:
- 对于大型语法,可能会变得复杂且效率低下。
- 语法中的每个规则都会导致一个类,对于复杂的语法,会导致大量的类。