设计模式之解释器模式(下)

3)Context的作用
1.概述

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

2.案例-增加 Context

a)结构图

在这里插入图片描述

Context充当环境角色,Node充当抽象表达式角色,ExpressionNode、CommandNode和LoopCommandNode充当非终结符表达式角色,PrimitiveCommandNode充当终结符表达式角色。

b)代码实现

抽象表达式

//抽象节点类:抽象表达式
abstract class Node {
    public abstract void interpret(Context text); //声明一个方法用于解释语句
    public abstract void execute(); //声明一个方法用于执行标记对应的命令
}

非终结符表达式

import java.util.ArrayList;
import java.util.Iterator;

//表达式节点类:非终结符表达式
class ExpressionNode extends Node {
    private ArrayList<Node> list = new ArrayList<Node>(); //定义一个集合用于存储多条命令

    public void interpret(Context context) {
        //循环处理Context中的标记
        while (true) {
            //如果已经没有任何标记,则退出解释
            if (context.currentToken() == null) {
                break;
            } else if (context.currentToken().equals("END")) {
                //如果标记为END,则不解释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() {
        switch (name) {
            case "PRINT":
                System.out.print(text);
                break;
            case "SPACE":
                System.out.print(" ");
                break;
            case "BREAK":
                System.out.println();
                break;
        }
    }
}

环境类

import java.util.StringTokenizer;

//环境类:用于存储和操作需要解释的语句,在本实例中每一个需要解释的单词可以称为一个动作标记(Action Token)或命令
public class Context {
    //StringTokenizer类,用于将字符串分解为更小的字符串标记(Token),默认情况下以空格作为分隔符
    private StringTokenizer tokenizer;
    //当前字符串标记
    private String currentToken; 

    public Context(String text) {
        //通过传入的指令字符串创建StringTokenizer对象
        tokenizer = new StringTokenizer(text); 
        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;
    }
}

客户端类

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

在本实例中,环境类Context类似一个工具类,它提供了用于处理指令的方法,如nextToken()、currentToken()、skipToken()等,同时它存储了需要解释的指令并记录了每一次解释的当前标记(Token),而具体的解释过程交给表达式解释器类来处理。

4)总结
1.优点

解释器模式的主要优点如下:

  • 易于改变和扩展文法,由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。

  • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

  • 实现文法较为容易,在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。

  • 增加新的解释表达式较为方便,如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。

2.缺点
  • 对于复杂文法难以维护,在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。

  • 执行效率较低,由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

3.适用场景
  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。

  • 一些重复出现的问题可以用一种简单的语言来进行表达。

  • 一个语言的文法较为简单。

  • 执行效率不是关键问题。

相关推荐

  1. 【前端设计模式解释模式

    2024-04-10 09:56:03       33 阅读
  2. 软件设计模式解释模式

    2024-04-10 09:56:03       16 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-10 09:56:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-10 09:56:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-10 09:56:03       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-10 09:56:03       20 阅读

热门阅读

  1. 对单片机的一点理解

    2024-04-10 09:56:03       13 阅读
  2. 前端开发语言有哪些?

    2024-04-10 09:56:03       14 阅读
  3. 关于IP地址发展历程的详细探讨

    2024-04-10 09:56:03       12 阅读
  4. 如何在debian12.5上安装snap和docker

    2024-04-10 09:56:03       16 阅读
  5. OneFlow深度学习框架介绍

    2024-04-10 09:56:03       17 阅读
  6. 常用工具之docker

    2024-04-10 09:56:03       13 阅读
  7. OneFlow深度学习框架介绍

    2024-04-10 09:56:03       16 阅读
  8. springboot 整合 websocket

    2024-04-10 09:56:03       12 阅读
  9. 【大数据面试题】023 Spark RDD 是什么?

    2024-04-10 09:56:03       15 阅读
  10. List接口(2)| Vector

    2024-04-10 09:56:03       12 阅读