设计模式(十七)之行为型模式-解释器模式

一、解释器模式

1、案例

1.1 四则运算问题

通过解释器模式来实现四则运算,如计算a+b-c的值,看具体要求

(1)先输入表达式的形式,比如a+b+c-d+e,要求表达式的字母不能重复

(2)再分别输入a,b,c,d,e的值

(3)最后求出结果:类似下图

image-20210815212928466

1.2 传统方案

(1)编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果

(2)问题分析:如果加入新的运算符,比如*/()等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰

(3)解决方案:可以考虑使用解释器模式,即 表达式->解释器(可以有多种)->结果

2、解释器模式基本介绍

2.1 基本介绍

  1. 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元在通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器。
  2. 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
  3. 应用场景:
    • 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
    • 一些重复出现的问题可以用一种简单的语言来表达
    • 一个简单语法需要解释的场景
  4. 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等。

2.2 原理类图

image-20210811221747006

Context:是环境角色,含有解释器之外的全局信息

AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享

TerminalExpression:为终结符表达式,实现与文法中的终结符相关的解释操作

NonTerminalExpression:为非终结符表达式,为文法中的非终结符实现解释操作

说明:输入Context和TerminalExpression信息,通过Client输入即可

3、代码实例

3.1 实例类图

image-20210811230517722

3.2 代码

3.2.1 Expression抽象类

1
2
3
4
5
6
7
//抽象类表达式,通过HashMap键值对,可以获取到变量的值
public abstract class Expression {
// a + b -c
// 解释公式和数值,key就是公式(表达式) 参数[a,b,c],value就是具体数值
// HashMap{a=10,b=20}
public abstract int interpreter(HashMap<String,Integer> var);
}

3.2.2 VarExpression类

继承Expression类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.HashMap;
public class VarExpression extends Expression{

private String key;// key = a,key = b, key = c

private VarExpression(String key){
this.key = key;
}

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

3.2.3 SymbolExpression类

继承Expression类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.HashMap;

// 抽象运算符 解析器,这里,每个运算符合都只和自己左右两个数字有关系
// 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
public class SymbolExpression extends Expression {

protected Expression left;
protected Expression right;

public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

// 因为SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}

3.2.4 AddExpression类

继承SymbolExpression类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.HashMap;

// 加法解释器
public class AddExpression extends SymbolExpression {

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

// 处理相加
// var 仍然是 {a=10,b=20}
@Override
public int interpreter(HashMap<String, Integer> var) {
// super.left.interpreter(var): 返回left 表达式对应的值 a = 10
// super.right.interpreter(var): right 表达式对应的值 b = 10
return super.left.interpreter(var) + super.right.interpreter(var);
}
}

3.2.5 SubExpression类

继承SymbolExpression类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.HashMap;

public class SubExpression extends SymbolExpression {

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

// 求出left 和right 表达式相减后的结果
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}

3.2.6 Calculator类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.HashMap;
import java.util.Stack;

public class Calculator {

// 定义表达式
private Expression expression;

// 构造函数传参,并解析
public Calculator(String expStr) { // expStr = a + b

//安排运算符先后顺序
Stack<Expression> stack = new Stack<>();

char[] charArray = expStr.toCharArray(); // [a,+,b]

Expression left = null;
Expression right = null;

// 遍历我们的字符数组,即遍历 [a,+,b]
// 针对不同的情况,做处理

for(int i = 0 ;i < charArray.length;i++){
switch (charArray[i]){
case '+':
left = stack.pop();// 从stack 取出left => "a"
right = new VarExpression(String.valueOf(charArray[++i])); //取出右表达式 "b"
stack.push(new AddExpression(left,right)); // 然后根据得到left和right 构建AddExpression 加入stack
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left,right));
break;
default:
// 如果是一个Var,就创建一个VrExpression对象,并Push到Stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
// 当遍历完整个charArray数组后,stack 就得到了最后的Expression
this.expression = stack.pop();
}

public int run(HashMap<String,Integer> var){
// 最后将表达式a+b 和var={a=10,b=20} 绑定
// 然后传递给expression 的interpreter进行解释执行
return this.expression.interpreter(var);
}
}

3.2.7 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

public class Client {
public static void main(String[] args) throws IOException {
String expStr = getExpStr();// a+b
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.println("请输入表达式: ");
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.println("请输入"+String.valueOf(ch) + "的值");
String data = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch),Integer.valueOf(data));
}
}
}
return map;
}
}

结果:

1
2
3
4
5
6
7
请输入表达式: 
a+b
请输入a的值
10
请输入b的值
20
运算结果是:a+b=30

4、解释器模式在Spring 框架应用的源码分析

4.1 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Interpreter {
public static void main(String[] args) {
// 先创建一个Parser对象
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
// 通过Parser对象 获取到一个Expression
// 会根据不同的Parser对象,返回不同的Expression对象
Expression expression = spelExpressionParser.parseExpression("10*(2+1)*1+66");

int result = (Integer)expression.getValue();
System.out.println(result);
}
}

4.2 分析

(1)创建一个SpelExpressionParser对象

SpelExpressionParser 继承 TemplateAwareExpressionParser 继承 ExpressionParser

ExpressionParser 是一个接口

image-20210814225656593

其中,spelExpressionParser.parseExpression()方法是在TemplateAwareExpressionParser 类中实现

image-20210814225828114

doParseExpression:

image-20210814233524322

(2)查看Expression:是一个接口

1
public interface Expression {}

(3)ctrl+alt+B 查看Expression实现子类

image-20210814224545637

4.3 说明

    1. Expression是一个接口,表示式接口

      下面有不同的实现类,比如SpelExpression,或者CompositeStringExpression

      使用时候,根据创建的不同Parser对象,返回不同的Expression对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @Override
      public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
      if (context != null && context.isTemplate()) {
      return parseTemplate(expressionString, context);
      }
      else {
      return doParseExpression(expressionString, context);
      }
      }
    2. 使用得到的Expression对象,调用getValue解释执行表达式,最后得到结果

5、解释器模式的注意事项和细节

  1. 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
  2. 应用场景:编译器、运算表达式计算、正则表达式、机器人等
  3. 使用解释器可能带来的问题:解释器模式会引起类爆炸、解释器模式采用递归调用方法,将会导致调试非常复杂,效率可能降低
0%