解释器模式-Interpreter
解释器模式提供了一种评估计算语言语法或表达式的方法。 这种类型的模式属于行为模式。 这种设计模式涉及实现一个表达式接口,它告诉解释一个指定的上下文。 此模式用于SQL解析,符号处理引擎等。
一、类图
解释器模式包含以下主要角色。
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
二、示例
//环境类:用于存储和操作需要解释的语句,在本实例中每一个需要解释的单词可以称为一个动作标记(Action Token)或命令 class Context { private StringTokenizer tokenizer; //StringTokenizer类,用于将字符串分解为更小的字符串标记(Token),默认情况下以空格作为分隔符 private String currentToken; //当前字符串标记 public Context(String text) { tokenizer = new StringTokenizer(text); //通过传入的指令字符串创建StringTokenizer对象 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) { if (!token.equals(currentToken)) { System.err.println("错误提示:" + currentToken + "解释错误!"); } nextToken(); } //如果当前的标记是一个数字,则返回对应的数值 public int currentNumber() { int number = 0; try { number = Integer.parseInt(currentToken); //将字符串转换为整数 } catch (NumberFormatException e) { System.err.println("错误提示:" + e); } return number; } } //抽象节点类:抽象表达式 abstract class Node { public abstract void interpret(Context text); //声明一个方法用于解释语句 public abstract void execute(); //声明一个方法用于执行标记对应的命令 } //表达式节点类:非终结符表达式 class ExpressionNode extends Node { private ArrayList<Node> list = new ArrayList<Node>(); //定义一个集合用于存储多条命令 public void interpret(Context context) { //循环处理Context中的标记 while (true) { //如果已经没有任何标记,则退出解释 if (context.currentToken() == null) { break; } //如果标记为END,则不解释END并结束本次解释过程,可以继续之后的解释 else if (context.currentToken().equals("END")) { context.skipToken("END"); break; } //如果为其他标记,则解释标记并将其加入命令集合 else { Node commandNode = new CommandNode(); commandNode.interpret(context); list.add(commandNode); } } } //循环执行命令集合中的每一条命令 public void execute() { Iterator iterator = list.iterator(); while (iterator.hasNext()) { ((Node) iterator.next()).execute(); } } } //语句命令节点类:非终结符表达式 class CommandNode extends Node { private Node node; public void interpret(Context context) { //处理LOOP循环命令 if (context.currentToken().equals("LOOP")) { node = new LoopCommandNode(); node.interpret(context); } //处理其他基本命令 else { node = new PrimitiveCommandNode(); node.interpret(context); } } public void execute() { node.execute(); } } //循环命令节点类:非终结符表达式 class LoopCommandNode extends Node { private int number; //循环次数 private Node commandNode; //循环语句中的表达式 //解释循环命令 public void interpret(Context context) { context.skipToken("LOOP"); number = context.currentNumber(); context.nextToken(); commandNode = new ExpressionNode(); //循环语句中的表达式 commandNode.interpret(context); } public void execute() { for (int i = 0; i < number; i++) commandNode.execute(); } } //基本命令节点类:终结符表达式 class PrimitiveCommandNode extends Node { private String name; private String text; //解释基本命令 public void interpret(Context context) { name = context.currentToken(); context.skipToken(name); if (!name.equals("PRINT") && !name.equals("BREAK") && !name.equals("SPACE")) { System.err.println("非法命令!"); } if (name.equals("PRINT")) { text = context.currentToken(); context.nextToken(); } } public void execute() { if (name.equals("PRINT")) System.out.print(text); else if (name.equals("SPACE")) System.out.print(" "); else if (name.equals("BREAK")) System.out.println(); } }
客户端测试代码
class Client { public static void main(String[] args) { String text = "LOOP 2 PRINT 杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉"; Context context = new Context(text); Node node = new ExpressionNode(); node.interpret(context); node.execute(); } }
在本实例代码中,环境类 Context 类似一个工具类,它提供了用于处理指令的方法,如 nextToken()、currentToken()、skipToken() 等,同时它存储了需要解释的指令并记录了每一次解释的当前标记(Token),而具体的解释过程交给表达式解释器类来处理。我们还可以将各种解释器类包含的公共方法移至环境类中,更好地实现这些方法的重用和扩展。
三、优缺点
优点:
解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
缺点:
每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
解释器模式采用递归调用方法,如果要排查一个语法错误,要一个一个断点的调试下去,会很麻烦。
解释器模式使用了大量的循环和递归,特别是用于解析复杂、冗长的语法时,效率会很低。
……更多设计模式的内容,可以访问Refactoring.Guru
不积跬步,无以至千里。不积小流,无以成江海!