1、Interpreter 解释器模式
解释器模式是一种使用频率相对较低但学习难度较大的设计模式,它用于描述如何使用面向对象语言构成一个简单的语言解释器。在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言拥有自己的表达式和结构,即文法规则,这些问题的实例将对应为该语言中的句子。此时,可以使用解释器模式来设计这种新的语言。对解释器模式的学习能够加深我们对面向对象思想的理解,并且掌握编程语言中文法规则的解释过程。
解释器模式定义如下:解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的。
比如:一个公交系统,当输入北京的老人、北京的儿童、北京的白领等时,会根据不同的输入来判断是否需要收费以及怎么收费。本文就以公交系统来演示这个模式。
2、示例代码
1、使用解释器模式来设计和实现机器人控制程序
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
2、增加环境类实例
可以根据输入的指令在字符界面中输出一些格式化内容,例如输入“LOOP 2 PRINT杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT郭靖 SPACE SPACE PRINT 黄蓉”,将输出如下结果:
杨过 小龙女
杨过 小龙女
郭靖 黄蓉
Context充当环境角色,Node充当抽象表达式角色,ExpressionNode、CommandNode和LoopCommandNode充当非终结符表达式角色,PrimitiveCommandNode充当终结符表达式角色。完整代码如下所示:
import java.util.*;
//环境类:用于存储和操作需要解释的语句,在本实例中每一个需要解释的单词可以称为一个动作标记(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();
}
}
在本实例代码中,环境类Context类似一个工具类,它提供了用于处理指令的方法,如nextToken()、currentToken()、skipToken()等,同时它存储了需要解释的指令并记录了每一次解释的当前标记(Token),而具体的解释过程交给表达式解释器类来处理。我们还可以将各种解释器类包含的公共方法移至环境类中,更好地实现这些方法的重用和扩展。
针对本实例代码,我们编写如下客户端测试代码:
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();
}
}
编译并运行程序,输出结果如下:
杨过 小龙女
杨过 小龙女
郭靖 黄蓉
思考
预测指令“LOOP 2 LOOP 2 PRINT杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉 BREAK END”的输出结果。
3、Interpreter 类图
在解释器模式结构图中包含如下几个角色:
● AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
● TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。
● NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
● Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。
4、小结
解释器模式为自定义语言的设计和实现提供了一种解决方案,它用于定义一组文法规则并通过这组文法规则来解释语言中的句子。虽然解释器模式的使用频率不是特别高,但是它在正则表达式、XML文档解释等领域还是得到了广泛使用。与解释器模式类似,目前还诞生了很多基于抽象语法树的源代码处理工具,例如Eclipse中的Eclipse AST,它可以用于表示Java语言的语法结构,用户可以通过扩展其功能,创建自己的文法规则。
优点: 1、可扩展性比较好,灵活。2、增加了新的解释表达式的方式。3、易于实现简单文法。
缺点: 1、可利用场景比较少。2、对于复杂的文法比较难维护。3、解释器模式会引起类膨胀。4、解释器模式采用递归调用方法。
使用场景: 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。2、一些重复出现的问题可以用一种简单的语言来进行表达。3、一个简单语法需要解释的场景。
公众号:发哥讲
这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。
如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈~
● 扫码关注我们
据说看到好文章不推荐的人,服务器容易宕机!