设计模式学习(22) 23-20 解释器模式

文章目录

  • [0. 个人感悟](#0. 个人感悟)
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 概念理解](#3.1 概念理解)
      • [3.1.1 文法](#3.1.1 文法)
      • [3.3.2 终结符和非终结符](#3.3.2 终结符和非终结符)
      • [3.3.3 句子](#3.3.3 句子)
      • 3.3.4语法树
    • [3.2 实现思路](#3.2 实现思路)
    • [3.3 UML类图](#3.3 UML类图)
    • [3.4 代码示例](#3.4 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)

0. 个人感悟

  • 解释器模式旨在定义语法规则,并对符合语法规则的句子进行解释
  • 解释器模式使用场景专业且有限,实际工作中场景很少,可以帮助简单理解编译器原理
  • 模式涉及很多编译原理的概念,看了一些文章,还是一直半解,这里把自己理解的内容记录下来,先不纠结了
  • 关于四则运行的示例,找了一个比较泛用、易懂的代码,有兴趣可以看看,核心在使用stack构建解释器
  • 对于基础的数据表达式分析计算,已有很多成熟的开源底代码,大家实际工作中遇到可以了解下,比如Expression4J、MESP(Math Expression String Parser) 、Jep等

1. 概念

英文定义 (《设计模式:可复用面向对象软件的基础》)

Given a language, define a represention for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

中文翻译

给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

理解

  • 语言导向的设计模式:专门为解释和执行特定领域语言而设计
  • 文法与解释分离:将文法规则抽象为类结构,解释逻辑分布在各个表达式类中
  • 组合模式的应用:通常使用组合模式构建抽象语法树来表示语言结构
  • 关注点分离:文法定义、语法解析、解释执行各司其职

2. 适配场景

2.1 适合的场景

  • 简单文法规则:当语言的文法相对简单,规则数量有限时
  • 执行效率非关键:对解释执行效率要求不高的场景
  • 频繁修改文法:当文法需要经常扩展或修改时
  • 领域特定语言(DSL):需要为特定领域创建专用解释器
  • 避免重复解析:需要多次执行相同或类似表达式

2.2 常见场景举例

  • 正则表达式引擎:解释并执行正则表达式匹配规则
  • SQL查询解释器:解释简单的SQL查询语句
  • 业务规则引擎:解释执行业务规则,如优惠券规则、风控规则
  • 数学公式计算器:解释数学表达式并计算结果
  • 配置文件解析:解释特定格式的配置文件
  • 机器人指令系统:解释机器人控制指令序列
  • 简单脚本语言:实现简单的脚本语言解释器

3. 实现方法

3.1 概念理解

定义里提到了很多编译原理中的概念,首先了解下各个概念含义,有利于理解模式

以逆波兰表示法(项在前 运算符在后)的四则运算的四则运算为例

3.1.1 文法

定义

形式语言的规则系统,定义了语言中合法句子的结构规则。通常使用产生式规则描述。

理解*

  • 文法是定义语法规则的,比如我们的汉语、正则表达式、sql、甚至代码,都有语法,符合规则才能正确解析。所以解释器一般都有很强的专业性。

  • 对于文法的表示,通常用BNF(巴科斯-诺尔范式),有兴趣可以了解下
    例子:四则运行

    <expression> ::= <term> <term> <operator>
    | <expression> <term> <operator>
    | <term>

    <term> ::= <number>
    | <variable>
    | <expression>

    <number> ::= [0-9]+
    <variable> ::= [a-zA-Z_][a-zA-Z0-9_]*
    <operator> ::= "+" | "-" | "*" | "/"

规则解释

复制代码
规则1: 表达式 → 项 项 运算符
规则2: 表达式 → 表达式 项 运算符
规则3: 项 → 数字 | 变量 | (表达式)
规则4: 数字 → 一个或多个数字字符
规则5: 变量 → 字母或下划线开头,后跟字母、数字或下划线
规则6: 运算符 → + | - | * | /

3.3.2 终结符和非终结符

定义

终结符 :文法中的基本符号,不能再被推导,对应语言的最小单位(如数字、变量、运算符)。

非终结符:可以由终结符和/或其他非终结符组成,表示语言的结构单元(如表达式、项、因子)。

理解*

  • 看完语法树的概念再回过头看这个会好理解点

3.3.3 句子

定义

符合文法规则的完整输入字符串,是解释器要处理的具体实例

理解*

  • 规则的实例。我们经常说句子通不通顺,实际上是汉语实例是否符合汉语语法。

例子

复制代码
表达式1: "3 4 +"           // 符合:项 项 运算符
表达式2: "3 4 5 * +"       // 符合:表达式 项 运算符
表达式3: "3 4 + 5 *"       // 符合:表达式 项 运算符
表达式4: "+ 3 4"           // 无效:运算符在前
表达式5: "3 4"             // 无效:缺少运算符
表达式6: "(3 + 4)"         // 无效:有括号

3.3.4语法树

定义

句子结构的树形表示,根节点是起始符号,内部节点是非终结符,叶子节点是终结符,反映了句子的层次结构。

例子

3+4 * 5

复制代码
    *
   / \
  +   5
 / \
3   4

3.2 实现思路

  1. 定义文法规则:分析要解释的语言,用形式文法描述其结构
  2. 设计抽象语法树节点:为每个文法规则创建对应的表达式类
  3. 实现终结符表达式:创建表示基本元素的类(如数字、变量)
  4. 实现非终结符表达式:创建表示组合结构的类(如运算表达式)
  5. 定义上下文环境:创建包含解释器所需全局信息的上下文类
  6. 构建解释器接口:定义统一的解释方法接口
  7. 实现解释逻辑:在每个表达式类中实现具体的解释操作
  8. 添加构建器或解析器:可选,用于将输入字符串转换为抽象语法树

3.3 UML类图

![[解释器模式_UML.png]]

角色说明

  • AbstractExpression:抽象表达式,声明解释操作的接口
  • TerminalExpression:终结符表达式,实现与文法终结符相关的解释操作
  • NonterminalExpression:非终结符表达式,实现与文法非终结符相关的解释操作
  • Context:上下文,包含解释器之外的全局信息
  • Client:构建抽象语法树并调用解释操作

3.4 代码示例

背景

  • 逆波兰表示法(项在前 运算符在后)的四则运算的四则运算
  • 核心是stack构建解释器逻辑
    表达式接口
java 复制代码
public interface Expression {  
    int interpret(Context context);  
}

上下文

java 复制代码
public class Context {  
    private Map<String, Integer> variables = new HashMap<>();  
  
    public void assign(String variable, int value) {  
        variables.put(variable, value);  
    }  
  
    public int getValue(String variable) {  
        Integer value = variables.get(variable);  
        if (value == null) {  
            throw new IllegalArgumentException("Variable not found: " + variable);  
        }  
        return value;  
    }  
}

终结符表达式

java 复制代码
// 变量
public class VariableExpression implements Expression {  
    private String name;  
  
    public VariableExpression(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        return context.getValue(name);  
    }  
}

// 数字
public class NumberExpression implements Expression {  
    private int number;  
  
    public NumberExpression(int number) {  
        this.number = number;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        return number;  
    }  
}

非终结符表达式

java 复制代码
// 加
public class AddExpression implements Expression {  
    private Expression left;  
    private Expression right;  
  
    public AddExpression(Expression left, Expression right) {  
        this.left = left;  
        this.right = right;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        return left.interpret(context) + right.interpret(context);  
    }  
}
// 减
public class SubtractExpression implements Expression {  
    private Expression left;  
    private Expression right;  
  
    public SubtractExpression(Expression left, Expression right) {  
        this.left = left;  
        this.right = right;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        return left.interpret(context) - right.interpret(context);  
    }  
}
// 乘
public class MultiplyExpression implements Expression {  
    private Expression left;  
    private Expression right;  
  
    public MultiplyExpression(Expression left, Expression right) {  
        this.left = left;  
        this.right = right;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        return left.interpret(context) * right.interpret(context);  
    }  
}
// 除
public class DivideExpression implements Expression {  
    private Expression left;  
    private Expression right;  
  
    public DivideExpression(Expression left, Expression right) {  
        this.left = left;  
        this.right = right;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        int divisor = right.interpret(context);  
        if (divisor == 0) {  
            throw new ArithmeticException("Division by zero");  
        }  
        return left.interpret(context) / divisor;  
    }  
}

表达式构建构器

java 复制代码
public class ExpressionBuilder {  
    public static Expression build(String expression) {  
  
        Stack<Expression> stack = new Stack<>();  
        String[] tokens = expression.split("\\s+");  
  
        for (String token : tokens) {  
            switch (token) {  
                case "+":  
                    Expression right = stack.pop();  
                    Expression left = stack.pop();  
                    stack.push(new AddExpression(left, right));  
                    break;  
                case "-":  
                    right = stack.pop();  
                    left = stack.pop();  
                    stack.push(new SubtractExpression(left, right));  
                    break;  
                case "*":  
                    right = stack.pop();  
                    left = stack.pop();  
                    stack.push(new MultiplyExpression(left, right));  
                    break;  
                case "/":  
                    right = stack.pop();  
                    left = stack.pop();  
                    stack.push(new DivideExpression(left, right));  
                    break;  
                default:  
                    // 尝试解析为数字,否则视为变量  
                    try {  
                        int number = Integer.parseInt(token);  
                        stack.push(new NumberExpression(number));  
                    } catch (NumberFormatException e) {  
                        stack.push(new VariableExpression(token));  
                    }  
                    break;  
            }  
        }  
        return stack.pop();  
    }  
}

测试

java 复制代码
public class Client {  
    static void main() {  
        // 创建上下文  
        Context context = new Context();  
        context.assign("a", 10);  
        context.assign("b", 5);  
        context.assign("c", 2);  
  
        Expression expression1 = ExpressionBuilder.build("a b c * +");  
        int result1 = expression1.interpret(context);  
        System.out.println("a + b * c = " + result1); // 输出: a + b * c = 20  
    }  
}

结果

复制代码
a + b * c = 20

4. 优缺点

4.1 优点

  • 高内聚:每个表达式类只关注自己的解释逻辑,职责单一
  • 易于扩展语法:添加新的表达式类即可扩展语言功能,符合开闭原则
  • 可复用性高:表达式类可以在不同的解释器中复用
  • 可维护性好:文法规则与解释逻辑分离,修改文法不影响解释器结构
  • 灵活性:可以通过组合不同的表达式创建复杂的语言结构
  • 良好的可读性:语法树直观反映了语言结构,便于理解和调试

4.2 缺点

  • 效率较低:复杂语法树需要递归解释,性能可能不佳
  • 类数量爆炸:文法复杂时,需要创建大量的表达式类
  • 可维护性挑战:对于复杂文法,类层次结构会变得复杂,难以维护
  • 不适合复杂文法:文法过于复杂时,解释器模式不是最佳选择
  • 稳定性受影响:文法频繁变化时,需要大量修改代码
  • 违反单一职责原则:文法规则和解释逻辑混合在表达式类中

参考:

相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码2 天前
嵌入式学习路线
学习
毛小茛2 天前
计算机系统概论——校验码
学习
babe小鑫2 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习