解释器模式
一、什么是解释器模式?
解释器模式(Interpreter Design Pattern):对具有某种语法规则的语言表示,定义一个解释器来处理这个语法
Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.
解释器模式的一些特点:
- 存在某种语法规则,比如算术运算表达式,中英文翻译等
- 语法规则可能会出现循环、递归
- 解释器模式就是根据语法规则去解读“句子”
二、为什么要用解释器模式?
场景:
- 一些重复出现的问题可以用一种简单的语言来表达
- 当有一个语言中的句子可以表示为一个抽象语法树时
原理:
- 简单的语法规则,可以不用解释器模式,防止过度设计
- 复杂的语法规则解析,逻辑复杂,代码量多
- 复杂语法解析代码都写在一个类中,代码的耦合度会很高,并且不易维护和扩展
- 将语法规则拆分成多条细则,拆分到各个小类中,避免解析类过于膨胀
- 每条细则,都是单独的单元,只负责对这部分语法规则进行解析
三、解释器的结构是怎么样的?
解释器中包含的角色有:
- 抽象表达式(AbstrctExpression):定义了语法树节点的共同接口
- 终结符表达式(TerminalExpression):负责语法树中的叶子节点
- 非终结符表达式(NoterminalExpression):负责语法树中的非叶子节点
- 上下文环境(Context):提供必要信息或者公共的方法

示例程序结构:

抽象表达式(AbstrctExpression):
1 2 3
| public abstract class Node { public abstract void parse(Context context) throws ParseException; }
|
终结符表达式(TerminalExpression):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class PrimitiveCommandNode extends Node {
private String name;
@Override public void parse(Context context) throws ParseException { name = context.currentToken(); context.skipToken(name); if (!"go".equals(name) && !"right".equals(name) && !"left".equals(name)) { throw new ParseException(name + " is undefined"); } }
@Override public String toString() { return name; } }
|
非终结符表达式(NoterminalExpression):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ProgramNode extends Node { private Node commandListNode;
@Override public void parse(Context context) throws ParseException { context.skipToken("program"); commandListNode = new CommandListNode(); commandListNode.parse(context); }
@Override public String toString() { return "[" + "program=" + commandListNode + "]"; } }
|
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
| public class CommandListNode extends Node {
private List<Node> commandNodes = new ArrayList<>();
@Override public void parse(Context context) throws ParseException { while (true) { if (context.currentToken() == null) { throw new ParseException("Missing end"); } if ("end".equals(context.currentToken())) { context.skipToken("end"); break; } Node commandNode = new CommandNode(); commandNode.parse(context); commandNodes.add(commandNode); } }
@Override public String toString() { return commandNodes.toString(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class CommandNode extends Node {
private Node node;
@Override public void parse(Context context) throws ParseException { if ("repeat".equals(context.currentToken())) { node = new RepeatCommandNode(); } else { node = new PrimitiveCommandNode(); } node.parse(context); }
@Override public String toString() { return node.toString(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class RepeatCommandNode extends Node {
private int number; private Node commandListNode;
@Override public void parse(Context context) throws ParseException { context.skipToken("repeat"); number = context.currentNumber(); context.nextToken(); commandListNode = new CommandListNode(); commandListNode.parse(context); }
@Override public String toString() { return "[repeat " + number + " " + commandListNode + "]"; } }
|
上下文环境(Context)
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
| public class Context {
private StringTokenizer tokenizer; private String currentToken;
public Context(String text) { tokenizer = new StringTokenizer(text); nextToken(); }
public String nextToken() { if (tokenizer.hasMoreTokens()) { currentToken = tokenizer.nextToken(); } else { currentToken = null; } return currentToken; }
public String currentToken() { return currentToken; }
public void skipToken(String token) throws ParseException { if (!token.equals(currentToken)) { throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found"); } nextToken(); }
public int currentNumber() throws ParseException { int number = 0; try { number = Integer.parseInt(currentToken); } catch (Exception e) { throw new ParseException("Warning: " + e); } return number; } }
|
四、解释器模式有什么优缺点?
优点:
- 符合“单一职责”原则,每个语法细则都是一个类,只负责自己那部分的解析
- 可扩展性好,扩展新的语法规则简单,只需要新增类或者继承已有类即可
缺点:
- 引起类膨胀,每条规则都定义一个类,如果规则很多,就会出现大量的类
- 执行效率低,使用了大量的循环和递归,同时调试也很麻烦
- 应用场景少,简单的规则一般用不上解释器模式,复杂规则的场景比较少,一般用在语言解析比较多
五、应用场景