解释器模式(INTERPRETER),通过面向对象的方式构造语言解释器,并使用该解释器按照一定的文法解释语言中的句子,属于类行为模式。记得大学时候参加过机器人足球比赛,通过面板输入指令"up move 10 and left run 8",足球机器人就会执行相应的动作。将这些指令组合就能完成高难度的射门,躲避,可谓智能也!下面将通过机器人指令解释来阐述解释器模式的强大。
一、使用场景
1、当有一个语言需要解释执行,并能从该语言的句子中抽象出语法树时,比如机器人足球比赛的指令,也就up,down,move等有限的基础操作码。
2、抽象出的文法要简单,不至于导致文法的类层次过于庞大而无法管理。对于复杂的文法可以使用语法分析程序生成器,这样无需构造抽象语法树,节省时间和空间。
3、效率不是关键的情况。高效的解释器通常不采用直接解释抽象语法树实现,而是通过转换其他形式实现,比如状态机。
二、UML图
三、Java实现
package study.patterns.interpreter; import java.util.Stack; /** * 解释器模式,用面向对象的方法构造简单的语言解释器。 * 示例:使用解释器模式实现机器人指令的解析。 * 指令文法: * expression ::= direction action distance | composite //表达式 * composite ::= expression ‘and‘ expression //复合表达式 * direction ::= ‘up‘ | ‘down‘ | ‘left‘ | ‘right‘ //移动方向 * action ::= ‘move‘ | ‘run‘ //移动方式 * distance ::= an integer //移动距离 * 终结符表达式:direction、action和distance,语言的最小组成单位,不能再拆分。 * 非终结符表达式:expression和composite,完整的句子,包含一系列终结符或非终结符。 * 抽象语法树:将指令按照语法抽象为树形结构(可以使用栈模拟),是简单句子解释的重要步骤。 * @author qbg */ public class InterpreterPattern { public static void main(String[] args) { String sentence = "up move 8 and right run 10 and left move 6";//输入指令要严格按照文法 InstructionHandler handler = new InstructionHandler(); handler.handle(sentence);//抽象语法树 System.out.println(handler.interpret());//解释输入指令并输出 } } /** * 抽象表达式,可以采用接口或抽象类实现 */ interface INode{ public String interpret(); } /** * And解释,用于将两个表达式进行and操作,非终止表达式 */ class AndNode implements INode{ private INode left;//and的左表达式 private INode right;//and的右表达式 public AndNode(INode left,INode right){ this.left = left; this.right = right; } /** * And表达式解释操作,两个操作的联合 */ @Override public String interpret() { return left.interpret()+"再"+right.interpret(); } } /** * 简单的句子解释,由基本的原子解释组成,句子解释可以组合成复杂的句子 */ class SentenceNode implements INode{ private INode direction;//方向解释 private INode action;//动作解释 private INode distance;//距离解释 public SentenceNode(INode direction,INode action,INode distance){ this.direction = direction; this.action = action; this.distance = distance; } /** * 简单句子解释逻辑,将原子解释按规则解释就行(必须按文法规则,否则解释出错) */ @Override public String interpret() { return direction.interpret()+action.interpret()+distance.interpret(); } } /** * 方向解释,解释方向操作码(up,down,left,right)并执行相应操作,终结符表达式。 */ class DirectionNode implements INode{ private String direction; public DirectionNode(String direction){ this.direction = direction; } /** * 方向表达式的解释操作 */ @Override 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 "无效操作码"; } } } /** * 动作解释,解释操作码(move,run)并执行相应操作,终结符表达式 */ class ActionNode implements INode{ private String action; public ActionNode(String action){ this.action = action; } /** * 动作表达式的解释操作 */ @Override public String interpret() { if(action.equalsIgnoreCase("move")){ return "移动"; }else if(action.equalsIgnoreCase("run")){ return "快速移动"; }else{ return "无效操作码"; } } } /** * 距离解释,用于解释移动的距离,数字,终结符表达式。 */ class DistanceNode implements INode{ private String distance; public DistanceNode(String distance){ this.distance = distance; } @Override public String interpret() { return distance; } } /** * 指令处理类,用于抽象语法树,工具类. */ class InstructionHandler{ private INode node; public void handle(String sentence){ INode left=null,right=null,direction=null,action=null,distance=null; Stack<INode> stack = new Stack<INode>(); //用stack存储抽象语法树 String[] words = sentence.split(" ");//以空格分隔操作码字符串 for(int i=0; i<words.length; i++){ //1、如果遇到“and”,则将后续的三个单词作为终结符练成一个简单的句子,并将其作为“and”的右表达式。 //2、将stack栈顶弹出的表达式作为“and”的左表达式,最后将新的“and”表达式压入stack。 if(words[i].equalsIgnoreCase("and")){ left = stack.pop();//将从stack弹出的作为左表达式 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));//将新and表达式入栈 } //从头开始解释的直接将三个单词组成简单句子,并压入栈中。 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 = (INode) stack.pop();//将最终的表达式从栈中弹出(其实就是整个抽象语法树) } public String interpret(){ return node.interpret();//解释抽象语法树 } }
运行结果:
向上移动8再向右快速移动10再向左移动6
四、模式优缺点
优点:
1、易于改变和扩展文法。由于解释器模式采用类来表示文法规则,可以通过继承来改变或扩展原有文法。
2、易于实现文法。定义抽象语法树中的各个节点的类实现相似,易于直接编写,也方便采用编译器或语法分析生成器自动生成。
2、方便添加新的解释表达式。你可以在表达式类上定义新的操作以支持优美打印或表达式类型检查,优化,代码生成等,通常结合Visitor模式更佳。
缺点:
1、复杂的文法难以维护。复杂的文法会导致类层次的庞大,从而难以管理。此时可以采用编译器或语法分析程序来实现语法的解释。