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、一个简单语法需要解释的场景。

公众号发哥讲

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

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

● 扫码关注我们

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

 

 

23、Interpreter 解释器模式的更多相关文章

  1. 二十三、Interpreter 解释器模式

    设计: 代码清单: Node public abstract class Node { public abstract void parse(Context context) throws Parse ...

  2. 设计模式15:Interpreter 解释器模式(行为型模式)

    Interpreter 解释器模式(行为型模式) 动机(Motivation) 在软件构建过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变 ...

  3. 面向对象设计模式之Interpreter解释器模式(行为型)

    动机:在软件构建过程中 ,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化.在这种情况下,将特定领域的问题表达为某种语法规则的句子,然后构建一个 ...

  4. interpreter(解释器模式)

    一.引子 其实没有什么好的例子引入解释器模式,因为它描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发编译器中:在实际应用中,我们可能很少碰到去构造一个语言的文法的情况. 虽然你几乎用 ...

  5. 设计模式(15)--Interpreter(解释器模式)--行为型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义: 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解 ...

  6. 设计模式之解释器模式(Interpreter)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  7. JAVA 23种开发模式详解(代码举例)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  8. 设计模式的征途—23.解释器(Interpreter)模式

    虽然目前计算机编程语言有好几百种,但有时人们还是希望用一些简单的语言来实现特定的操作,只需要向计算机输入一个句子或文件,就能按照预定的文法规则来对句子或文件进行解释.例如,我们想要只输入一个加法/减法 ...

  9. 23种设计模式之解释器模式(Interpreter)

    解释器模式属于类的行为型模式,描述了如何为语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子,这里的“语言”是使用规定格式和语法的代码.解释器模式主要用在编译器中,在应用系统开发中很少 ...

随机推荐

  1. easyUI传递参数

    #======================JSP=====================================                <div class="l ...

  2. webStrom中配置nodejs

    1.安装nodejs 下载地址:node.js:https://nodejs.org/download/ 按照提示安装即可 2.安装WebStrom 按照提示安装即可 下载地址:webstorm:ht ...

  3. maven自动创建项目目录骨架

    方法一: 1:打开命令窗口 在要创建项目的路径下按住H2SIT ,然后点击右键  ,在弹出菜单中选择 在此处打开命令窗口(W) 2:目录创建 方法二:

  4. 第八章:理解Window和WindowManager

    Window表示一个窗口的概念. Window是一个抽象类,它的具体实现是PhoneWindow, WindowManager是外界访问Window的入口,Window的具体实现位于WindowMan ...

  5. web自动化 -- Keys(键盘操作)

    Keys没啥好讲的 语法:Keys.CONTRAL    等等类似. 下方就是可以  Keys.   跟的键 那些 \ue000  就是对应的  Windows系统中的键盘码,pywin32 也一样的 ...

  6. [leetcode/lintcode 题解] 谷歌面试题:找出有向图中的弱连通分量

    请找出有向图中弱连通分量.图中的每个节点包含 1 个标签和1 个相邻节点列表.(有向图的弱连通分量是任意两点均有有向边相连的极大子图) 将连通分量内的元素升序排列. 在线评测地址:https://ww ...

  7. 究竟什么时候该使用MQ?

    究竟什么时候该使用MQ? 原创: 58沈剑 架构师之路  昨天 任何脱离业务的组件引入都是耍流氓.引入一个组件,最先该解答的问题是,此组件解决什么问题. MQ,互联网技术体系中一个常见组件,究竟什么时 ...

  8. python xpath的基本用法

    XPath是一种在XML文档中查找信息的语言,使用路径表达式在XML文档中进行导航.学习XPath需要对XML和HTML有基本的了解. 在XPath中,有七种类型的节点:文档(根)节点.元素.属性.文 ...

  9. Centos 7下编译安装Nginx

    一.下载源代码 百度云网盘下载地址:https://pan.baidu.com/s/19MQODvofRNnLV9hdAT-R6w 提取码:zi0u 二.安装依赖及插件 yum -y install ...

  10. Android系统前台进程,可见进程,服务进程,后台进程,空进程的优先级排序

    1.前台进程 前台进程是Android中最重要的进程,在最后被销毁,是目前正在屏幕上显示的进程和一些系统进程,也就是和用户正在交互的进程. 2.可见进程 可见进程指部分程序界面能够被用户看见,却不在前 ...