24 【行为型模式】解释器模式

定义

  解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。

结构图


要素
● AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
● TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。
● NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
● Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

abstract class AbstractExpression {
       public  abstract void interpret(Context ctx);
}

class TerminalExpression extends  AbstractExpression {
       public  void interpret(Context ctx) {
              //终结符表达式的解释操作
       }
}

class NonterminalExpression extends  AbstractExpression {
       private  AbstractExpression left;
       private  AbstractExpression right;

       public  NonterminalExpression(AbstractExpression left,AbstractExpression right) {
              this.left=left;
              this.right=right;
       }

       public void interpret(Context ctx) {
              //递归调用每一个组成部分的interpret()方法
              //在递归调用时指定组成部分的连接方式,即非终结符的功能
       }     
}


class NonterminalExpression extends  AbstractExpression {
       private  AbstractExpression left;
       private  AbstractExpression right;

       public  NonterminalExpression(AbstractExpression left,AbstractExpression right) {
              this.left=left;
              this.right=right;
       }

       public void interpret(Context ctx) {
              //递归调用每一个组成部分的interpret()方法
              //在递归调用时指定组成部分的连接方式,即非终结符的功能
       }     
}

示例

  Sunny软件公司欲为某玩具公司开发一套机器人控制程序,在该机器人控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式(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个单位”。
抽象语法树


//注:本实例对机器人控制指令的输出结果进行模拟,将英文指令翻译为中文指令,实际情况是调用不同的控制程序进行机器人的控制,包括对移动方向、方式和距离的控制等  
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;  
    }  
}  

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);  
    }  
} 

核心:

  • 拆解+递归
  • 环境类Context用于存储解释器之外的一些全局信息,它通常作为参数被传递到所有表达式的解释方法interpret()中,可以在Context对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据,此外还可以在Context中增加一些所有表达式解释器都共有的功能,减轻解释器的职责

总结

  • 主要优点:
    (1) 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
    (2) 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
    (3) 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
    (4) 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。

主要缺点:*
(1) 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
(2) 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

适用场景

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

推荐阅读更多精彩内容