首页 > 其他分享 >解释器模式:如何实现一个自定义配置规则功能?

解释器模式:如何实现一个自定义配置规则功能?

时间:2024-09-22 16:23:15浏览次数:9  
标签:解释器 自定义 expr2 expr1 规则 interpreter Expression public

解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助我们思考如何通过更简洁的规则来表示复杂的逻辑

一、模式原理分析

解释器模式的原始定义是:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。

语法也称文法,在语言学中指任意自然语言中句子、短语以及词等语法单位的语法结构与语法意义的规律。比如,在编程语言中,if-else 用作条件判断的语法,for 用于循环语句的语法标识。再比如,“我爱中国”是一个中文句子,我们可以用名词、动词、形容词等语法规则来直观地描述句子。

我们还是直接先来看看解释器模式的 UML 图:

从该 UML 图中,我们能看出解释器模式包含的关键角色有四个。

  • 抽象表达式(AbstractExpression):定义一个解释器有哪些操作,可以是抽象类或接口,同时说明只要继承或实现的子节点都需要实现这些操作方法。

  • 终结符表达式(TerminalExpression):用于解释所有终结符表达式。

  • 非终结符表达式(NonterminalExpression):用于解释所有非终结符表达式。

  • 上下文(Context):包含解释器全局的信息。

解释器模式 UML 对应的代码实现如下:

//抽象表达式类
public interface AbstractExpression {
    boolean interpreter(Context context);
}
//上下文信息类
public class Context {
    private String data;
    public Context(String data) {
        this.data = data;
    }
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
    }
}
//终结符表达式类
public class TerminalExpression implements AbstractExpression {
    private String data;
    public TerminalExpression(String data) {
        this.data = data;
    }
    @Override
    public boolean interpreter(Context context) {
        if(context.getData().contains(data)) {
            return true;
        } else {
            return false;
        }
    }
}
//非终结符表达式类
public class NonterminalExpression implements AbstractExpression {
    AbstractExpression expr1;
    AbstractExpression expr2;
    public NonterminalExpression(AbstractExpression expr1, AbstractExpression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    @Override
    public boolean interpreter(Context context) {
        return expr1.interpreter(context) && expr2.interpreter(context);
    }
}
//单元测试类
public class Demo {
    public static void main(String[] args) {
        AbstractExpression person1 = new TerminalExpression("mick");
        AbstractExpression person2 = new TerminalExpression("mia");
        AbstractExpression isSingle = new NonterminalExpression(person1, person2);
        Context context1 = new Context("mick,mia");
        Context context2 = new Context("mia,mock");
        Context context3 = new Context("spike");
        System.out.println(isSingle.interpreter(context1));
        System.out.println(isSingle.interpreter(context2));
        System.out.println(isSingle.interpreter(context3));
    }
}
//输出结果
true
false
false


在上面的代码实现中,NonterminalExpression 用于判断两个表达式是否都存在,存在则在解释器判断时输出 true,如果只有一个则会输出 false。也就是说,表达式解释器的解析逻辑放在了不同的表达式子节点中,这样就能通过增加不同的节点来解析上下文。

所以说,解释器模式原理的本质就是对语法配备解释器,通过解释器来执行更详细的操作。

二、使用场景分析

一般来讲,解释器模式常见的使用场景有这样几种。

  • 当语言的语法较为简单并且对执行效率要求不高时。比如,通过正则表达式来寻找 IP 地址,就不需要对四个网段都进行 0~255 的判断,而是满足 IP 地址格式的都能被找出来。

  • 当问题重复出现,且可以用一种简单的语言来进行表达时。比如,使用 if-else 来做条件判断语句,当代码中出现 if-else 的语句块时都统一解释为条件语句而不需要每次都重新定义和解释。

  • 当一个语言需要解释执行时。如 XML 文档中<>括号表示的不同的节点含义。

为了更好地理解解释器模式的使用场景,下面我们通过一个简单的例子来详细说明。

我们创建一个逻辑与的解释器例子。简单来说,就是通过字符串名字来判断表达式是否同时存在,存在则打印 true,存在一个或不存在都打印 false。在下面的代码中,我们会创建一个接口 Expression 和实现 Expression 接口的具体类,并定义一个终结符表达式类 TerminalExpression 作为主解释器,再定义非终结符表达式类,这里 OrExpression、AndExpression 分别是处理不同逻辑的非终结符表达式。

public interface Expression {
    boolean interpreter(String con);
}
public class TerminalExpression implements Expression{
    String data;
    public TerminalExpression(String data) {
        this.data = data;
    }
    @Override
    public boolean interpreter(String con) {
        if(con.contains(data)) {
            return true;
        } else {
            return false;
        }
    }
}
public class AndExpression implements Expression {
    Expression expr1;
    Expression expr2;
    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    public boolean interpreter(String con) {
        return expr1.interpreter(con) && expr2.interpreter(con);
    }
}
public class OrExpression implements Expression {
    Expression expr1;
    Expression expr2;
    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    public boolean interpreter(String con) {
        return expr1.interpreter(con) || expr2.interpreter(con);
    }
}
public class AndExpression implements Expression {
    Expression expr1;
    Expression expr2;
    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    public boolean interpreter(String con) {
        return expr1.interpreter(con) && expr2.interpreter(con);
    }
}
public class Client {
    public static void main(String[] args) {
        Expression person1 = new TerminalExpression("mick");
        Expression person2 = new TerminalExpression("mia");
        Expression isSingle = new OrExpression(person1, person2);
        Expression spike = new TerminalExpression("spike");
        Expression mock = new TerminalExpression("mock");
        Expression isCommitted = new AndExpression(spike, mock);
        System.out.println(isSingle.interpreter("mick"));
        System.out.println(isSingle.interpreter("mia"));
        System.out.println(isSingle.interpreter("max"));
        System.out.println(isCommitted.interpreter("mock, spike"));
        System.out.println(isCommitted.interpreter("Single, mock"));
    }
}
//输出结果
true
true
false
true
false

在最终单元测试的结果中,我们可以看到:在表达式范围内的单词能获得 true 的返回,没有在表达式范围内的单词则会获得 false 的返回。

三、为什么使用解释器模式?

分析完解释器模式的原理和使用场景后,我们再来说说使用解释器模式的原因,可总结为以下两个。

第一个,将领域语言(即问题表征)定义为简单的语言语法。这样做的目的是通过多个不同规则的简单组合来映射复杂的模型。比如,在中文语法中会定义主谓宾这样的语法规则,当我们写了一段文字后,其实是可以通过主谓宾这个规则来进行匹配的。如果只是一个汉字一个汉字地解析,解析效率会非常低,而且容易出错。同理,在开发中我们可以使用正则表达式来快速匹配IP地址,而不是将所有可能的情况都用 if-else 来进行编写。

第二个,更便捷地提升解释数学公式这一类场景的计算效率。我们都知道,计算机在计算加减乘除一类的数学运算时,和人类计算的方式是完全不同的,需要通过一定的规则运算才能得出最后的结果。比如,3+2-(4 X 5),如果我们不告诉计算机先要运算括号中的表达式,计算机则只会按照顺序进行计算,这显然是错误的。而使用解释器模式,则能很好地通过预置的规则来进行判断和解释。分析完解释器模式的原理和使用场景后,我们再来说说使用解释器模式的原因,可总结为以下两个。

四、解释器模式的优缺点是什么?

通过前面的分析,我们也就可以总结出使用解释器模式主要有以下优点。

  • 很容易改变和扩展语法逻辑。由于在模式中是使用类来表示语法规则的,因此当我们需要新增或修改规则时,只需要新增或修改类即可。同时,还可以使用继承或组合方式来扩展语法。

  • 更容易实现语法。我们可以定义节点的类型,并编写通用的规则来实现这些节点类,或者还可以使用编译器来自动生成它们。

同样,解释器模式也不是万能的,也有一些缺点。

  • 维护成本很高。语法中的每个规则至少要定义一个类,因此,语法规则越多,类越难管理和维护。

  • 执行效率较低。由于解释器模式会使用到树的数据结构,那么就会使用大量的循环和递归调用来访问不同的节点,当需要解释的句子语法比较复杂时,会执行大量的循环语句,性能很低。

  • 应用场景单一,复用性不高。在开发中,除了要发明一种新的编程语言或对某些新硬件进行解释外,解释器模式的应用实例其实非常少,加上特定的数据结构,扩展性很低。

在实际的业务开发中,解释器模式很少使用,主要应用于 SQL 解析、符号处理引擎等场景中。

在解释器模式中通常会使用树的结构,有点类似于组合模式中定义的树结构,终端表达式对象是叶对象,非终端表达式是组合对象

虽然解释器模式很灵活,能够使用语法规则解析很多复杂的句子,比如,编程语法。但是稍不留神就很容易把解释逻辑写在一个类中,进而导致后期难以维护。除此之外,把解析逻辑拆分为单个的子节点后,又会因为类数量的暴增,导致代码的理解难度倍增。

不过,解释器模式能够通过一些简短的规则来解决复杂的数据匹配问题,比如,正则表达式 [0-9] 就能匹配数字字符串。所以说,理解解释器模式的原理还是很有必要的。

文章(专栏)将持续更新,欢迎关注公众号:服务端技术精选。欢迎点赞、关注、转发

个人小工具程序上线啦,通过公众号(服务端技术精选)菜单【个人工具】即可体验,欢迎大家体验后提出优化意见

标签:解释器,自定义,expr2,expr1,规则,interpreter,Expression,public
From: https://blog.51cto.com/jiangyi/12080798

相关文章

  • 如何使用 AWS 部署带有自定义域的 React 项目?
    使用aws(amazonwebservices)等云提供商通过自定义域部署react应用程序可能看起来令人畏惧,但当分解为可管理的步骤时,它就很简单。在本文中,我们将指导您完成从构建应用程序到使其在您的自定义域上运行的整个过程。当您准备好与世界分享您的react项目时,使用自定义域部署它可以为......
  • 在 React 中创建自定义 Hook 的最佳技巧
    react的自定义hooks是从组件中删除可重用功能的有效工具。它们支持代码中的dry(不要重复)、可维护性和整洁性。但开发有用的自定义钩子需要牢牢掌握react的基本思想和推荐程序。在这篇文章中,我们将讨论在react中开发自定义钩子的一些最佳策略,并举例说明如何有效地应用它们。......
  • Python 交互解释器与 AI
    当Python交互解释器拥有了AI,这将带来一系列令人兴奋的变革和技术进步。一、增强的代码辅助在传统的Python交互解释器中,用户输入代码后,解释器会立即执行并返回结果。而当它拥有了AI后,能够在你输入代码的过程中提供实时的代码建议和自动补全。例如,当你开始输入一个函......
  • 【Ambari自定义组件集成】Bigtop编译大数据组件,看这一篇就够了
    快捷导航在正式内容之前,通过下方导航,大家可以快速找到相关资源:快捷导航链接地址备注相关文档-ambari+bigtop自定义组件集成https://blog.csdn.net/TTBIGDATA/article/details/142150086CSDN地址编译、开发、部署、集成解决方案https://t.zsxq.com/0PVcI知识星球源代码-Ambar......
  • 鸿蒙开发项目中你是怎么理解生命周期?你知道的生命周期函数有那些, 说一下执行时机?(页面
    生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。#一、怎么理解生命周期?生命周期:简单点理解就是从创建到销毁的过程#二、你知道的生命周期函数有那些,说一下执行时机?自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组......
  • view-ui-plus iView Vue 3 table 自定义输入筛选条件
    使用自定义表格头实现筛选:为何和如何在使用view-ui-plus(iView的Vue3版本)时,发现原生的表格组件不支持自定义输入筛选条件为什么要使用自定义表格头?原生组件的限制view-ui-plus的表格组件提供了基本的功能,但在原生实现中,对于复杂的筛选条件或输入框的支持较为有限。......
  • 【服务集成】最新版 | 阿里云OSS对象存储服务使用教程(包含OSS工具类优化、自定义阿里
    文章目录一、阿里云OSS对象存储服务介绍二、服务开通与使用准备1、准备工作2、开通OSS云服务(新用户免费使用三个月)3、创建存储空间bucket4、创建并保存Accesskey5、配置访问凭证AK&SK(系统环境变量)三、阿里云OSS使用步骤1、导入依赖坐标2、文件上传Demo快速入门3、阿里......
  • 自定义类型:联合和枚举
    一.联合体类型的声明像结构体一样,联合体也是有一个或者多个成员构成,这些成员可以是不同的类型。但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。给联合体其中一个成员赋值,其他成员的值也跟着变化。#include......
  • 为什么 Streams API 改变了 Web 开发者的游戏规则
    我们首先解释一下数据是如何通过网络发送的。它不是作为单个连续流发送的;相反,它被分成更小的块。在接收端,消费者或应用程序负责在收到所有数据后以正确的顺序和格式重新组装这些块。对于图像、视频和其他相对较大的数据类型,此过程会自动发生。因此streamsapi提供的是一种无需等......
  • 优化数据库结构:自定义元数据、索引与约束的应用
       当在导入预设表结构时,确实可以自定义一些额外的元数据来优化数据库结构。这些元数据不仅限于表的注释,还包括索引、约束等,这些都是为了提高查询性能、保证数据完整性和便于数据库管理而设计的。表注释表注释是用来描述表的作用、存储的数据类型等信息的文本信息。这......