【设计模式】行为型模式其三: 解释器模式

408 阅读7分钟

解释器模式

什么是解释器模式

给一个语言构建一个解释器,这个解释器可以解释语言中的语句。

解释器模式允许我们定义一个语言的语法,然后定义一个解释器来解释这个语法。

用大白话来说: 我有一句话(我是傻子),但是Java不理解我在说啥,然后呢,我在Java定义(定义为一个值X, 定义为y傻子定义为z )好了,它现在懂了,翻译为xyz

image.png

  • Java语言无法直接解释类似“1 + 2 + 3 – 4 + 1”这样的字符串
  • 定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言
  • 基于现有的编程语言 -> 面向对象编程语言 -> 解释器模式

定义

解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

解释器模式的定义:

在解释器模式的定义中所指的“语言”是使用规定格式和语法的代码

  • 是一种使用频率相对较低学习难度相对较大的设计模式,用于描述如何使用面向对象语言构成一个简单的语言解释器
  • 能够加深对面向对象思想的理解,并且理解编程语言中文法规则的解释过程

解释器模式结构

解释器模式包含以下4个角色:

  • AbstractExpression(抽象表达式)
  • TerminalExpression(终结符表达式)
  • NonterminalExpression(非终结符表达式)
  • Context(环境类)

终结符与非终结符

  1. 含义
  • 终结符表示最基本语法元素,无法进一步分解。
  • 非终结符表示更复杂的语法结构,可以由终结符和其他非终结符构成。
  1. 类型
  • 终结符包括关键字、变量名、常数、运算符等基本符号
  • 非终结符表示更高级的结构,如表达式、语句、函数等。
  1. 分析过程
  • 在解释器模式的解析过程中,首先解析终结符,获得最基本的元素。
  • 基于终结符,构建非终结符,形成更高级的结构。
  1. 解析方式
  • 终结符直接对应基本的语法元素,直接从输入中获取
  • 非终结符根据子非终结符或终结符的解析结果计算得出。

环境类

环境类Context

用于存储一些全局信息,一般包含一个HashMapArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,例如变量名与值的映射关系(key/value)等,用于在执行具体的解释操作时从中获取相关信息

可以在环境类中增加一些所有表达式解释器都共有的功能,以减轻解释器的职责
当系统无须提供全局公共信息时可以省略环境类,根据实际情况决定是否需要环境类

实例

某软件公司要开发一套机器人控制程序,在该机器人控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式(expression),该表达式可以是简单表达式也可以是复合表达式。

每一个简单表达式由移动方向(direction),移动方式(action)和移动距离(distance)三部分组成,其中,移动方向包括向上(up)、向下(down)、向左(left)、向右(right);

移动方式包括移动(move)和快速移动(run);移动距离为一个正整数。

两个表达式之间可以通过与(and)连接,形成复合(composite)表达式。

用户通过对图形化的设置界面进行操作可以创建一个机器人控制指令,机器人在收到指令后将按照指令的设置进行移动,

例如输入控制指令“up move 5”将“向上移动5个单位”;输入控制指令“down run 10 and left move 20”将“向下快速移动10个单位再向左移动20个单位”。

现使用解释器模式来设计该程序并模拟实现。

分析

既然有这么多指令,我们可以定义一个抽象结点类,然后,具体的结点去继承,完成自己的功能。

抽象结点

定义解释方法

//抽象表达式
public abstract class AbstractNode {
   public abstract String interpret();
}

具体结点(终结符)

//动作解释:终结符表达式

public 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 "无效指令";
      }
   }
}

//方向解释:终结符表达式

public 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 "无效指令";
      }
   }
}

//距离解释:终结符表达式

public class DistanceNode extends AbstractNode {
   private String distance;
   
   public DistanceNode(String distance) {
      this.distance = distance;
   }
   //距离表达式的解释操作
   public String interpret() {
      return this.distance;
   }  
}

具体结点,这里终结符的作用是:

  • 接收具体的命令
  • 真正完成命令解析。

具体结点(非终结符)

// 把终结符的类组合在一起,因此可以在类里面维护这几个变量,然后它的interpret()方法输出它组合的语句。

//简单句子解释:非终结符表达式
public 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();
   }  
}

// 下面的类用于把终结符组合,这里只能装填两条语句,当然如果想要继续装填语句,可以对该类反复new,就可以创建越来越多的语句。

//And解释:非终结符表达式
public 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();
   }
}

工具类(这里不是环境类,只是对语句进行处理的工具类)

//指令处理类:工具类
public class InstructionHandler {
   private AbstractNode node;
  
   public void handle(String instruction) {
      AbstractNode left = null, right = null;
      AbstractNode direction = null, action = null, distance = null;
      Stack<AbstractNode> stack = new Stack<AbstractNode>(); //声明一个栈对象用于存储抽象语法树
      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;
   }
}

客户端调用

public class Client {
   public static void main(String args[]) {
      String instruction = "down run 10 and left move 20 and up move 40";
      InstructionHandler handler = new InstructionHandler();
      handler.handle(instruction);
      String outString;
      outString = handler.output();
      System.out.println(outString);
   }
}

类的总结

(1) AbstractNode:抽象结点类,充当抽象表达式角色
(2) AndNode:And结点类,充当非终结符表达式角色
(3) SentenceNode:简单句子结点类,充当非终结符表达式角色
(4) DirectionNode:方向结点类,充当终结符表达式角色
(5) ActionNode:动作结点类,充当终结符表达式角色
(6) DistanceNode:距离结点类,充当终结符表达式角色
(7) InstructionHandler:指令处理类,工具类
(8) Client:客户端测试类

输出及分析

向下快速移动10再向左移动20再向上移动40

分析:

  1. 这里是先在客户端构造语句,将语句传入语句处理器
  2. 处理器处理语句
  3. 将语句按空格分割,然后读取前三个,将其组装为语句,并将其压入stack
  4. 如果读取到and,将栈里的语句取出,然后再读取三个单词组装为语句,并且组装为新的语句。
  5. 最后调用语句interpret()方法, 这样的话,将语句解析依次解释。

image.png

模式优缺点

模式优点

  • 易于改变和扩展文法
  • 可以方便地实现一个简单的语言
  • 实现文法较为容易(有自动生成工具)
  • 增加新的解释表达式较为方便

模式缺点

  • 对于复杂文法难以维护
  • 执行效率较低

适用环境

  • 可以将一个需要解释执行的语言中的句子表示为一棵抽象语法树
  • 一些重复出现的问题可以用一种简单的语言来进行表达
  • 一个语言的文法较为简单
  • 执行效率不是关键问题