设计模式 13 解释器模式

解释器模式(Interpreter Pattern)属于 行为型模式

解释器模式是指给定一门语言, 基于它的语法, 定义解释器来解释语言中的句子。是一种按照规定的语法进行解析的模式。

就比如 编译器可以将源码编译解释为机器码, 让 CPU 能进行识别并运行。解释器模式的作用其实与编译器一样,都是将一些固定的语法进行解释,构建出一个解释句子的解释器。

解释器是一个语法分析工具,它可以识别句子语义,分离终结符号和非终结符号,提取出需要的信息,针对不同的信息做出相应的处理。其核心思想是 识别语法,构建解释

解释器模式主要包含 4 种角色:

  • 抽象表达式(Expression) :负责定义解释方法 interpret, 交由子类进行具体解释。
  • 终结符表达式(Terminal Expression) :实现文法中与 终结符有关的解释操作。 文法中的每一个终结符都有一个具体终结表达式与之相对应,比如公式 R = R1 + R2,R1 和 R2 就是终结符,对应的解析 R1 和 R2 的解释器就是终结符表达式。 通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
  • 非终结符表达式(Nonterminal Expression):实现文法中与 非终结符有关的解释操作。 文法中的每条规则都对应于一个非终结符表达式。非终结符表达式一般是文法中的运算符或者其他关键字,比如公式 R = R1 + R2 中, + 就是非终结符,解析它的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
  • 上下文环境(Context) :包含解释器之外的全局信息。 它的任务一般是用来存放文法中各个终结符所对应的具体值,比如 R = R1 + R2,给 R1 赋值 100,给 R2 赋值 200,这些信息需要存放到环境中。

这里以根据乘客年龄和身高来判断乘坐公交车是否免费为例介绍解释器模式:

1、定义乘客

/**
 * 乘客
 */
public class Passenger {

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    public Passenger(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Double getHeight() {
        return height;
    }

    public void setHeight(Double height) {
        this.height = height;
    }
}

2、定义表达式

/**
 * 表达式
 */
public interface Expression {

    /**
     * 解释年龄
     * @param age 年龄
     * @return 解释结果
     */
    boolean interpret(int age);

    /**
     * 解释身高
     * @param height 身高
     * @return 解释结果
     */
    boolean interpret(double height);

}

3、定义比较器

/**
 * 比较器
 */
public enum Compare {

    /**
     * 较大
     */
    GT,

    /**
     * 相等
     */
    EQ,

    /**
     * 较小
     */
    LT

}

4、定义终结符表达式

/**
 * 终结符表达式
 */
public class TerminalExpression implements Expression {

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    /**
     * 比较器
     */
    private final Compare compare;

    /**
     * 构造年龄比较
     * @param age 年龄
     * @param compare 比较器
     */
    public TerminalExpression(int age, Compare compare) {
        this.age = age;
        this.compare = compare;
    }

    /**
     * 构造身高比较
     * @param height 身高
     * @param compare 比较器
     */
    public TerminalExpression(double height, Compare compare) {
        this.height = height;
        this.compare = compare;
    }

    @Override
    public boolean interpret(int age) {

        // 比较年龄大小
        switch (compare) {
            // 较大
            case GT:
                return age > this.age;
            // 相等
            case EQ:
                return age == this.age;
            // 较小
            case LT:
                return age < this.age;
            default:
                return false;
        }
    }

    @Override
    public boolean interpret(double height) {

        // 比较身高大小
        switch (compare) {
            // 较大
            case GT:
                return height > this.height;
            // 相等
            case EQ:
                return height == this.height;
            // 较小
            case LT:
                return height < this.height;
            default:
                return false;
        }
    }

}

5、定义非终结符表达式

与表达式:

/**
 * 与表达式
 */
public class AndExpression implements Expression {

    /**
     * 表达式1
     */
    private Expression expression1;

    /**
     * 表达式2
     */
    private Expression expression2;

    /**
     * 构造表达式
     * @param expression1 表达式1
     * @param expression2 表达式2
     */
    public AndExpression(Expression expression1, Expression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    @Override
    public boolean interpret(int age) {
        return this.expression1.interpret(age) && this.expression2.interpret(age);
    }

    @Override
    public boolean interpret(double height) {
        return this.expression1.interpret(height) && this.expression2.interpret(height);
    }
}

或表达式:

/**
 * 或表达式
 */
public class OrExpression implements Expression {

    /**
     * 表达式1
     */
    private Expression expression1;

    /**
     * 表达式2
     */
    private Expression expression2;

    /**
     * 构造表达式
     * @param expression1 表达式1
     * @param expression2 表达式2
     */
    public OrExpression(Expression expression1, Expression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    @Override
    public boolean interpret(int age) {
        return this.expression1.interpret(age) || this.expression2.interpret(age);
    }

    @Override
    public boolean interpret(double height) {
        return this.expression1.interpret(height) || this.expression2.interpret(height);
    }
}

6、定义免费标准

/**
 * 免费标准
 */
public class Free {

    /**
     * 年龄表达式
     */
    private Expression ageExpression;

    /**
     * 身高表达式
     */
    private Expression heightExpression;

    /**
     * 构造免费情况
     * @param age 年龄
     * @param height 身高
     */
    public Free(int age, double height) {

        // 大于等于设定年龄
        Expression expression1 = new TerminalExpression(age, Compare.GT);
        Expression expression2 = new TerminalExpression(age, Compare.EQ);
        ageExpression = new OrExpression(expression1, expression2);
        // 小于等于设定身高
        expression1 = new TerminalExpression(height, Compare.LT);
        expression2 = new TerminalExpression(height, Compare.EQ);
        heightExpression = new OrExpression(expression1, expression2);
    }

    /**
     * 结果
     * @param age 年龄
     * @param height 身高
     * @return 判定结果
     */
    public boolean result(int age, double height) {
        return ageExpression.interpret(age) || heightExpression.interpret(height);
    }

}

7、客户端调用

// 定义乘客集合
List list = new ArrayList<>();
Passenger p1 = new Passenger("张三", 65, 170.0);
Passenger p2 = new Passenger("李四", 10, 130.0);
Passenger p3 = new Passenger("王五", 50, 170.0);
list.add(p1);
list.add(p2);
list.add(p3);

// 所有年龄大于等于65或者身高小于等于130的乘客免费乘车
for (Passenger p : list) {

    // 定义免费标准
    Free free = new Free(65, 130);
    // 满足条件则免费
    if (free.result(p.getAge(), p.getHeight())) {
        System.out.println(p.getName() + ":免费");
    }
    // 不满足条件则正常收费
    else {
        System.out.println(p.getName() + ":刷卡或投币");
    }
}

输出结果:

&#x5F20;&#x4E09;&#xFF1A;&#x514D;&#x8D39;
&#x674E;&#x56DB;&#xFF1A;&#x514D;&#x8D39;
&#x738B;&#x4E94;&#xFF1A;&#x5237;&#x5361;&#x6216;&#x6295;&#x5E01;

可以看到,按照预期输出了结果,实现了根据年龄和身高自动判断是否免费的功能。

1、扩展性好。解释器模式中使用类来表示语言的文法规则,可以比较方便的通过继承等机制来改变或扩展文法。

2、容易实现。在语法树中的每个表达式节点类都是相似的,实现其文法较为容易。

1、执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。

2、会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护,因此可应用的场景比较少。

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

1、Pattern 有参构造器

private Pattern(String p, int f) {

    // 保存数据
    pattern = p;
    flags = f;

    // 如果存在 UNICODE_CHARACTER_CLASS,则使用 UNICODE_CASE
    if ((flags & UNICODE_CHARACTER_CLASS) != 0) {
        flags |= UNICODE_CASE;
    }

    // 重置组索引计数
    capturingGroupCount = 1;
    localCount = 0;

    if (!pattern.isEmpty()) {
        try {
            // 调用编译方法
            compile();
        } catch (StackOverflowError soe) {
            throw error("Stack overflow during pattern compilation");
        }
    } else {
        root = new Start(lastAccept);
        matchRoot = lastAccept;
    }
}

2、编译方法 compile()

private void compile() {

    // 处理规范等价
    if (has(CANON_EQ) && !has(LITERAL)) {
        normalize();
    } else {
        normalizedPattern = pattern;
    }
    patternLength = normalizedPattern.length();

    // 为方便起见,将模式复制到 int 数组,使用双零终止模式
    temp = new int[patternLength + 2];

    hasSupplementary = false;
    int c, count = 0;

    // 将所有字符转换为代码点
    for (int x = 0; x < patternLength; x += Character.charCount(c)) {
        c = normalizedPattern.codePointAt(x);
        if (isSupplementary(c)) {
            hasSupplementary = true;
        }
        temp[count++] = c;
    }

    // patternLength 现在在代码点中
    patternLength = count;

    if (! has(LITERAL)) {
        RemoveQEQuoting();
    }

    // 在这里分配所有临时对象
    buffer = new int[32];
    groupNodes = new GroupHead[10];
    namedGroups = null;

    if (has(LITERAL)) {
        // 文字模式处理
        matchRoot = newSlice(temp, patternLength, hasSupplementary);
        matchRoot.next = lastAccept;
    } else {
        // 开始递归下降解析
        matchRoot = expr(lastAccept);
        // 检查额外的模式字符
        if (patternLength != cursor) {
            if (peek() == ')') {
                throw error("Unmatched closing ')'");
            } else {
                throw error("Unexpected internal error");
            }
        }
    }

    // 窥孔优化
    if (matchRoot instanceof Slice) {
        root = BnM.optimize(matchRoot);
        if (root == matchRoot) {
            root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot);
        }
    } else if (matchRoot instanceof Begin || matchRoot instanceof First) {
        root = matchRoot;
    } else {
        root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot);
    }

    // 释放临时存储
    temp = null;
    buffer = null;
    groupNodes = null;
    patternLength = 0;
    compiled = true;
}

解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4JJep 等来设计。

Original: https://www.cnblogs.com/codesail/p/16583651.html
Author: 程序航
Title: 设计模式 13 解释器模式

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/715003/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Serilog高级玩法之用Serilog记录所选终结点附加属性

    这是在ASP.NET Core 3.X中使用Serilog.AspNetCore系列文章的第二篇文章:。 第1部分-使用Serilog RequestLogging减少日志详细程度…

    技术杂谈 2023年6月1日
    0110
  • CentOS 文本编辑器

    Linux 终端的文本编辑器中,较著名的有:Nano、Vim、Emacs。其它文本编辑器还有 Gedit、Sublime,Atom 等等。 1.1、基础命令 nano:打开 nan…

    技术杂谈 2023年7月10日
    094
  • Spring Boot下的一种导出Excel文件的代码框架

    1、前言 ​ 在Spring Boot项目中,将数据导出成Excel格式文件是常见的功能。与Excel文件导入类似,此处也用代码框架式的方式实现Excel文件导出,使得代码具有可重…

    技术杂谈 2023年6月21日
    0101
  • quartz框架(十)-QuartzSchedulerThread

    本篇博文,博主将介绍QuartzSchedulerThread的相关内容。话不多说,直接进入正题。 从源码和该类的名称上,QuartzSchedulerThread首先是一个线程类…

    技术杂谈 2023年7月24日
    084
  • opencv-python函数

    opencv-python读取、展示和存储图像 cv2.imshow(windows_name, image) 函数参数一: 窗口名称(字符串)函数参数二: 图像对象,类型是num…

    技术杂谈 2023年6月21日
    090
  • mongdb Atlas

    免费的 512M mongdb 数据库,可直观查看数据,不需要另外安装 Navicat。 登录地址:https://account.mongodb.com/account/logi…

    技术杂谈 2023年5月30日
    088
  • legend3—laravel报419错误

    legend3—laravel报419错误 一、总结 一句话总结: js中对象写成了数组,导致后端读不到csrf的token报错 二、legend3—lar…

    技术杂谈 2023年5月30日
    085
  • Java编辑器的下载和应用——IDEA

    IDEA下载 (1)搜索 IntelliJ IDEA,选择电脑适合的版本下载(跟着指示一步步安装就好了) (2)安装完成后打开,创建一个空项目(在之后的学习中可把所有的代码放这里,…

    技术杂谈 2023年6月21日
    0108
  • SpringBoot整合MongoDB

    NoSQL(Not Only SQL),即反SQL运动或者是不仅仅SQL,指的是非关系型的数据库,是一项全新的数据库革命运动,是一种全新的思维注入 NoSQL优点 数据库高并发读写…

    技术杂谈 2023年6月21日
    089
  • Java线程安全与锁

    Java 线程安全 与 锁 多线程内存模型 线程私有栈内存 每个线程 私有的内存区域 进程公有堆内存 同一个进程 共有的内存区域 为什么会有线程安全问题? 多个线程同时具有对同一资…

    技术杂谈 2023年7月24日
    074
  • 记录一下copy我博客的地址(捂脸)

    背景 今天又需要基于Spring扩展点做些事情,来看看自己之前记录的博客,好奇百度页面搜索了下看看能不能搜出我的文章,发现了熟悉的字眼和图片,发现完全就是自己的…. 有…

    技术杂谈 2023年7月25日
    072
  • 工厂模式详解

    1.1工厂模式的由来 现实生活中,原始社会(没有工厂)–> 农耕小作坊(简单工厂)–> 工业革命(工厂方法)–> 代工厂(抽象…

    技术杂谈 2023年6月21日
    090
  • FPGA学习-2,一点理解

    1、Wire只能赋一次值,Reg可以多次改变2、#100这种是在仿真系统下有效。3、同一个文件下也可以写多个module. 本博客是个人工作中记录,遇到问题可以互相探讨,没有遇到的…

    技术杂谈 2023年6月1日
    087
  • target 1

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年7月25日
    082
  • 从一个例子中体会React的基本面

    【起初的准备工作】 npm init npm install –save react react-dom npm install –save-dev html-webpack-…

    技术杂谈 2023年5月31日
    099
  • WIN10平板 总是提示你需要管理员权限怎么办

    例如在往C盘拷贝文件的时候,会出现下面的提示,虽然点击继续也可以执行,但是还是非常麻烦 WIN+R,打开组策略 在Windows设置-安全设置-安全选项中,找到用户账户控制,设置为…

    技术杂谈 2023年5月31日
    0188
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球