Files
knowledge-kit/Chapter6 - Design Pattern/6.29.md
2023-11-02 16:06:38 +08:00

138 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 解释器模式
## 定义
解释器模式的英文翻译是 Interpreter Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的
> Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.
翻译成中文就是:解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法
这里面有很多我们平时开发中很少接触的概念,比如“语言”“语法”“解释器”。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
要想了解“语言”表达的信息我们就必须定义相应的语法规则。这样书写者就可以根据语法规则来书写“句子”专业点的叫法应该是“表达式”阅读者根据语法规则来阅读“句子”这样才能做到信息的正确传递。而我们要讲的解释器模式其实就是用来实现根据语法规则解读“句子”的解释器。这个过程类似加解密。通信前Alice 使用约定好的密钥(语法、文法)进行加密,得到秘文。然后 Bob 收到秘文后同样利用约定好的加密方式(语法、文法)去解密,得到原始信息。
## 实现
假设我们定义了一个新的加减乘除计算“语言”,语法规则如下:
- 运算符只包含加、减、乘、除,并且没有优先级的概念;
- 表达式(也就是前面提到的“句子”)中,先书写数字,后书写运算符,空格隔开;
按照先后顺序,取出两个数字和一个运算符计算结果,结果重新放入数字的最头部位置,循环上述过程,直到只剩下一个数字,这个数字就是表达式最终的计算结果。
我们举个例子来解释一下上面的语法规则:比如“ 8 3 2 4 - + * ”这样一个表达式我们按照上面的语法规则来处理取出数字“8 3”和“-”运算符,计算得到 5于是表达式就变成了“ 5 2 4 + * ”。然后,我们再取出“ 5 2 ”和“ + ”运算符,计算得到 7表达式就变成了“ 7 4 * ”。最后,我们取出“ 7 4”和“ * ”运算符,最终得到的结果就是 28。
```
public class ExpressionInterpreter {
private Deque<Long> numbers = new LinkedList<>();
public long interpret(String expression) {
String[] elements = expression.split(" ");
int length = elements.length;
for (int i = 0; i < (length+1)/2; ++i) {
numbers.addLast(Long.parseLong(elements[i]));
}
for (int i = (length+1)/2; i < length; ++i) {
String operator = elements[i];
boolean isValid = "+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator);
if (!isValid) {
throw new RuntimeException("Expression is invalid: " + expression);
}
long number1 = numbers.pollFirst();
long number2 = numbers.pollFirst();
long result = 0;
if (operator.equals("+")) {
result = number1 + number2;
} else if (operator.equals("-")) {
result = number1 - number2;
} else if (operator.equals("*")) {
result = number1 * number2;
} else if (operator.equals("/")) {
result = number1 / number2;
}
numbers.addFirst(result);
}
if (numbers.size() != 1) {
throw new RuntimeException("Expression is invalid: " + expression);
}
return numbers.pop();
}
}
```
问题分析:
上面的代码实现中,语法解析都写在了 if 判断中了,如果简单的语法规则解析,这样的设计就足够了。但是,对于简单的语法规则解析,这样就足够了。但是,对于复杂的语法规则的解析,逻辑耦合在一起很复杂了,代码量会很多,这在设计上是不合理的。往往这个时候就需要拆分代码,将解析逻辑拆分到独立的子类中。
如何拆分?
解释器模式的实现比较灵活,没有固定的模版,主要是思想。核心是将语法解析的工作拆分到各个子类中,以此来避免大而全的解析类。
前面定义的语法规则有两类表达式:一类是数字、一类是运算符。运算符包括加减乘除,利用解析器模式,把解析工作拆分到 NumberExpression、AdditionExpression、SubstractionExpress、MultiplicationExpression、DivisionExpression 解析类。
代码如下:
```
public interface Expression {
long interpret();
}
public class NumberExpression implements Expression {
private long number;
public NumberExpression(long number) {
this.number = number;
}
public NumberExpression(String number) {
this.number = Long.parseLong(number);
}
@Override
public long interpret() {
return this.number;
}
}
public class AdditionExpression implements Expression {
private Expression exp1;
private Expression exp2;
public AdditionExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() + exp2.interpret();
}
}
// SubstractionExpression、MultiplicationExpression、DivisionExpression、AdditionExpression
public class ExpressionInterpreter {
private Deque<Expression> numbers = new LinkedList<>();
public long interpret(String expression) {
String[] elements = expression.split(" ");
int length = elements.length;
for (int i = 0; i < (length+1)/2; ++i) {
numbers.addLast(new NumberExpression(elements[i]));
}
for (int i = (length+1)/2; i < length; ++i) {
String operator = elements[i];
boolean isValid = "+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator);
if (!isValid) {
throw new RuntimeException("Expression is invalid: " + expression);
}
Expression exp1 = numbers.pollFirst();
Expression exp2 = numbers.pollFirst();
Expression combinedExp = null;
if (operator.equals("+")) {
combinedExp = new AdditionExpression(exp1, exp2);
} else if (operator.equals("-")) {
combinedExp = new AdditionExpression(exp1, exp2);
} else if (operator.equals("*")) {
combinedExp = new AdditionExpression(exp1, exp2);
} else if (operator.equals("/")) {
combinedExp = new AdditionExpression(exp1, exp2);
}
long result = combinedExp.interpret();
numbers.addFirst(new NumberExpression(result));
}
if (numbers.size() != 1) {
throw new RuntimeException("Expression is invalid: " + expression);
}
return numbers.pop().interpret();
}
}
```
## 场景
- Java 中注解处理器做的就是解释功能
- 前端编译时的语法分析、语义分析
- 语言编译时生成的中间代码 IR用于编译后端的更多编译优化
- 自定义一门语言,然后通过语法解析器去分析读入