找回密码
 立即注册
首页 业界区 业界 [设计模式/Java] 设计模式之解释器模式【27】 ...

[设计模式/Java] 设计模式之解释器模式【27】

晦险忿 前天 22:17
概述:解释器模式 := Interpreter Pattern ∈ 行为型模式

模式定义


  • 解释器模式(Interpreter Pattern)提供了评估语言的语法表达式的方式
属于行为型模式


  • 解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
  • 这种模式被用在 SQL 解析符号处理引擎等。
  • 模式的意图
定义一种语言的文法表示,并创建一个解释器,该解释器能够解释该语言中的句子。。


  • 主要解决的问题
解释器模式用于构建一个能够解释特定语言或文法句子的解释器
模式结构

解释器模式包含以下几个主要角色:


  • 抽象表达式(Abstract Expression):定义了解释器的抽象接口,声明了解释操作的方法,通常是一个抽象类或接口。
  • 终结符表达式(Terminal Expression):实现了抽象表达式接口的终结符表达式类,用于表示语言中的终结符(如变量、常量等),并实现了对应的解释操作。
  • 非终结符表达式(Non-terminal Expression):实现了抽象表达式接口的非终结符表达式类,用于表示语言中的非终结符(如句子、表达式等),并实现了对应的解释操作。
  • 上下文(Context):包含解释器之外的一些全局信息,在解释过程中提供给解释器使用,通常用于存储变量的值、保存解释器的状态等。
  • 客户端(Client):创建并配置具体的解释器对象,并将需要解释的表达式传递给解释器进行解释。
适用场景


  • 当某一特定类型的问题频繁出现,并且可以通过一种简单的语言来表达这些问题的实例时。
实现方式


  • 定义【文法】:明确语言的终结符和非终结符。
  • 构建【语法树】:根据语言的句子构建对应的语法树结构。
  • 创建【环境类】:包含解释过程中所需的全局信息,通常是一个HashMap。
关键代码


  • 终结符与非终结符:定义语言的文法结构。
  • 环境类:存储解释过程中需要的外部环境信息。
模式特点

优点


  • 可扩展性好:容易添加新的解释表达式的方式。
  • 灵活性:可以根据需要轻松扩展或修改文法。
  • 易于实现简单文法:对于简单的语言,实现起来相对容易。
缺点


  • 使用场景有限:只适用于适合使用解释的简单文法。
  • 维护困难:对于复杂的文法,维护和扩展变得困难。
  • 类膨胀:可能会产生很多类,每个文法规则对应一个类。
  • 递归调用:解释器模式通常使用【递归调用】,这可能难以理解和跟踪。
使用建议


  • 在需要解释执行语言中的句子时,考虑使用解释器模式。
  • 确保文法简单,以避免系统变得过于复杂。
  • 解释器模式在 Java 中可能不是首选,如果遇到适用场景,可以考虑使用如expression4J之类的库来代替。
案例实践

案例总结


  • 编译器:解释器模式可以用于编译器设计,将源代码解释为目标代码。
  • 正则表达式:解释器模式可以用于解析和执行正则表达式。
  • SQL解析:解释器模式可以用于解析和执行SQL语句。
  • 文本模板渲染引擎 : 用于将各层级的环境信息渲染到文本模板中,生成最终的SQL、HTML等。著名的文本模板渲染引擎有:Jinjia(2) / Thymeleaf / Freemaker 等。
CASE 解释器模式的简单实现(性别/婚姻)


  • 我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。
  • 定义作为上下文中主要解释器的 TerminalExpression 类。
  • 其他的类 OrExpression、AndExpression 用于创建组合式表达式
  • InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。
1.png

表达式接口 : Expression
  1. public interface Expression {
  2.    public boolean interpret(String context);
  3. }
复制代码
表达式接口的实现类: TerminalExpression / OrExpression / AndExpression


  • TerminalExpression
  1. public class TerminalExpression implements Expression {
  2.    
  3.    private String data;
  4.    public TerminalExpression(String data){
  5.       this.data = data;
  6.    }
  7.    @Override
  8.    public boolean interpret(String context) {
  9.       if(context.contains(data)){
  10.          return true;
  11.       }
  12.       return false;
  13.    }
  14. }
复制代码

  • OrExpression
  1. public class OrExpression implements Expression {
  2.    private Expression expr1 = null;
  3.    private Expression expr2 = null;
  4.    public OrExpression(Expression expr1, Expression expr2) {
  5.       this.expr1 = expr1;
  6.       this.expr2 = expr2;
  7.    }
  8.    @Override
  9.    public boolean interpret(String context) {      
  10.       return expr1.interpret(context) || expr2.interpret(context);
  11.    }
  12. }
复制代码

  • AndExpression
  1. public class AndExpression implements Expression {
  2.    private Expression expr1 = null;
  3.    private Expression expr2 = null;
  4.    public AndExpression(Expression expr1, Expression expr2) {
  5.       this.expr1 = expr1;
  6.       this.expr2 = expr2;
  7.    }
  8.    @Override
  9.    public boolean interpret(String context) {      
  10.       return expr1.interpret(context) && expr2.interpret(context);
  11.    }
  12. }
复制代码
Client : InterpreterPatternDemo


  • InterpreterPatternDemo : 使用 Expression 类来创建规则,并解析它们。
  1. public class InterpreterPatternDemo {
  2.    //规则:Robert 和 John 是男性
  3.    public static Expression getMaleExpression(){
  4.       Expression robert = new TerminalExpression("Robert");
  5.       Expression john = new TerminalExpression("John");
  6.       return new OrExpression(robert, john);   
  7.    }
  8.    //规则:Julie 是一个已婚的女性
  9.    public static Expression getMarriedWomanExpression(){
  10.       Expression julie = new TerminalExpression("Julie");
  11.       Expression married = new TerminalExpression("Married");
  12.       return new AndExpression(julie, married);   
  13.    }
  14.    public static void main(String[] args) {
  15.       Expression isMale = getMaleExpression();
  16.       Expression isMarriedWoman = getMarriedWomanExpression();
  17.       System.out.println("John is male? " + isMale.interpret("John"));
  18.       System.out.println("Julie is a married women? "
  19.       + isMarriedWoman.interpret("Married Julie"));
  20.    }
  21. }
复制代码
out
  1. John is male? true
  2. Julie is a married women? true
复制代码
CASE 开源 SQL 解析引擎 - Apche Calcite


  • 推荐文献


  • Calcite - SQL 解析框架 - Zhihu/廖嘉逸
Calcite vs Antlr


  • 推荐文献


  • 【Antlr】和【Calcite】解析sql对比 - makeyourchoice.cn
  • Calcite SQL 解析、语法扩展、元数据验证原理与实战(上) - CSDN
  1. ANTLR4和Calcite是两个不同的工具,它们各自有不同的用途和功能。
  2. ANTLR4是一个强大的解析器生成器,它可以根据语法规则生成解析器和词法分析器。
  3. 它支持多种编程语言,包括Java、Python、C++等,可以用于生成用于编译器、解释器、代码生成器等领域中的解析器。
  4. Apache Calcite是一个开源的动态数据管理框架,它提供了一个通用的SQL语言解析器、查询优化器和执行引擎。
复制代码

  • Apache Calcite 是否依赖 Antlr? 否
  1. Apache Calcite 默认使用 JavaCC 生成 SQL 解析器,可以很方便的将其替换为 Antlr 作为代码生成器 。
  2. JavaCC 全称 Java Compiler Compiler,是一个开源的 Java 程序解析器生成器,生成的 语法分析器 采用递归下降语法解析,简称 LL (K)。
  3. 主要通过一些模版文件生成语法解析程序(例如根据 .jj 文件或者 .jjt 等文件生产代码)。
复制代码
CASE 开源语法分析工具 - Antlr


  • 推荐文献


  • Antlr - 强大的开源语法分析工具 - Zhihu
  • Antlr : 开源语法分析工具 - 博客园/千千寰宇
CASE 开源语法分析工具 - JavaCC


  • 推荐文献


  • JavaCC官方入门指南-概述 - 博客园
  • JAVACC使用总结(一):基础入门 - CSDN


  • JavaCC版的Hello World
以下是一个简单的JavaCC语法文件Hello.jj,用于解析以“Hello”或“hello”或“HELLO”开头,跟着至少一个变量,多个变量时用逗号分隔,并以感叹号结尾的字符串:
  1. options {
  2.         STATIC = false;
  3.         DEBUG_PARSER = true;
  4.         JDK_VERSION = "1.8";
  5.         UNICODE_INPUT = true;
  6. }
  7. PARSER_BEGIN(Hello)
  8. package com.javacc.hello;
  9. import cn.hutool.core.date.DateUtil;
  10. public class Hello {
  11.         public static void main(String[] args) {
  12.                 Hello helloParser = new Hello(System.in);
  13.                 try {
  14.                         String res = helloParser.sayHello();
  15.                         System.out.println(res);
  16.                 } catch (Exception e) {
  17.                         e.printStackTrace();
  18.                 }
  19.         }
  20. }
  21. PARSER_END(Hello)
  22. SKIP : {" "}
  23. TOKEN : {
  24.         <HELLO : "Hello" | "hello" | "HELLO">
  25.         | <IDENTITY : (["a" - "z", "A" - "Z", "\\u4e00" - "\\u9fa5"])+>
  26.         | <EXCLAMATION : "!">
  27.         | <COMMA : ",">
  28. }
  29. TOKEN : { <EOL : "\\n" | "\\r" | "\\r\\n"> }
  30. String sayHello():
  31. {
  32.         Token token;
  33.         StringBuilder builder = new StringBuilder("Javacc Say : Hello ");
  34. }
  35. {
  36.         <HELLO> token = <IDENTITY>
  37.         { builder.append(token.image); }
  38.         (<COMMA> token = <IDENTITY>
  39.         { builder.append(" , "); builder.append(token.image); })*
  40.         <EXCLAMATION>
  41.         { builder.append(" ! ").append(DateUtil.now()); return builder.toString(); }
  42. }
复制代码

  • 生成解析程序
  1. javacc Hello.jj
复制代码
成功生成后,会有反馈信息,并生成多个Java文件,包括Hello.java、HelloConstants.java、HelloTokenManager.java等


  • 运行测试
可以运行生成的Hello程序,测试不同的输入字符串,查看解析结果和错误信息
JavaCC是一个强大的工具,适用于需要自定义语法解析的场景,如编译器和解释器的开发
CASE 文本模板渲染引擎 - Thymeleaf / Freemaker / JSP / Jinjia


  • 常见的Java模板引擎 - CSDN
  • Jinjia使用指南 - 博客园/千千寰宇
Y 推荐文献


  • 设计模式之总述 - 博客园/千千寰宇
X 参考文献


  • 解释器模式 - 菜鸟教程
    本文作者:        千千寰宇   
    本文链接:         https://www.cnblogs.com/johnnyzen   
    关于博文:评论和私信会在第一时间回复,或直接私信我。   
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA     许可协议。转载请注明出处!
    日常交流:大数据与软件开发-QQ交流群: 774386015        【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!   

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册