news 2026/6/10 10:24:14

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式学习(21) 23-20 解释器模式

文章目录

  • 0. 个人感悟
  • 1. 概念
  • 2. 适配场景
    • 2.1 适合的场景
    • 2.2 常见场景举例
  • 3. 实现方法
    • 3.1 概念理解
      • 3.1.1 文法
      • 3.3.2 终结符和非终结符
      • 3.3.3 句子
      • 3.3.4语法树
    • 3.2 实现思路
    • 3.3 UML类图
    • 3.4 代码示例
  • 4. 优缺点
    • 4.1 优点
    • 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构建解释器逻辑
    表达式接口
publicinterfaceExpression{intinterpret(Contextcontext);}

上下文

publicclassContext{privateMap<String,Integer>variables=newHashMap<>();publicvoidassign(Stringvariable,intvalue){variables.put(variable,value);}publicintgetValue(Stringvariable){Integervalue=variables.get(variable);if(value==null){thrownewIllegalArgumentException("Variable not found: "+variable);}returnvalue;}}

终结符表达式

// 变量publicclassVariableExpressionimplementsExpression{privateStringname;publicVariableExpression(Stringname){this.name=name;}@Overridepublicintinterpret(Contextcontext){returncontext.getValue(name);}}// 数字publicclassNumberExpressionimplementsExpression{privateintnumber;publicNumberExpression(intnumber){this.number=number;}@Overridepublicintinterpret(Contextcontext){returnnumber;}}

非终结符表达式

// 加publicclassAddExpressionimplementsExpression{privateExpressionleft;privateExpressionright;publicAddExpression(Expressionleft,Expressionright){this.left=left;this.right=right;}@Overridepublicintinterpret(Contextcontext){returnleft.interpret(context)+right.interpret(context);}}// 减publicclassSubtractExpressionimplementsExpression{privateExpressionleft;privateExpressionright;publicSubtractExpression(Expressionleft,Expressionright){this.left=left;this.right=right;}@Overridepublicintinterpret(Contextcontext){returnleft.interpret(context)-right.interpret(context);}}// 乘publicclassMultiplyExpressionimplementsExpression{privateExpressionleft;privateExpressionright;publicMultiplyExpression(Expressionleft,Expressionright){this.left=left;this.right=right;}@Overridepublicintinterpret(Contextcontext){returnleft.interpret(context)*right.interpret(context);}}// 除publicclassDivideExpressionimplementsExpression{privateExpressionleft;privateExpressionright;publicDivideExpression(Expressionleft,Expressionright){this.left=left;this.right=right;}@Overridepublicintinterpret(Contextcontext){intdivisor=right.interpret(context);if(divisor==0){thrownewArithmeticException("Division by zero");}returnleft.interpret(context)/divisor;}}

表达式构建构器

publicclassExpressionBuilder{publicstaticExpressionbuild(Stringexpression){Stack<Expression>stack=newStack<>();String[]tokens=expression.split("\\s+");for(Stringtoken:tokens){switch(token){case"+":Expressionright=stack.pop();Expressionleft=stack.pop();stack.push(newAddExpression(left,right));break;case"-":right=stack.pop();left=stack.pop();stack.push(newSubtractExpression(left,right));break;case"*":right=stack.pop();left=stack.pop();stack.push(newMultiplyExpression(left,right));break;case"/":right=stack.pop();left=stack.pop();stack.push(newDivideExpression(left,right));break;default:// 尝试解析为数字,否则视为变量try{intnumber=Integer.parseInt(token);stack.push(newNumberExpression(number));}catch(NumberFormatExceptione){stack.push(newVariableExpression(token));}break;}}returnstack.pop();}}

测试

publicclassClient{staticvoidmain(){// 创建上下文Contextcontext=newContext();context.assign("a",10);context.assign("b",5);context.assign("c",2);Expressionexpression1=ExpressionBuilder.build("a b c * +");intresult1=expression1.interpret(context);System.out.println("a + b * c = "+result1);// 输出: a + b * c = 20}}

结果

a + b * c = 20

4. 优缺点

4.1 优点

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

4.2 缺点

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

参考:

  • 韩顺平 Java设计模式
  • 行无际 解释器模式(Interpreter Pattern)——自定义语言的实现
  • kosamino 设计模式之解释器模式(Interpreter)详解及代码示例
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 11:11:44

Java实现无人健身房物联网系统解析

以下从技术架构深度解析、核心模块实现逻辑、安全与性能优化实践三个维度&#xff0c;对基于Java的无人共享健身房物联网系统进行系统性拆解&#xff1a;一、技术架构深度解析&#xff1a;分层设计与微服务协同表现层多端适配机制UniApp框架&#xff1a;通过Vue语法实现“一次开…

作者头像 李华
网站建设 2026/6/9 17:24:53

强烈安利10个AI论文工具,专科生搞定毕业论文不求人!

强烈安利10个AI论文工具&#xff0c;专科生搞定毕业论文不求人&#xff01; AI工具&#xff0c;让论文写作不再难 对于专科生来说&#xff0c;毕业论文的撰写往往是一道难以跨越的难关。面对繁重的写作任务、复杂的格式要求以及对学术规范的不熟悉&#xff0c;很多学生感到无从…

作者头像 李华
网站建设 2026/5/27 12:35:00

汽车制造行业KindEditor如何处理设计图WORD粘贴?

企业网站Word粘贴和导入功能开发全记录 需求分析与技术评估 作为福建某软件公司的前端工程师&#xff0c;我最近接到一个企业网站后台管理系统的功能升级需求。客户希望在现有的KindEditor编辑器中增加Word粘贴和文档导入功能&#xff0c;同时支持微信公众号内容粘贴并自动处…

作者头像 李华
网站建设 2026/6/2 18:25:53

免费Nano Banana 制作PPT,SpeedAI 智能体一句话生成

大家好&#xff0c;这里是K姐。 一个帮你追踪最新AI应用的女子&#xff01; 2026了&#xff0c;不会还有打工人述职汇报是自己哼哧哼哧手搓 PPT 吧&#xff1f; 想用 AI 做 PPT 省时间&#xff0c;很多人第一反应就是 Nano Banana。确实不少大佬用做 Nano Banana 的 PPT&…

作者头像 李华
网站建设 2026/6/2 16:43:52

Flutter 三端应用实战:OpenHarmony 简易文本首字母提取器开发指南

一、为什么需要“简易文本首字母提取器”&#xff1f; 在 OpenHarmony 的内容摘要、笔记整理与快速索引场景中&#xff0c;“首字符”具有独特的信息密度价值&#xff1a; 学生&#xff1a;从课堂笔记中快速提取关键词首字&#xff0c;构建记忆锚点&#xff1b;程序员&#x…

作者头像 李华