23、Interpreter 解释器模式

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 类图

23、Interpreter 解释器模式

在解释器模式结构图中包含如下几个角色:

● AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。

● TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。

● NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。

● Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

4、小结

解释器模式为自定义语言的设计和实现提供了一种解决方案,它用于定义一组文法规则并通过这组文法规则来解释语言中的句子。虽然解释器模式的使用频率不是特别高,但是它在正则表达式、XML文档解释等领域还是得到了广泛使用。与解释器模式类似,目前还诞生了很多基于抽象语法树的源代码处理工具,例如Eclipse中的Eclipse AST,它可以用于表示Java语言的语法结构,用户可以通过扩展其功能,创建自己的文法规则。

优点: 1、可扩展性比较好,灵活。2、增加了新的解释表达式的方式。3、易于实现简单文法。

缺点: 1、可利用场景比较少。2、对于复杂的文法比较难维护。3、解释器模式会引起类膨胀。4、解释器模式采用递归调用方法。

使用场景: 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。2、一些重复出现的问题可以用一种简单的语言来进行表达。3、一个简单语法需要解释的场景。

公众号发哥讲

这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。

23、Interpreter 解释器模式

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈~

● 扫码关注我们

据说看到好文章不推荐的人,服务器容易宕机!

 

 

上一篇:android 4.0后不允许屏蔽掉home键


下一篇:dedecms list 实现noflag