CS-Notes 设计模式精讲:解释器模式(Interpreter)的原理、类图与规则引擎示例
解释器模式(Interpreter Pattern)是 CS-Notes 仓库 [设计模式 - 解释器.md](https://gitcode.com/GitHub_Trending/cs/CS-Notes/blob/b70121d377cb6005eb65f12b098cd5decd905669/notes/设计模式 - 解释器.md?utm_source=gitcode_repo_files) 讲解的一种行为型设计模式:它为一门语言定义文法的对象化表示,并提供一个解释器来解释该文法描述的语言句子。本文以文档中"规则检验器(and / or 规则)"完整实现为主线,逐类拆解解析树的构建与递归求值过程,并梳理模式结构与 JDK 中的应用,帮助你在面试与日常设计中准确理解、复现和评价该模式。
一、模式意图:什么是解释器模式
GoF 对解释器模式的经典定义是:给定一门语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。CS-Notes 文档将 Intent 概括为:
为语言创建解释器,通常由语言的语法和语法分析来定义。
这句话揭示了模式的本质工作流:
- 先用类对象把一条文法规则(如
D And (A Or (B C)))静态地表示为一棵解析树 / 抽象语法树(AST); - 再通过每个节点上统一的
interpret(...)方法,对这棵树做递归求值,从而判断一段输入(文本)是否符合文法、能否得到解释结果。
解释器模式适合解决这样一类问题:某类问题重复出现、频率足够高,并且其解法可以抽象成一种简单语言的文法。典型的落地场景包括:
- 布尔规则 / 权限表达式校验(本文示例即属此类);
- SQL、正则、模板字符串等小规模专用语言的解析与执行;
- 数学表达式求值、配置 DSL(领域专用语言)解释等。
仓库将解释器归入"行为型"模式(见 [设计模式 - 目录.md](https://gitcode.com/GitHub_Trending/cs/CS-Notes/blob/b70121d377cb6005eb65f12b098cd5decd905669/notes/设计模式 - 目录.md?utm_source=gitcode_repo_files) 第三节),因为它关注的是文法对象之间如何协作完成"解释"这一职责分配:每个语法节点各自承担对自身子树的解释义务,再通过递归把结果向上汇总。
二、结构剖析:四个核心角色与 UML 类图
CS-Notes 文档为解释器模式绘制的 UML 类图如下:
结合类图与本仓库示例代码,模式包含以下角色:
| 角色 | 在类图中的职责 | 在本文示例中的对应 |
|---|---|---|
| AbstractExpression(抽象表达式) | 声明抽象的 interpret(Context) 接口,是所有语法节点的公共父类 |
抽象类 Expression,声明 interpret(String) |
| TerminalExpression(终结符表达式) | 表示文法中的终结符(字面量、变量),每个终结符都需要一个实例;实现"句子与终结符匹配"的解释逻辑 | TerminalExpression,封装 A、B、C、D 等字面量 |
| NonTerminalExpression(非终结符表达式) | 表示文法中的组合规则(运算符、复合结构),内部持有其他子表达式并递归调用其 interpret() |
AndExpression、OrExpression(由文档实现可推断,它们正是对非终结符 And / Or 的角色落地) |
| Context(上下文) | 保存解释器之外的一些全局信息,供解释过程读取 | 示例中直接简化为 String(文档明确说明"这里的 Context 指的是 String") |
| Client(客户端) | 构建(或由语法分析器生成)抽象语法树,并调用根节点的 interpret() 触发解释 |
下述 Client.buildInterpreterTree() |
值得注意的关键结构事实是:每个终结符需要对应一个 TerminalExpression 对象,而每个非终结符(And / Or 规则)对应一个持有子表达式的组合节点——整棵解析树是自相似的递归组合结构。因此从结构上看,解释器模式与仓库另一篇 [设计模式 - 组合.md](https://gitcode.com/GitHub_Trending/cs/CS-Notes/blob/b70121d377cb6005eb65f12b098cd5decd905669/notes/设计模式 - 组合.md?utm_source=gitcode_repo_files) 天然契合:AST 正是"叶子(终结符)+容器(非终结符)"的组合树。
三、规则检验器实现:从文法到代码的完整复现
仓库文档给出了一个可直接编译运行的规则检验器:它支持 and / or 两种规则,通过规则组合构建一棵解析树,用来检验一段文本是否满足树定义的规则。下面按类逐一拆解,并补齐必要的语义注释,代码可在 JDK 8 及以上版本直接编译运行。
3.1 抽象表达式:定义统一的解释接口
public abstract class Expression {
public abstract boolean interpret(String str);
}
Expression 扮演 AbstractExpression。这里把抽象方法的入参直接定为 String,正是"Context 即 String"这一简化设计的体现:解释所需的全部外部信息都被压缩进一段文本。
3.2 终结符表达式:判断文本中是否出现某字面量
public class TerminalExpression extends Expression {
private String literal = null;
public TerminalExpression(String str) {
literal = str;
}
public boolean interpret(String str) {
StringTokenizer st = new StringTokenizer(str);
while (st.hasMoreTokens()) {
String test = st.nextToken();
if (test.equals(literal)) {
return true;
}
}
return false;
}
}
TerminalExpression 持有唯一的字面量 literal。其解释逻辑很直观:用 java.util.StringTokenizer 把输入文本按空白字符切分成词元(token),只要存在一个词元与 literal 相等就返回 true,否则返回 false。
new StringTokenizer(str)采用默认分隔符(空格、制表符、换行等空白字符),等价于"把文本拆成一个个单词";- 当文本为空或不含任何目标词元时,循环自然结束并返回
false; - 工程提示:
StringTokenizer在 JDK 官方文档中被标记为仅保留用于兼容目的的遗留类,新代码建议改用String.split(...)或java.util.Scanner,但本示例用它表达"逐个词元匹配"的语义非常清晰,不影响对模式本身的理解。
3.3 非终结符表达式:AndExpression 与 OrExpression
public class AndExpression extends Expression {
private Expression expression1 = null;
private Expression expression2 = null;
public AndExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
public boolean interpret(String str) {
return expression1.interpret(str) && expression2.interpret(str);
}
}
public class OrExpression extends Expression {
private Expression expression1 = null;
private Expression expression2 = null;
public OrExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
public boolean interpret(String str) {
return expression1.interpret(str) || expression2.interpret(str);
}
}
AndExpression 与 OrExpression 是成对出现的复合节点,它们并不直接判定文本,而是各自持有两个子表达式,并把自己的解释逻辑定义为对子表达式的布尔组合:
AndExpression.interpret():只有当左右两个子表达式都解释为 true 时才返回 true(&&短路求值);OrExpression.interpret():只要左右任意一个子表达式为 true 即返回 true(||短路求值)。
正是这种"节点持有子节点 + 递归调用 interpret()"的写法,让整棵解析树具备了可组合、可无限嵌套的能力——任何一颗子树都可以作为更大表达式的一个操作数。
3.4 Client:手工构建解析树并触发解释
public class Client {
/**
* 构建解析树
*/
public static Expression buildInterpreterTree() {
// Literal
Expression terminal1 = new TerminalExpression("A");
Expression terminal2 = new TerminalExpression("B");
Expression terminal3 = new TerminalExpression("C");
Expression terminal4 = new TerminalExpression("D");
// B C
Expression alternation1 = new OrExpression(terminal2, terminal3);
// A Or (B C)
Expression alternation2 = new OrExpression(terminal1, alternation1);
// D And (A Or (B C))
return new AndExpression(terminal4, alternation2);
}
public static void main(String[] args) {
Expression define = buildInterpreterTree();
String context1 = "D A";
String context2 = "A B";
System.out.println(define.interpret(context1));
System.out.println(define.interpret(context2));
}
}
Client.buildInterpreterTree() 用自底向上的方式手工组装解析树,最终得到根表达式 D And (A Or (B Or C)),即逻辑式 D ∧ (A ∨ B ∨ C)。组装步骤为:
- 为四个字面量分别创建终结符节点
A、B、C、D; alternation1 = OrExpression(B, C),对应子表达式B ∨ C;alternation2 = OrExpression(A, alternation1),把结果扩展为A ∨ B ∨ C;- 根节点
AndExpression(D, alternation2),得到D ∧ (A ∨ B ∨ C)。
运行该 main 方法,输出为:
true
false
四、递归求值推演:解释器是如何"跑"起来的
规则检验器的解释过程,本质上是从根节点出发、沿解析树逐层下钻、再把布尔结果逐层回传的递归调用。以文本 "D A" 为例,define.interpret("D A") 的执行轨迹为:
- 根节点是
AndExpression,先解释terminal4(字面量D):"D A"分词得到{D, A},包含D→ 返回true; - 再解释右子树
alternation2(OrExpression):其左子terminal1(字面量A)在"D A"中找到A→ 返回true(||短路,无需再判断B ∨ C); - 根
AndExpression得到true && true→ 最终输出true。
再看文本 "A B":根节点先解释 terminal4(字面量 D),而 "A B" 中只有 {A, B}、没有 D → 立即返回 false;&& 短路求值使右子树不再被解释,整体输出 false。这一结果同时验证了:
- 非终结符节点不关心子表达式的内部细节,只按自己的规则组合子结果;
- 解释器的调用链复杂度与树的深度相关,树越深、组合越多,单次求值的间接层就越多,这也是下文要讨论的模式代价之一。
五、使用边界与工程权衡
从实现可以直观总结解释器模式的优点与代价,这与 CS-Notes 笔记面向面试复盘的知识结构一致:
优点
- 易于改变与扩展文法:每条文法规则对应一个表达式类,新增一种规则只需新增一个表达式子类并接入组合逻辑,符合开闭原则;
- 文法易实现:只要定义好表达式接口,终结符与非终结符的解释逻辑都可以用极少的代码表达;
- 天然贴近文法:类结构与文法产生式一一对应,可读性和可维护性都较强。
代价与边界
- 类数量随文法复杂度爆炸:文档结构明确要求"每个终结符都需要一个 TerminalExpression",文法规则一多,表达式类会迅速膨胀;
- 执行效率偏低:每次求值都要递归遍历解析树、频繁做字符串分词与匹配,间接层开销明显。文档与示例均未给出任何性能数据,从结构上即可推断——它并不适合高频、大数据量场景;
- 适用前提苛刻:只有当文法足够简单、且执行性能不敏感时才值得手写解释器;对于表达式语法繁多或规模较大的语言,工程上应改用语法分析器生成器(如 ANTLR、JavaCC)等专门工具生成真正的解析器,而非逐个手写表达式类。
因此,面试与实践中对解释器模式最稳妥的定位是:理解它解决"语言文法对象化 + 递归解释"问题的思路,并清醒地认识到它只适用于小规模语言。
六、JDK 与 Java 生态中的解释器模式身影
CS-Notes 文档在 JDK 一节列出了一批标准库中的解释器模式应用,理解它们有助于把抽象的模式概念锚定到真实的 Java 类库中:
java.util.Pattern:正则表达式引擎的核心。正则串先被"编译"为内部状态机表示,再对目标串执行匹配——这与"把一种语言的文法表示为可解释的对象"是同一思想的工程实现。仓库另有 正则表达式.md 笔记对正则的语法细节做系统梳理,可对照阅读;java.text.Format的所有子类:如DecimalFormat、MessageFormat、SimpleDateFormat,它们都接收一个格式模式串(pattern),运行时解析该串并对值做格式化输出,属于典型的"按文法解释输入";java.text.Normalizer:执行 Unicode 文本规范化(如 NFC / NFD),可理解为按 Unicode 规范化文法对字符序列做解释转换;javax.el.ELResolver:Java EE 表达式语言(EL)的解析器抽象,用于把 EL 表达式字符串解释为实际的对象与属性访问。
这几类组件规模不一,但都符合解释器模式"为语言建立对象化文法表示"的内核,可作为论述"该模式在标准库中的位置"时的具体论据。
七、延伸阅读
解释器模式只是 CS-Notes 行为型模式家族中的一员,本仓库的设计模式资料组织如下,便于继续深入:
- [设计模式 - 目录.md](https://gitcode.com/GitHub_Trending/cs/CS-Notes/blob/b70121d377cb6005eb65f12b098cd5decd905669/notes/设计模式 - 目录.md?utm_source=gitcode_repo_files):完整的设计模式索引,按创建型、行为型、结构型三组分类,解释器位于行为型分组;
- 设计模式.md:全部模式合并卷,其中第 3 节即解释器模式;
- [设计模式 - 组合.md](https://gitcode.com/GitHub_Trending/cs/CS-Notes/blob/b70121d377cb6005eb65f12b098cd5decd905669/notes/设计模式 - 组合.md?utm_source=gitcode_repo_files):与解释器模式结构同源(树形递归组合),理解组合模式后再看解析树会更容易;
- 面向对象思想.md:多态、递归等支撑解释器落地的 OO 基础。
一言以蔽之:解释器模式用"每个语法符号一个类"的文法对象化,换来了文法的易扩展与代码的清晰表达,其实现灵魂是组合树上的递归求值——把握住"文法 → 解析树 → 递归 interpret"这条主线,你就能在任何需要解释执行的小语言场景中自如地复现它。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
