设计模式学习(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 缺点

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

参考:

相关推荐
zpedu2 小时前
什么是CISA证书?有啥作用
学习
ooope2 小时前
求资源网站?涵盖影视、学习、软件等多领域的资源?
学习
2601_949720262 小时前
flutter_for_openharmony手语学习app实战+个人中心实现
学习·flutter
冰语竹2 小时前
Android学习-随笔(安装后设置路径)
android·学习
芯思路2 小时前
STM32开发学习笔记之七【LCD显示图片】
笔记·stm32·学习
问道飞鱼2 小时前
【大模型学习】提示词工程(Prompt Engineering)技术深度报告
学习·prompt·提示词
hssfscv2 小时前
Javaweb学习笔记——后端实战7 springAOP
笔记·后端·学习
来两个炸鸡腿2 小时前
【Datawhale组队学习202601】Base-NLP task06 大模型训练与量化
人工智能·学习·自然语言处理
bylander2 小时前
【AI学习】TM Forum自智网络L4级标准体系
人工智能·学习·智能体·自动驾驶网络