设计模式之解释器模式的魅力:让代码读懂你的语言

目录

一、什么是解释器模式

二、解释器模式的应用场景

三、解释器模式的优缺点

3.1. 优点

3.2. 缺点

四、解释器模式示例

4.1. 问题描述

4.2. 问题分析

4.3. 代码实现

4.4. 优化方向

五、总结


一、什么是解释器模式

    解释器模式(Interpreter pattern)是一种行为型(Behavioral Pattern)的设计模式,用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。该模式将句子表示为一个抽象语法树,每个节点代表一个语法规则,通过递归地解释这些节点来实现对句子的解释。

    解释器模式主要包含以下五类角色:

  • 抽象表达式(Abstract Expression):定义了解释器的接口,包括一个解释方法,并根据文法规则解释表达式。
  • 终结符表达式(Terminal Expression):实现抽象表达式接口,代表文法中的终结符。
  • 非终结符表达式(Non-Terminal Expression):实现抽象表达式接口,代表文法中的非终结符,通常包含其他表达式。
  • 上下文(Context):包含需要被解释的信息或状态,解释器通过上下文来执行解释操作。
  • 客户端(Client):创建和配置具体的解释器表达式,构建解释器的抽象语法树,并调用解释器的解释方法来解释表达式。

    解释器模式的结构如下所示:

二、解释器模式的应用场景

    解释器模式适用于需要解释和执行特定领域语言的场景,常见的适合应用解释器模式的场景包括但不限于:

  • 编程语言解释器:解释器模式最经典的应用就是编程语言的解释器。例如,Python、JavaScript等编程语言都使用解释器来解释和执行代码。
  • 数学表达式解析:解释器模式可以用于解析和计算数学表达式。例如,我们可以使用解释器模式来解析并计算一个复杂的数学公式。
  • 查询语言解析:解释器模式可以用于解析和执行查询语言。例如,数据库查询语言的解析和执行就可以使用解释器模式来实现。

三、解释器模式的优缺点

3.1. 优点

    1)可扩展性:增加新的解释表达式比较方便,扩展时不需要修改原有的逻辑,符合开闭原则。

    2)灵活性:改变或扩展文法都比较容易。

    3)实现文法容易:语法树中的每个表达式节点类都是相似的,易于实现。

3.2. 缺点

    1)类膨胀:每个语法都要产生一个非终结符表达式,可能导致大量类文件。

    2)性能问题:递归解释语法可能导致效率低下。

四、解释器模式示例

4.1. 问题描述

    为了更好地理解解释器模式的应用,我们将以一个简单的例子来演示它的实现过程。假设我们需要设计一个简单的计算器,能够解析并计算用户输入的包含加减乘除四种计算的数学表达式,其中只存在数字和加(+)、减(-)、乘(*)、除(/)四种符号,每种元素之间以空格区分,并且四种运算符号的运算顺序总是从左到右的(即不限定一定要先算乘除后算加减)。

4.2. 问题分析

    我们以一个简单的表达式“2 + 3 * 4 - 5 / 3”为例,其语法树可以表示为:

    其中包含一种终结符表达式(数字表达式)以及四种非终结符表达式(加减乘除四种表达式)。则我们可以:

    1)定义一个抽象表达式接口(AbstractExpression),并包含一个解释方法interpret()用于返回解释结果。

    2)定义一个数字表达式类(NumberExpression),它的内部维护了一个数字对象,并且其实现的interpret()方法直接返回这个数字对象。

    3)为加减乘除四种运算规则分别定义相应的表达式类(AddExpression,SubExpression,MulExpression,DivExpression),它们的内部都维护了AbstractExpression类型的运算的左值(left)和右值(right),并分别按照各自的规则实现interpret()方法返回运算结果。

    4)由于此计算器只需要从左往右顺序地计算下来就可以了,所以表达式中并不需要知道上下文环境,那么我们可以省略定义上下文类(Context),而直接定义客户端类(Client)用于对外提供运算接口(execute(String exp)),将接收到的表达式字符串参数转换成使用解释器对象描述的语法树,并解释并输出解释结果。

4.3. 代码实现

    经过上一步的分析之后,下面我们通过代码来实现它:

interface AbstractExpression{
    public int interpret();
}
​
class NumberExpression implements AbstractExpression{
    private int number;
​
    NumberExpression(int number) {
        this.number = number;
    }
​
    @Override
    public int interpret() {
        return number;
    }
}
//加法
class AddExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    AddExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}
//减法
class SubExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    SubExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}
//乘法
class MulExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    MulExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() * right.interpret();
    }
}
//除法
class DivExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    DivExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() / right.interpret();
    }
}
​
class Cient{
    public int execute(String exp){
        String[] elements = exp.split(" ");
        if(elements.length % 2 == 0){
            throw new RuntimeException("表达式错误");
        }
        AbstractExpression expression = new NumberExpression(Integer.parseInt(elements[0]));
        for (int i = 1; i < elements.length-1; i+=2) {
            String symbol =  elements[i];
            int num;
            try{
                num = Integer.parseInt(elements[i+1]);
            }catch (NumberFormatException e){
                throw new RuntimeException("表达式错误");
            }
            if (symbol.equals("+")){
                expression = new AddExpression(expression, new NumberExpression(num));
            }else if (symbol.equals("-")){
                expression = new SubExpression(expression, new NumberExpression(num));
            }else if (symbol.equals("*")){
                expression = new MulExpression(expression, new NumberExpression(num));
            }else if (symbol.equals("/")){
                expression = new DivExpression(expression, new NumberExpression(num));
            }else {
                System.out.println("无效的符号");
            }
        }
        return expression.interpret();
    }
}

    接下来编写测试类:

    public static void main(String[] args) {
        Cient cient = new Cient();
        System.out.println(cient.execute("2 + 3 * 4 - 5 / 3"));
        System.out.println(cient.execute("5 * 2 + 9 - 1 / 6"));
        System.out.println(cient.execute("5 / 5 + 1 / 1 * 10 - 1"));
    }

    测试结果为:

        测试通过! 

4.4. 优化方向

    作为一个开发人员,完成一段代码功能之后当然要不断反思自己的代码还有哪些不足,就以上示例而言,针对使用者有可能的千奇百怪的输入来说,首先这段代码的异常捕获和提示内容就是不足的,当然,仅仅用来作为说明解释器模式的实例来讲,我们的目的已经达到了,所以这里不再多做补充(不是因为懒~)。另外,如果我们学习过其他设计模式,我们可以将解释器模式和其他设计模式结合使用,而达到进一步优化的目的,比如我们可以用组合模式来构建语法树,用工厂模式来创建不同类型的表达式,而如何通过代码来实现它们,就要涉及其他设计模式的说明了,本文暂不做赘述(那肯定也不是因为懒~)

五、总结

    解释器模式是一种强大而有趣的设计模式,它可以帮助我们简化复杂问题的处理过程。通过定义文法规则、创建抽象语法树和实现解释器,我们可以轻松地解释和执行特定语言的句子。希望本文对你理解解释器模式有所帮助,能够在实际开发中灵活运用!

最近更新

  1. TCP协议是安全的吗?

    2024-03-31 03:06:02       17 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-31 03:06:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-31 03:06:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-31 03:06:02       18 阅读

热门阅读

  1. MySQL5.7源码分析--连接

    2024-03-31 03:06:02       19 阅读
  2. 花钱的艺术:消费和投资如何分配

    2024-03-31 03:06:02       16 阅读
  3. WebAPI调优

    2024-03-31 03:06:02       15 阅读
  4. LeetCode-热题100:208. 实现 Trie (前缀树)

    2024-03-31 03:06:02       19 阅读
  5. Vuex工作机制

    2024-03-31 03:06:02       17 阅读
  6. 2024 蓝桥打卡Day27

    2024-03-31 03:06:02       17 阅读
  7. PTA - 转换函数使用

    2024-03-31 03:06:02       23 阅读
  8. 如何做一个知识博主? 思考 + 提问

    2024-03-31 03:06:02       19 阅读
  9. vue 预览v-html 富文本里的图片 使用vant

    2024-03-31 03:06:02       15 阅读
  10. 21.59万

    21.59万

    2024-03-31 03:06:02      18 阅读