解释器是一种不常使用的设计模式,它用于描述如何构成一个简单的语言解释器,主要应用于使用面向对象语言开发的编译器和解释器设计。当咱们须要开发一个新的语言时,能够考虑使用解释器模式html
若是在系统中某一特定类型的问题发生的频率很高,此时能够考虑将这些问题的实例表述为一个语言中的句子。再构建一个解释器,解释器经过解释这些句子来解决对应的问题。java
举个例子,咱们但愿系统提供一个功能来支持一种新的加减法表达式语言,当输入表达式为 "1 + 2 + 3 - 4 + 1" 时,输出计算结果为 3。为了实现上述功能,须要对输入表达式进行解释,若是不做解释,直接把 "1 + 2 + 3 - 4 + 1" 丢过去,现有的如 Java、C 之类的编程语言只会把它看成普通的字符串,不可能实现咱们想要的计算效果。咱们必须本身定义一套规则来实现该语句的解释,即实现一个简单语言来解释这些句子,这就是解释器模式的模式动机。node
定义语言的文法,而且创建一个解释器来解释该语言中的句子,这里的 “语言” 意思是使用规定格式和语法的代码,它是一种类行为型模式。express
AbstractExpression(抽象表达式)编程
声明了抽象的解释操做,它是全部终结符表达式和非终结符表达式的公共父类设计模式
TerminalExpression(终结符表达式)编程语言
抽象表达式的子类,实现了文法中的终结符相关联的解释操做,在句子中每个终结符都是该类的一个实例。ide
NonterminalExpression(非终结符表达式)学习
也是抽象表达式的子类,实现了文法中的非终结符相关联的解释操做,非终结符表达式中能够包含终结符表达式,也能够继续包含非终结符表达式,所以其解释操做通常经过递归方式来完成。测试
Context(环境类)
环境类又称上下文类,它用于存储解释器以外的一些全局信息,一般它临时存储了须要解释的语句。
Client(客户类)
客户类中构造了表示以规定文法定义的一个特定句子的抽象语法树,该抽象语法树由非终结符表达式和终结符表达式实例组合而成。在客户类中还将调用解释操做,实现对句子的解释,有时候为了简化客户类代码,也能够将抽象语法树的构造工做封装到专门的类中完成,客户端只需提供待解释的句子并调用该类的解释操做便可,该类能够称为解释器封装类
仍是以以前提到的加减法表达式语言来举例,咱们要为这门语言定义语法规则,可使用以下文法来定义
expression ::= value | symbol symbol ::= expression '+' expression | expression '-' expression value ::= an integer // 一个整数值
该文法规则包含三条定义语句,第一句是表达式的组成方式,expression 是咱们最终要获得的句子,假设是 "1 + 2 + 3 - 4 + 1",那么该句的组成元素无非就是两种,数字(value)和运算符号(symbol),若是用专业术语来描述的话,symbol 和 value 称为语法构形成分或语法单位。根据句子定义,expression 要么是一个 value,要么是一个 symbol。
value 是一个终结符表达式,由于它的组成元素就是一个整数值,不能再进行分解。与之对应的 symbol 则是非终结符表达式,它的组成元素仍旧能够是表达式 expression,expression 又能够是 value 或者 symbol,便可以进一步分解。
按照上述的文法规则,咱们能够经过一种称之为抽象语法树(Abstract Syntax Tree)的图形方式来直观地表示语言的构成,每一颗抽象语法树对应一个语言实例,如 "1 + 2 + 3 - 4 + 1" 能够经过如图的抽象语法树来表示。
每个具体的语句均可以用相似的抽象语法树来表示,终结符表达式类的实例做为树的叶子节点,而非终结符表达式类的实例做为非叶子节点。抽象语法树描述了如何构成一个复杂的句子,经过对抽象语法树的分析,能够识别出语言中的终结符和非终结符类。
在解释器模式中,每个终结符和非终结符都有一个具体类与之对应,正由于使用类来表示每个语法规则,使得系统具备较好的扩展性和灵活性。对于全部的终结符和非终结符,首先要抽象出一个公共父类
public abstract class AbstractExpression { public abstract void interpret(Context ctx); }
对于终结符表达式,其代码主要是对终结符元素的处理
public class TerminalExpression extends AbstractExpression { public void interpret(Context ctx) { // 对于终结符表达式的解释操做 } }
对于终结符表达式,其代码比较复杂,由于经过非终结符表达式能够将表达式组合成更复杂的结构。表达式能够经过非终结符链接在一块儿,对于两个操做元素的非终结符表达式,其典型代码以下
public class NonterminalExpression extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public NonterminalExpression(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } public void interpret(Context ctx) { // 递归调用每个组成部分的 interpret() 方法 // 在递归调用时指定组成部分的链接方式,即非终结符的功能 } }
一般在解释器模式中还提供了一个环境类 Context,用于存储一些全局信息,用于在进行具体的解释操做时从中获取相关信息。当系统无须提供全局公共信息时,能够省略环境类
public class Context { private HashMap map = new HashMap(); public void assign(String key, String value) { // 往环境类中设值 } public void lookup(String key) { // 获取存储在环境类中的值 } }
现需构造一个语言解释器,使系统能够执行整数间的乘、除和求模运算。当用户输入表达式 "3 * 4 / 2 % 4",输出结果为 2
抽象表达式类 Node(抽象节点)
public interface Node { public int interpret(); }
终结符表达式类 ValueNode(值节点类)
public class ValueNode implements Node { private int value; public ValueNode(int value) { this.value = value; } @Override public int interpret() { return this.value; } }
抽象非终结符表达式类 SymbolNode(符号节点类)
public abstract class SymbolNode implements Node { protected Node left; protected Node right; public SymbolNode(Node left, Node right) { this.left = left; this.right = right; } }
非终结符表达式类 MulNode(乘法节点类)
public class MulNode extends SymbolNode { public MulNode(Node left, Node right) { super(left, right); } @Override public int interpret() { return super.left.interpret() * super.right.interpret(); } }
非终结符表达式类 DivNode(除法节点类)
public class DivNode extends SymbolNode { public DivNode(Node left, Node right) { super(left, right); } @Override public int interpret() { return super.left.interpret() / super.right.interpret(); } }
非终结符表达式类 ModNode(求模节点类)
public class ModNode extends SymbolNode { public ModNode(Node left, Node right) { super(left, right); } @Override public int interpret() { return super.left.interpret() % super.right.interpret(); } }
解释器封装类 Calculator(计算器类)
Calculator 类是本实例的核心类之一,Calculator 类中定义了如何构造一棵抽象语法树,在构造过程当中使用了栈结构 Stack。经过一连串判断语句判断字符,若是是数字,实例化终结符表达式类 ValueNode 并压栈;若是判断为运算符号,则取出栈顶内容做为其左表达式,而将以后输入的数字封装在 ValueNode 类型的对象做为其右表达式,建立非终结符表达式 MulNode 类型的对象,最后将该表达式压栈。
public class Calculator { private String statement; private Node node; public void build(String statement) { Node left = null, right = null; Stack<Node> stack = new Stack<Node>(); String[] statementArr = statement.split(" "); for (int i = 0; i < statementArr.length; i++) { if (statementArr[i].equalsIgnoreCase(("*"))) { left = stack.pop(); int val = Integer.parseInt(statementArr[++i]); right = new ValueNode(val); stack.push(new MulNode(left, right)); } else if (statementArr[i].equalsIgnoreCase(("/"))) { left = stack.pop(); int val = Integer.parseInt(statementArr[++i]); right = new ValueNode(val); stack.push(new DivNode(left, right)); } else if (statementArr[i].equalsIgnoreCase(("%"))) { left = stack.pop(); int val = Integer.parseInt(statementArr[++i]); right = new ValueNode(val); stack.push(new ModNode(left, right)); } else { stack.push(new ValueNode(Integer.parseInt(statementArr[i]))); } } this.node = stack.pop(); } public int compute() { return node.interpret(); } }
客户端测试类 Client
程序执行时将递归调用每个表达式类的 interpret() 的解释方法,最终完成对整棵抽象语法树的解释。
public class Client { public static void main(String[] args) { String statement = "3 * 4 / 2 % 4"; Calculator calculator = new Calculator(); calculator.build(statement); int result = calculator.compute(); System.out.println(statement + " = " + result); } }
运行结果
解释器模式优势以下:
解释器模式缺点以下: