场景引入:解释器模式用于描述如何构成一个简单的语言解释器,主要应用于使用面向对象语言开发的解释器的设计。当需要开发一个新的语言的时候可以考虑使用解释器模式
定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表达式来解释语言中的句子
文法规则和抽象语法树
本文以加/减法解释器为例,每一个输入表达式(例如1+2+3-4+1)都是包含了3个语言单位,可以使用以下文法规则来定义:
expression :: = value | operation operation :: = expression ‘+‘ expression | expression ‘-‘ expression value :: = an integer //一个整数值
该文法规则包含3条语句,第一条表示表达式的组成方式,其中value和operation是后面两个语言单位的定义,每一条语句所定义的字符串(如operation和value)称为语言构造成分或语言单位,符号“::=”是定义为的意思,其左边的语言单位通过右边来进行说明和定义,语言单位对应终结符表达式和非终结符表达式。例如本规则中的operation是非终结符表达式,它的组成元素仍可以是表达式,可以进一步分解,而value是终结符表达式,它的组成元素是最基本的语言单位,不能再进行分解。
抽象语法树可以直观的表示语言的构成,每一棵抽象语法树对应一个语言实例。例如加法/减法表达式语言中的语句“1+2+3-4+1”可以通过如图所示的抽象语法树来表示:
解释器模式结构
解释器模式包含以下4个角色:
(1)AbstractExpression(抽象表达式):在抽象表达式中声明了抽象解释操作,它是所有终结符和非终结符表达式的父类
(2)TerminalExpression(终结符表达式):它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例
(3)NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式完成
(4)Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通过它临时存储了需要解释的语句
解释器模式应用实例
1. 实例说明
某公司要开发一套简单的机器人控制程序,控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式,该表达式可以是简单的表达式也可以是复合表达式,每一个简单的表达式由移动方向(上:up、下:down、左:left、右:right),移动方式(移动:move、快速移动:run),移动距离(一个正整数),两个表达式可以通过与(and)连接形成复合表达式。控制指令示例:“down run 10 and left move 5”,则“向下快速移动10个单位再向左移动5个单位”;输入“up move 10”,则“向上移动10个单位”
2. 实例分析及类图
根据上述的需求描述用形式化语言表达的文法规则如下:
expression ::= direction action distance | composite //表达式 composite ::= expression ‘and‘ expression //复合表达式 direction ::= ‘up‘ | ‘down‘ | ‘left‘ | ‘right‘ //移动方向 action ::= ‘move‘ | ‘run‘ //移动方式 distance ::= an integer //移动距离
3. 代码实例
1 package designpatterns.interpreter; 2 3 public abstract class AbstractNode { 4 public abstract String interpret(); 5 }
1 package designpatterns.interpreter; 2 3 public class AndNode extends AbstractNode { 4 private AbstractNode left; //And的左表达式 5 private AbstractNode right; //And的右表达式 6 7 public AndNode(AbstractNode left, AbstractNode right) { 8 this.left = left; 9 this.right = right; 10 } 11 12 @Override 13 public String interpret() { 14 return left.interpret() + "再" + right.interpret(); 15 } 16 }
1 package designpatterns.interpreter; 2 3 public class SentenceNode extends AbstractNode { 4 private AbstractNode direction; 5 private AbstractNode action; 6 private AbstractNode distance; 7 8 public SentenceNode(AbstractNode direction, AbstractNode action, AbstractNode distance) { 9 super(); 10 this.direction = direction; 11 this.action = action; 12 this.distance = distance; 13 } 14 15 @Override 16 public String interpret() { 17 return direction.interpret() + action.interpret() + distance.interpret(); 18 } 19 20 }
1 package designpatterns.interpreter; 2 3 public class DirectionNode extends AbstractNode { 4 private String direction; 5 6 public DirectionNode(String direction) { 7 super(); 8 this.direction = direction; 9 } 10 11 @Override 12 public String interpret() { 13 if(direction.equalsIgnoreCase("up")){ 14 return "向上"; 15 } 16 else if(direction.equalsIgnoreCase("down")) { 17 return "向下"; 18 } 19 else if(direction.equalsIgnoreCase("left")) { 20 return "向左"; 21 } 22 else if(direction.equalsIgnoreCase("right")) { 23 return "向右"; 24 } 25 else{ 26 return "无效指令"; 27 } 28 } 29 }
1 package designpatterns.interpreter; 2 3 public class ActionNode extends AbstractNode { 4 private String action; 5 6 public ActionNode(String action) { 7 super(); 8 this.action = action; 9 } 10 11 @Override 12 public String interpret() { 13 if(action.equalsIgnoreCase("move")) { 14 return "移动"; 15 } 16 else if(action.equalsIgnoreCase("run")){ 17 return "快速移动"; 18 } 19 else{ 20 return "无效指令"; 21 } 22 } 23 }
1 package designpatterns.interpreter; 2 3 public class DistanceNode extends AbstractNode { 4 private String distance; 5 6 public DistanceNode(String distance) { 7 super(); 8 this.distance = distance; 9 } 10 11 @Override 12 public String interpret() { 13 return this.distance; 14 } 15 }
1 package designpatterns.interpreter; 2 3 import java.util.Stack; 4 5 public class InstructionHandler { 6 private AbstractNode node; 7 8 public void handle(String instruction){ 9 AbstractNode left = null,right = null; 10 AbstractNode action = null,direction = null,distance = null; 11 12 //声明一个栈对象用于存储抽象语法树 13 Stack<AbstractNode> stack = new Stack<AbstractNode>(); 14 //以空格分割指令字符串 15 String[] words = instruction.split(" "); 16 for(int i = 0;i < words.length;i++) { 17 /** 18 * 本实例采用栈的方式处理指令,如果遇到and,将其后的3个单词作为3个终结 19 * 表达式连成一个简单句子SentenceNode作为and的右表达式,而将从栈顶 20 * 弹出的表达式作为and的左表达式,最后将新的and表达式压入栈中 21 */ 22 if(words[i].equalsIgnoreCase("and")) { 23 left = (AbstractNode)stack.pop(); //弹出栈顶表达式作为左表达式 24 String word1 = words[++i]; 25 direction = new DirectionNode(word1); 26 String word2 = words[++i]; 27 action = new ActionNode(word2); 28 String word3 = words[++i]; 29 distance = new DistanceNode(word3); 30 31 right = new SentenceNode(direction, action, distance); 32 stack.push(new AndNode(left,right)); //将新表达式压入栈中 33 } 34 else{ 35 String word1 = words[i]; 36 direction = new DirectionNode(word1); 37 String word2 = words[++i]; 38 action = new ActionNode(word2); 39 String word3 = words[++i]; 40 distance = new DistanceNode(word3); 41 left = new SentenceNode(direction, action, distance); 42 stack.push(left); 43 } 44 } 45 this.node = (AbstractNode)stack.pop(); //将全部表达式从栈中弹出 46 } 47 48 public String output(){ 49 String result = node.interpret(); //解释表达式 50 return result; 51 } 52 }
1 package designpatterns.interpreter; 2 3 public class Client { 4 public static void main(String[] args) { 5 String instruction = "down run 10 and up move 20"; 6 InstructionHandler handler = new InstructionHandler(); 7 handler.handle(instruction); 8 9 String outputString; 10 outputString = handler.output(); 11 System.out.println(outputString); 12 } 13 }
4. 结果及分析
解释器模式优/缺与适用环境
1.解释器模式优点
(1)易于改变和扩展文法
(2)每一条文法规则都可以表示为一个类,因此可以方便的实现一个简单的语言
(3)实现文法较为容易
(4)增加新的解释表达式较为方便
2.解释器模式缺点
(1)对于复杂的文法难以维护
(2)执行效率低
3. 解释器模式的适用环境
(1)可以将一个需要解释执行的语言中的句子表达为一个抽象语法书
(2)一些重复出现的问题可以用一个简单的语言进行表达
(3)一个语言的文法比较简单
(4)执行效率不是关键问题