1. 行为型模式里最容易被人放在一起比较的两兄弟
事情得从一次代码评审说起。当时组里有个同事在处理一份配置文件的解析逻辑,代码里写了一大堆 if-else 去判断不同的命令关键词,然后在另一个模块里又写了一堆游标逻辑去逐个遍历结果集。评到一半,另一个同事随口说了句:"这不就是解释器模式吗?下面那个是迭代器模式吧?"大家都点头,但又都说不清楚这两个东西到底差在哪——除了都是行为型设计模式之外。
这个场景我见过太多次了。设计模式的书里,解释器模式和迭代器模式常常被安排在同一章,因为它们的名字都有点"动作感",感觉都是"在处理什么东西"。但很多人学着学着就会发现一个问题:解释器模式好像很难用到,迭代器模式又好像天天在写但自己没意识到。实际上,这两者虽然同属于行为型设计模式,但一个是在制造语言,一个是在规划路径,解决问题的维度完全不同。搞混了,轻则设计出来的代码结构别扭,重则在错误的方向上加抽象,把本来能解决的问题搞成一堆永远不会被调用的类。
这篇文章我想把这两个模式从头到尾拆开讲一遍,包括各自的本质问题、典型结构、代码示例、适合的场景,以及一套我认为比较实用的选型判断框架。如果你在学设计模式、准备面试,或者正在纠结"我这里到底该用哪个模式",这篇文章可以给你一个相对清晰的参考。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解释器模式:它解决的是"如何定义一个语言并解释执行"的问题
2.1 从一份简单的规则表达式说起
先说解释器模式。它的 GoF 定义是:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
这个定义太绕了。我换个说法:解释器模式就是让你能自己定义一套"小语言",然后写一套程序来读懂并执行这套语言写的"句子"。
举个例子。假设你在做一个日志分析系统,运营人员经常会问:"把昨天访问量超过 1000 且来自移动端的请求都筛出来"。如果每个这样的需求都要改代码,你迟早会被运营的 50 个类似需求逼疯。这时候你可能会想到,我能不能定义一种简单的查询语法?
code复制type=request AND count>1000 AND source=mobile
然后让运营自己在输入框里填这种表达式,你的系统去解析它、执行它,把结果返回。这时候问题就来了:怎么把一个类似 count>1000 的字符串转换成程序里的逻辑判断?怎么处理 AND 和 OR 的组合?怎么嵌套括号?
这种场景就是解释器模式的主场。你定义一套语法规则(文法),再为每条规则写一个解释器类。每个类代表语法树上的一个节点,节点之间互相组合,最终形成一棵能表达任意复杂表达式的树。
2.2 解释器模式的核心结构:语法树就是一张类图
解释器模式的类结构非常有特点,它把文法规则直接映射成了类结构。最基本的是这四个角色:
- 抽象表达式(AbstractExpression):声明一个 interpret(Context) 方法,是所有节点的父类。
- 终结符表达式(TerminalExpression):语法树中的叶子节点,不可再分。比如
count>1000里的1000、mobile,或者一个表示"数字"的节点。 - 非终结符表达式(NonterminalExpression):语法树中的分支节点,包含子表达式。比如
AND、OR、>, 这类节点需要递归调用子节点的 interpret 方法。 - 上下文(Context):存放语法解析过程中的全局信息,比如变量值、中间结果等。
写个最简化的 Java 示例。先定义一个表达式接口:
java复制public interface Expression {
boolean interpret(Map<String, String> context);
}
然后定义终结符表达式,比如一个"判断字段值等于什么"的节点:
java复制public class EqualsExpression implements Expression {
private String field;
private String value;
public EqualsExpression(String field, String value) {
this.field = field;
this.value = value;
}
@Override
public boolean interpret(Map<String, String> context) {
return value.equals(context.get(field));
}
}
再定义非终结符表达式,比如 AND 组合节点:
java复制public class AndExpression implements Expression {
private Expression left;
private Expression right;
public AndExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean interpret(Map<String, String> conte
