设计模式之【解释器模式】,用语言定义一门语言

news2024/12/28 19:05:00

文章目录

  • 一、什么是解释器模式
    • 1、常见文法(语法)规则
    • 2、抽象语法树
    • 3、解释器模式的使用场景
    • 4、解释器模式的四大角色
    • 5、解释器模式优缺点
  • 二、实例
    • 1、解释器模式的一般写法
    • 2、数学表达式案例
  • 三、源码中的解释器模式
    • 1、Pattern正则
    • 2、Spring的ExpressionParser

一、什么是解释器模式

解释器模式(Interpreter Pattern)是指给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。是一种按照规定的语法(文法)进行解析的模式,属于行为型模式。

就比如编译器可以将源码编译解释为机器码,让CPU能进行识别并运行。解释器模式的作用其实与编译器一样,都是将一些固定的文法(即语法)进行解释,构建出一个解释句子的解释器。简单理解,解释器是一个简单语法分析工具,它可以识别句子语义,分离终结符号和非终结符号,提取出需要的信息,让我们能针对不同的信息做出相应的处理。其核心思想是识别文法,构建解释。

1、常见文法(语法)规则

比如,加减乘除等运算,5+3/6+2*8。
比如,摩尔斯电码。
再比如正则表达式,el表达式,OGNL表达式等等。

2、抽象语法树

在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和±符号组成的合法序列,“1+3-2” 就是这种语言的句子。

在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

用树形来表示符合文法规则的句子。
在这里插入图片描述

3、解释器模式的使用场景

我们的程序中,如果存在一种特定类型的问题,该类型问题涉及多个不同实例,但是具备固定文法描述,那么可以使用解释器模式对该类型问题进行解释,分离出需要的信息,根据获取的信息做出相应的处理。简而言之,对于一些固定文法构建一个解释橘子的解释器。

解释器模式适用于以下应用场景:

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

4、解释器模式的四大角色

在这里插入图片描述
解释器模式包含以下主要角色。

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret(),交由子类进行解释。
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。比如公式R=R1+R2,R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。通常一个解释器模式只有一个终结符表达式,但有多个实例,对应不同的终结符(R1和R2)。
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中, + 就是非终结符,解析 + 的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。比如R=R1+R2,给R1赋值100,给R2赋值200,这些信息需要存放到环境中。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

5、解释器模式优缺点

优点:

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

缺点:

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

二、实例

1、解释器模式的一般写法

// 抽象表达式
public interface IExpression {
    // 对表达式进行解释
    Object interpret(Context context);
}
// 非终结符表达式
public class NonterminalExpression implements IExpression {
    private IExpression [] expressions;

    public NonterminalExpression(IExpression... expressions) {
        // 每个非终结符表达式都会对其他表达式产生依赖
        this.expressions = expressions;
    }


    public Object interpret(Context context) {
        // 进行文法处理
        context.put("","");
        return null;
    }
}
// 终结符表达式
public class TerminalExpression implements IExpression {

    private Object value;

    public Object interpret(Context context) {
        // 实现文法中与终结符有关的操作
        context.put("","");
        return null;
    }

}
// 上下文环境类
public class Context extends HashMap {

}
public class Test {
    public static void main(String[] args) {
        try {
            Context context = new Context();
            // 定义一个语法容器,用于存储一个具体表达式
            Stack<IExpression> stack = new Stack<IExpression>();
//            for (; ; ) {
//                // 进行语法解析,并产生递归调用
//            }
            // 获取得到最终的解析表达式:完整语法树
            IExpression expression = stack.pop();
            // 递归调用获取结果
            expression.interpret(context);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

2、数学表达式案例

我们使用解释器模式来实现一个包含加减乘除的数学表达式。

// 抽象表达式角色
public interface IArithmeticInterpreter {
    int interpret();
}
// 终结表达式角色抽象
public abstract class Interpreter implements IArithmeticInterpreter {

    protected IArithmeticInterpreter left;
    protected IArithmeticInterpreter right;

    public Interpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
        this.left = left;
        this.right = right;
    }
}
// 非终结表达式
public class AddInterpreter extends Interpreter {

    public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
        super(left, right);
    }

    public int interpret() {
        return this.left.interpret() + this.right.interpret();
    }
}
public class SubInterpreter extends Interpreter {
    public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
        super(left, right);
    }

    public int interpret() {
        return this.left.interpret() - this.right.interpret();
    }
}
public class MultiInterpreter extends Interpreter {

    public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
        super(left,right);
    }

    public int interpret() {
        return this.left.interpret() * this.right.interpret();
    }

}
public class DivInterpreter extends Interpreter {

    public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
        super(left,right);
    }

    public int interpret() {
        return this.left.interpret() / this.right.interpret();
    }

}

// 数字表达式
public class NumInterpreter implements IArithmeticInterpreter {
    private int value;

    public NumInterpreter(int value) {
        this.value = value;
    }

    public int interpret() {
        return this.value;
    }
}
import java.util.Stack;
// 计算器
public class GPCalculator {
    private Stack<IArithmeticInterpreter> stack = new Stack<IArithmeticInterpreter>();

    public GPCalculator(String expression) {
        this.parse(expression);
    }

    private void parse(String expression) {
        String [] elements = expression.split(" ");
        IArithmeticInterpreter leftExpr, rightExpr;

        for (int i = 0; i < elements.length ; i++) {
            String operator = elements[i];
            if (OperatorUtil.isOperator(operator)){
                leftExpr = this.stack.pop();
                rightExpr = new NumInterpreter(Integer.valueOf(elements[++i]));
                System.out.println("出栈: " + leftExpr.interpret() + " 和 " + rightExpr.interpret());
                this.stack.push(OperatorUtil.getInterpreter(leftExpr, rightExpr,operator));
                System.out.println("应用运算符: " + operator);
            }
            else{
                NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i]));
                this.stack.push(numInterpreter);
                System.out.println("入栈: " + numInterpreter.interpret());
            }
        }
    }

    public int calculate() {
        return this.stack.pop().interpret();
    }
}
// 工具类
public class OperatorUtil {

    public static boolean isOperator(String symbol) {
        return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*") || symbol.equals("/"));
    }

    public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) {
        if (symbol.equals("+")) {
            return new AddInterpreter(left, right);
        } else if (symbol.equals("-")) {
            return new SubInterpreter(left, right);
        } else if (symbol.equals("*")) {
            return new MultiInterpreter(left, right);
        } else if (symbol.equals("/")) {
            return new DivInterpreter(left, right);
        }
        return null;
    }
}
// 测试类
public class Test {

    public static void main(String[] args) {
        System.out.println("result: " + new GPCalculator("10 + 30").calculate());
        System.out.println("result: " + new GPCalculator("10 + 30 - 20").calculate());
        System.out.println("result: " + new GPCalculator("100 * 2 + 400 * 1 + 66").calculate());
    }

}

三、源码中的解释器模式

1、Pattern正则

JDK源码中的Pattern对正则表达式的编译和解析。

public final class Pattern implements java.io.Serializable {
    private Pattern(String p, int f) {
        pattern = p;
        flags = f;

        // to use UNICODE_CASE if UNICODE_CHARACTER_CLASS present
        if ((flags & UNICODE_CHARACTER_CLASS) != 0)
            flags |= UNICODE_CASE;

        // Reset group index count
        capturingGroupCount = 1;
        localCount = 0;

        if (pattern.length() > 0) {
            compile();
        } else {
            root = new Start(lastAccept);
            matchRoot = lastAccept;
        }
    }
	// ...
	public static Pattern compile(String regex) {
        return new Pattern(regex, 0);
    }
	// ...
    public static Pattern compile(String regex, int flags) {
        return new Pattern(regex, flags);
    }
	// ...
}

Java正则表达式及Pattern与Matcher使用详解

2、Spring的ExpressionParser

Spring的ExpressionParser也是使用解释器模式。

public interface ExpressionParser {

	Expression parseExpression(String expressionString) throws ParseException;

	Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}

它有个实现类SpelExpressionParser,用来解析Spel表达式的。

// 测试案例
public class SpringTest {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("100 * 2 + 400 * 2 + 66");
        int result = (Integer) expression.getValue();
        System.out.println("计算结果是:" + result);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/562223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jupyter notebook零散操作整理

1 修改Jupyter Notebook打开路径 1.1 永久修改 jupyter notebook --generate-config 打开相应的.py文件&#xff0c;修改c.NotebookApp.notebook_dir 1.2 临时修改 .切换到需要的临时目录&#xff0c;打开jupyter notebook 2 使用Matplotlib绘图时输出矢量图 %config Inli…

MT4期货软件怎么使用?有哪些MT4期货软件使用知识?

现在MT4软件在投资市场上应用广泛&#xff0c;当然也包括期货交易市场&#xff0c;但有不少投资者不知道为什么一定要选择MT4期货软件&#xff0c;其实选择MT4期货软件的理由有很多&#xff0c;MT4作为一款交易软件&#xff0c;不仅能够为投资者提供准确的市场信息&#xff0c;…

PyQt5实现父窗口内点击按钮显示子窗口(窗口嵌套功能)

摘要&#xff1a;在软件中&#xff0c;常会有点击某个按钮&#xff0c;显示一个新的子界面的需求&#xff0c;本文介绍如何在PyQt5中实现这一功能&#xff0c;主要涉及知识点是“信号与槽函数的自动绑定”。 程序说明&#xff1a; 1.开发环境&#xff1a;win10系统&#xff0c…

【C++】C++11线程库 和 C++IO流

春风若有怜花意&#xff0c;可否许我再少年。 文章目录 一、C11线程库1.thread类介绍2.mutex互斥锁 和 CAS原子操作&#xff08;compare and set&#xff09;3.lock_guard和unique_lock4.两个线程交替打印&#xff0c;一个打印奇数&#xff0c;一个打印偶数&#xff08;线程同步…

Java前缀和算法

一.什么是前缀和算法 通俗来讲&#xff0c;前缀和算法就是使用一个新数组来储存原数组中前n-1个元素的和&#xff08;如果新数组的当前元素的下标为n&#xff0c;计算当前元素的值为原数组中从0到n-1下标数组元素的和&#xff09;&#xff0c;可能这样讲起来有点抽象&#xff0…

题解 . 洛谷题单之动态规划的引入

前置知识&#xff1a; 数字三角形问题&#xff1a;动态规划之数字三角形模型_如何何何的博客-CSDN博客 01背包问题&#xff1a;动态规划之01背包模型_如何何何的博客-CSDN博客 完全背包问题&#xff1a;动态规划之完全背包模型_如何何何的博客-CSDN博客 多重背包问题&#…

两种蚁狮群优化(Ant Lion Optimizer, ALO)实现及仿真实验——附代码

目录 蚁狮群优化算法介绍&#xff1a; 总结概括&#xff1a; ALO算法设计&#xff1a; 1.觅食的蚂蚁随机行走 2.设置陷阱 3.设置陷阱诱捕蚂蚁 4.捕获猎物重建洞穴 多目标MOALO算法 两种蚁狮算法求解效果 (1) ALO (2) MOALO Matlab代码分享&#xff1a; 蚁狮群优化算…

CleanMyMac X4.13.5中文版Mac电脑优化软件

CleanMyMac X4.13.5是一款Mac电脑优化软件&#xff0c;旨在提高Mac电脑的性能、稳定性和安全性。它可扫描您的Mac电脑&#xff0c;并删除不需要的文件、清理缓存、卸载不必要的应用程序、优化启动项、检测并删除恶意软件等。此外&#xff0c;它还可以帮助您管理您的文件&#x…

Python库 pdf2docx 轻松将PDF转换成docx

前言&#xff1a; 可将 PDF 转换成 docx 文件的 Python 库。该项目通过 PyMuPDF 库提取 PDF 文件中的数据&#xff0c;然后采用 python-docx 库解析内容的布局、段落、图片、表格等&#xff0c;最后自动生成 docx 文件。 pdf2docx功能 解析和创建页面布局 页边距章节和分栏 (目…

腾讯云-服务违规封禁提醒解决

腾讯云-服务违规封禁提醒解决 背景解决方案Step1: 查看目标主机上有哪些TCP链接在使用22端口以及该进程的详细信息Step2&#xff1a;删除异常进程&#xff0c;及其可执行文件Step3&#xff1a;处理已经挂起的链接Step4&#xff1a;查看是否存在相关的定时任务Step5&#xff1a;…

vue - 大文件分片上传之simple-uploader.js的使用

vue - 大文件分片上传之simple-uploader.js的使用 分片上传的思路前端文件切片常见的写法后端常见的写法 关于大文件上传 关于单个文件上传&#xff1a;其实就是前端中的文件通过http传到后端&#xff0c;后端再写入服务器的过程 那单个大文件分片上传&#xff1a;其实就是前…

SpringBoot中操作Redis解析JsonArray数据为对象List(ruoyi字典值sys_dict为例)

场景 若依前后端分离版手把手教你本地搭建环境并运行项目&#xff1a; 若依前后端分离版手把手教你本地搭建环境并运行项目_前后端分离的项目怎么跑起来_霸道流氓气质的博客-CSDN博客 在上面搭建系统的基础上&#xff0c;会将系统的字典值缓存进redis中。 看数据格式存储的是…

大数据数据湖技术Hudi0.12.0版本源码编译

0 介绍 Apache Hudi&#xff08;Hadoop Upserts Delete and Incremental&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取服务、数据集群/压缩优化和并发&#xff0c;同时保…

WorkPlus AI助理 | 将企业业务场景与ChatGPT结合

近年来&#xff0c;人工智能成为了企业数字化转型的热门话题&#xff0c;作为被训练的语言模型&#xff0c;ChatGPT具备模拟对话、回答问题、写代码、写小说、进行线上内容创作的能力&#xff0c;还能根据聊天的上下文进行互动。作为一款新兴的人工智能应用程序&#xff0c;对于…

jdk14至16——record关键字

record类型是从14开始预览到16成为正式版的&#xff0c;record类型是一种受限制的类&#xff0c;一般用来封装不可变对象&#xff0c;record类型会自动生成一个全部属性的构造方法&#xff0c;以及属性的get方法&#xff0c;但没有set方法&#xff0c;会自动生成hashCode()、eq…

JS中深拷贝浅拷贝的区别

深浅拷贝在MDN官方中的表述是这样的&#xff1a; 对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用&#xff08;指向相同的底层值&#xff09;的副本。因此&#xff0c;当你更改源或副本时&#xff0c;可以确保不会导致其他对象也发生更改&#xff1b;也就是说&a…

vue下拉框vue字典映射转换失败 字符转数字parseInt :value vue下拉框无法选择 选什么都是最后一个 el-select默认选中 el-select设为只读 input输入框设为只读

现象&#xff1a; 解决后&#xff1a; 关键点&#xff1a; 1、value"0" 与 :value"0" 不同&#xff0c;加冒号:试试 2、:value"parseInt(dict.dictValue)" 字符串转int试试 parseInt() 写死下拉框 <el-form-item label"类型" pro…

COBOL 程序结构

COBOL 程序结构 COBOL程序结构由部&#xff08;division&#xff09;组成&#xff0c;如下图所示&#xff1a; 这些部简介如下&#xff1a; **Sections&#xff08;节&#xff09;**是程序逻辑的逻辑细分。节是段落的集合。**Paragraphs&#xff08;段&#xff09;**是一个节…

路面积水监测-路面积水监测系统

在城市化进程不断加快的背景下&#xff0c;城市道路面临着日益严重的积水问题。持续的降雨和不良的排水系统导致路面积水&#xff0c;给交通运输和城市生活带来了诸多不便和安全隐患。路面积水监测系统旨在易涝点布设内涝积水监测仪&#xff0c;实时了解路面积水情况&#xff0…

矿井水除氟,污水除氟的工艺分析

高矿化度的废水是指含有高浓度溶解性矿物质的废水&#xff0c;通常指的是含有高浓度钠、钙、镁、铁、铝、钾等离子的废水。这些离子通常来自于废水所处的环境、工业或生产过程中使用的原材料和化学品。高矿化度的废水通常具有高盐度、高电导率、高硬度等特征&#xff0c;对环境…