设计模式之---解释器模式
解释器模式概述
解释器模式是一种使用频率相对较低但学习难度较大的设计模式,它用于描述如何使用面向对象语言构成一个简单的语言解释器。在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言拥有自己的表达式和结构,即文法规则,这些问题的实例将对应为该语言中的句子。此时,可以使用解释器模式来设计这种新的语言。对解释器模式的学习能够加深我们对面向对象思想的理解,并且掌握编程语言中文法规则的解释过程。
解释器模式定义如下:
解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。 |
由于表达式可分为终结符表达式和非终结符表达式,因此解释器模式的结构与组合模式的结构有些类似,但在解释器模式中包含更多的组成元素,它的结构如图18-3所示:
图18-3 解释器模式结构图
在解释器模式结构图中包含如下几个角色:
● AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
● TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。
● NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
● Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。
在解释器模式中,每一种终结符和非终结符都有一个具体类与之对应,正因为使用类来表示每一条文法规则,所以系统将具有较好的灵活性和可扩展性。对于所有的终结符和非终结符,我们首先需要抽象出一个公共父类,即抽象表达式类,其典型代码如下所示:
abstract class AbstractExpression { public abstract void interpret(Context ctx); } |
终结符表达式和非终结符表达式类都是抽象表达式类的子类,对于终结符表达式,其代码很简单,主要是对终结符元素的处理,其典型代码如下所示:
class TerminalExpression extends AbstractExpression { public void interpret(Context ctx) { //终结符表达式的解释操作 } } |
对于非终结符表达式,其代码相对比较复杂,因为可以通过非终结符将表达式组合成更加复杂的结构,对于包含两个操作元素的非终结符表达式类,其典型代码如下:
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,用于存储一些全局信息,通常在Context中包含了一个HashMap或ArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,如变量名与值的映射关系(key/value)等,用于在进行具体的解释操作时从中获取相关信息。其典型代码片段如下:
class Context { private HashMap map = new HashMap();
public void assign(String key, String value) { //往环境类中设值 }
public String lookup(String key) { //获取存储在环境类中的值 } } |
当系统无须提供全局公共信息时可以省略环境类,可根据实际情况决定是否需要环境类。
解释器模式实践
为了能够解释机器人控制指令,Sunny软件公司开发人员使用解释器模式来设计和实现机器人控制程序。针对五条文法规则,分别提供五个类来实现,其中终结符表达式direction、action和distance对应DirectionNode类、ActionNode类和DistanceNode类,非终结符表达式expression和composite对应SentenceNode类和AndNode类。
我们可以通过抽象语法树来表示具体解释过程,例如机器人控制指令“down run 10 and left move 20”对应的抽象语法树如图18-4所示:
图18-4 机器人控制程序抽象语法树实例
机器人控制程序实例基本结构如图18-5所示:
图18-5 机器人控制程序结构图
在图18-5中,AbstractNode充当抽象表达式角色,DirectionNode、ActionNode和DistanceNode充当终结符表达式角色,AndNode和SentenceNode充当非终结符表达式角色。完整代码如下所示:
- //注:本实例对机器人控制指令的输出结果进行模拟,将英文指令翻译为中文指令,实际情况是调用不同的控制程序进行机器人的控制,包括对移动方向、方式和距离的控制等
- import java.util.*;
- //抽象表达式
- abstract class AbstractNode {
- public abstract String interpret();
- }
- //And解释:非终结符表达式
- class AndNode extends AbstractNode {
- private AbstractNode left; //And的左表达式
- private AbstractNode right; //And的右表达式
- public AndNode(AbstractNode left, AbstractNode right) {
- this.left = left;
- this.right = right;
- }
- //And表达式解释操作
- public String interpret() {
- return left.interpret() + "再" + right.interpret();
- }
- }
- //简单句子解释:非终结符表达式
- class SentenceNode extends AbstractNode {
- private AbstractNode direction;
- private AbstractNode action;
- private AbstractNode distance;
- public SentenceNode(AbstractNode direction,AbstractNode action,AbstractNode distance) {
- this.direction = direction;
- this.action = action;
- this.distance = distance;
- }
- //简单句子的解释操作
- public String interpret() {
- return direction.interpret() + action.interpret() + distance.interpret();
- }
- }
- //方向解释:终结符表达式
- class DirectionNode extends AbstractNode {
- private String direction;
- public DirectionNode(String direction) {
- this.direction = direction;
- }
- //方向表达式的解释操作
- public String interpret() {
- if (direction.equalsIgnoreCase("up")) {
- return "向上";
- }
- else if (direction.equalsIgnoreCase("down")) {
- return "向下";
- }
- else if (direction.equalsIgnoreCase("left")) {
- return "向左";
- }
- else if (direction.equalsIgnoreCase("right")) {
- return "向右";
- }
- else {
- return "无效指令";
- }
- }
- }
- //动作解释:终结符表达式
- class ActionNode extends AbstractNode {
- private String action;
- public ActionNode(String action) {
- this.action = action;
- }
- //动作(移动方式)表达式的解释操作
- public String interpret() {
- if (action.equalsIgnoreCase("move")) {
- return "移动";
- }
- else if (action.equalsIgnoreCase("run")) {
- return "快速移动";
- }
- else {
- return "无效指令";
- }
- }
- }
- //距离解释:终结符表达式
- class DistanceNode extends AbstractNode {
- private String distance;
- public DistanceNode(String distance) {
- this.distance = distance;
- }
- //距离表达式的解释操作
- public String interpret() {
- return this.distance;
- }
- }
- //指令处理类:工具类
- class InstructionHandler {
- private String instruction;
- private AbstractNode node;
- public void handle(String instruction) {
- AbstractNode left = null, right = null;
- AbstractNode direction = null, action = null, distance = null;
- Stack stack = new Stack(); //声明一个栈对象用于存储抽象语法树
- String[] words = instruction.split(" "); //以空格分隔指令字符串
- for (int i = 0; i < words.length; i++) {
- //本实例采用栈的方式来处理指令,如果遇到“and”,则将其后的三个单词作为三个终结符表达式连成一个简单句子SentenceNode作为“and”的右表达式,而将从栈顶弹出的表达式作为“and”的左表达式,最后将新的“and”表达式压入栈中。 if (words[i].equalsIgnoreCase("and")) {
- left = (AbstractNode)stack.pop(); //弹出栈顶表达式作为左表达式
- String word1= words[++i];
- direction = new DirectionNode(word1);
- String word2 = words[++i];
- action = new ActionNode(word2);
- String word3 = words[++i];
- distance = new DistanceNode(word3);
- right = new SentenceNode(direction,action,distance); //右表达式
- stack.push(new AndNode(left,right)); //将新表达式压入栈中
- }
- //如果是从头开始进行解释,则将前三个单词组成一个简单句子SentenceNode并将该句子压入栈中
- else {
- String word1 = words[i];
- direction = new DirectionNode(word1);
- String word2 = words[++i];
- action = new ActionNode(word2);
- String word3 = words[++i];
- distance = new DistanceNode(word3);
- left = new SentenceNode(direction,action,distance);
- stack.push(left); //将新表达式压入栈中
- }
- }
- this.node = (AbstractNode)stack.pop(); //将全部表达式从栈中弹出
- }
- public String output() {
- String result = node.interpret(); //解释表达式
- return result;
- }
- }
工具类InstructionHandler用于对输入指令进行处理,将输入指令分割为字符串数组,将第1个、第2个和第3个单词组合成一个句子,并存入栈中;如果发现有单词“and”,则将“and”后的第1个、第2个和第3个单词组合成一个新的句子作为“and”的右表达式,并从栈中取出原先所存句子作为左表达式,然后组合成一个And节点存入栈中。依此类推,直到整个指令解析结束。
编写如下客户端测试代码:
- class Client {
- public static void main(String args[]) {
- String instruction = "up move 5 and down run 10 and left move 5";
- InstructionHandler handler = new InstructionHandler();
- handler.handle(instruction);
- String outString;
- outString = handler.output();
- System.out.println(outString);
- }
- }
编译并运行程序,输出结果如下:
向上移动5再向下快速移动10再向左移动5 |
解释器模式总结
解释器模式为自定义语言的设计和实现提供了一种解决方案,它用于定义一组文法规则并通过这组文法规则来解释语言中的句子。虽然解释器模式的使用频率不是特别高,但是它在正则表达式、XML文档解释等领域还是得到了广泛使用。与解释器模式类似,目前还诞生了很多基于抽象语法树的源代码处理工具,例如Eclipse中的Eclipse AST,它可以用于表示Java语言的语法结构,用户可以通过扩展其功能,创建自己的文法规则。
1. 主要优点
解释器模式的主要优点如下:
(1) 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
(2) 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
(3) 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
(4) 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。
2. 主要缺点
解释器模式的主要缺点如下:
(1) 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
(2) 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
3. 适用场景
在以下情况下可以考虑使用解释器模式:
(1) 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
(2) 一些重复出现的问题可以用一种简单的语言来进行表达。
(3) 一个语言的文法较为简单。
(4) 执行效率不是关键问题。【注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。】