C++ · 设计模式 (领域规则=Interpreter<解析器>)
1. 概述
GoF分类
解释器模式属于行为型设计模式
官方定义
“给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。”
核心目的
将特定领域的问题表示为语言的句子,然后构建解释器来解释这些句子,从而解决问题。
主要问题
-
需要解释和执行特定领域语言或规则
-
频繁变化的业务规则需要灵活表示
-
避免将领域规则硬编码到业务逻辑中
C++典型应用
-
数学表达式解析和计算
-
正则表达式引擎
-
SQL查询解析
-
编译器实现
-
业务规则引擎
关键特征
-
领域特定语言(DSL)的表示
-
语法树的构建
-
递归解释执行
-
可扩展的文法规则
2. 核心思想
设计哲学
将领域规则表示为语言结构,通过面向对象的方式构建可组合的解释器组件,实现规则的灵活组合和解释执行。
面向对象原则
-
开闭原则:通过新增解释器类来扩展文法,而非修改现有代码
-
单一职责:每个解释器类只负责一种语法规则的解释
-
组合优于继承:通过对象组合构建复杂表达式
C++特殊考虑
-
使用智能指针管理语法树节点生命周期
-
异常安全保证资源释放
-
利用多态实现解释器接口
-
考虑表达式求值的性能优化
工作流程
3. 适用场景
应该使用解释器模式的场景
1. 数学表达式计算器
// 构建表达式: (5+3)*2
auto expr = std::make_shared<Multiply>(
std::make_shared<Add>(new Number(5), new Number(3)),
new Number(2)
);
int result = expr->interpret(); // 输出16
2. 业务规则引擎
// 年龄>18 且 信用分>700
auto rule = std::make_shared<And>(
std::make_shared<GreaterThan>(new Age(), new Number(18)),
std::make_shared<GreaterThan>(new CreditScore(), new Number(700))
);
bool approved = rule->interpret(context);
3. 简单查询语言
// WHERE name='John' AND (age>30 OR status='active')
auto condition = std::make_shared<And>(
std::make_shared<Equals>(new Field("name"), new Literal("John")),
std::make_shared<Or>(
std::make_shared<GreaterThan>(new Field("age"), new Literal(30)),
std::make_shared<Equals>(new Field("status"), new Literal("active"))
)
);
不适合使用解释器模式的场景
1. 复杂文法规则
当文法非常复杂时,解释器模式会导致类数量爆炸,应考虑使用解析器生成器(如ANTLR)。
2. 高性能场景
解释器模式的递归解释执行效率较低,对性能要求高的场景应考虑编译执行方案。
3. 简单一次性解析
如果只需要解析一次且规则简单,直接使用过程式代码可能更直接高效。
4. UML类图
5. 结构
模式参与者
AbstractExpression(抽象表达式)
-
角色:定义解释操作的通用接口
-
职责:声明interpret()抽象方法,所有具体表达式必须实现
TerminalExpression(终结符表达式)
-
实现:实现与文法中的终结符相关的解释操作
-
特殊行为:不再包含其他表达式,是递归的终止点
NonterminalExpression(非终结符表达式)
-
实现:实现文法规则的解释操作
-
特殊行为:通常包含其他表达式作为子节点,递归解释
Context(上下文)
-
角色:包含解释器需要的全局信息
-
职责:存储和访问表达式中的变量值
Client(客户端)
-
使用方式:构建表示特定句子的语法树
-
交互:调用顶层表达式的interpret()方法
协作流程
-
客户端构建语法树,组合Terminal和Nonterminal表达式
-
客户端调用顶层表达式的interpret()方法
-
每个非终结符表达式递归调用其子表达式的interpret()
-
终结符表达式执行实际解释操作
-
解释过程中通过Context对象共享状态
-
最终返回解释结果
6. 代码示例
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
// 上下文类,存储变量值
class Context {
public:
void setVariable(const std::string& var, int value) {
variables_[var] = value;
}
int getVariable(const std::string& var) const {
auto it = variables_.find(var);
if (it != variables_.end()) {
return it->second;
}
return 0;
}
private:
std::unordered_map<std::string, int> variables_;
};
// 抽象表达式接口
class Expression {
public:
virtual ~Expression() = default;
virtual int interpret(Context& context) const = 0;
};
// 终结符表达式:变量
class VariableExpression : public Expression {
public:
explicit VariableExpression(std::string name) : name_(std::move(name)) {}
int interpret(Context& context) const override {
return context.getVariable(name_);
}
private:
std::string name_;
};
// 终结符表达式:数值常量
class NumberExpression : public Expression {
public:
explicit NumberExpression(int value) : value_(value) {}
int interpret(Context& context) const override {
return value_;
}
private:
int value_;
};
// 非终结符表达式:加法
class AddExpression : public Expression {
public:
AddExpression(std::unique_ptr<Expression> left, std::unique_ptr<Expression> right)
: left_(std::move(left)), right_(std::move(right)) {}
int interpret(Context& context) const override {
return left_->interpret(context) + right_->interpret(context);
}
private:
std::unique_ptr<Expression> left_;
std::unique_ptr<Expression> right_;
};
// 非终结符表达式:减法
class SubtractExpression : public Expression {
public:
SubtractExpression(std::unique_ptr<Expression> left, std::unique_ptr<Expression> right)
: left_(std::move(left)), right_(std::move(right)) {}
int interpret(Context& context) const override {
return left_->interpret(context) - right_->interpret(context);
}
private:
std::unique_ptr<Expression> left_;
std::unique_ptr<Expression> right_;
};
// 非终结符表达式:乘法
class MultiplyExpression : public Expression {
public:
MultiplyExpression(std::unique_ptr<Expression> left, std::unique_ptr<Expression> right)
: left_(std::move(left)), right_(std::move(right)) {}
int interpret(Context& context) const override {
return left_->interpret(context) * right_->interpret(context);
}
private:
std::unique_ptr<Expression> left_;
std::unique_ptr<Expression> right_;
};
int main() {
Context context;
context.setVariable("x", 10);
context.setVariable("y", 5);
context.setVariable("z", 42);
// 构建表达式: (x + y) * z
auto expr = std::make_unique<MultiplyExpression>(
std::make_unique<AddExpression>(
std::make_unique<VariableExpression>("x"),
std::make_unique<VariableExpression>("y")
),
std::make_unique<VariableExpression>("z")
);
int result = expr->interpret(context);
std::cout << "Result: " << result << std::endl; // 输出 (10 + 5) * 42 = 630
return 0;
}
7. 运行结果
Result: 630
解释说明:
-
程序创建了一个Context并设置了三个变量:x=10, y=5, z=42
-
构建了表示(x + y) * z的语法树
-
解释器递归解释表达式:
-
先解释x+y得到15
-
然后解释z得到42
-
最后计算15 * 42得到630
- 输出验证了解释器正确解释了表达式
8. 应用场景
实际项目应用
C++标准库
- 正则表达式库中的模式匹配实现
Boost库
- Spirit库中的解析器组合子实现
游戏开发
-
AI行为树的条件表达式
-
游戏技能效果的计算公式
系统编程
-
配置文件的条件解析
-
网络协议的消息过滤规则
行业特定应用
嵌入式系统
-
设备配置规则的解析执行
-
传感器数据处理规则
高性能计算
-
数学公式的符号计算
-
数据过滤条件的解释执行
图形编程
-
着色器语言的解释执行
-
图像处理管道的条件组合
网络编程
-
路由规则的匹配和解释
-
防火墙规则的解析执行
9. 变体
主要变体实现
1. 基于模板的表达树
template <typename T>
class Expression {
public:
virtual T interpret(Context&) const = 0;
};
class IntAdd : public Expression<int> {
// 实现特定类型的解释器
};
2. 带缓存的解释器
class CachedExpression : public Expression {
public:
int interpret(Context& context) const override {
if (!cached_) {
cached_ = expr_->interpret(context);
}
return *cached_;
}
private:
std::unique_ptr<Expression> expr_;
mutable std::optional<int> cached_;
};
3. 访问者模式组合
class ExpressionVisitor {
public:
virtual void visit(NumberExpression&) = 0;
virtual void visit(AddExpression&) = 0;
// ...其他表达式类型
};
class InterpreterVisitor : public ExpressionVisitor {
// 实现解释逻辑
};
变体对比表格
| 变体名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础解释器 | 简单直接 | 性能较低 | 简单规则解释 |
| 模板表达式 | 类型安全 | 代码膨胀 | 需要强类型检查 |
| 缓存解释器 | 性能优化 | 内存消耗 | 重复解释相同表达式 |
| 访问者组合 | 分离算法 | 复杂度高 | 需要多种解释方式 |
10. 解释器模式 vs. 其他模式
与相似模式的区别
解释器模式 vs. 组合模式
-
解释器模式:关注表达式的解释执行
-
组合模式:关注对象的树形结构组织
解释器模式 vs. 访问者模式
-
解释器模式:内置解释逻辑
-
访问者模式:外部定义操作逻辑
选择指南
-
选择解释器模式当:需要解释领域特定语言
-
选择组合模式当:只需要管理层次结构
-
选择访问者模式当:需要在结构上执行多种不同操作
11. 解释器模式的优缺点
优点分析
1. 易于扩展文法
通过新增表达式类即可扩展语言文法,符合开闭原则。
2. 实现简单文法
对简单文法,实现直接且易于理解。
3. 分离语法和解释
将语法表示与解释逻辑分离,提高可维护性。
缺点分析
1. 复杂文法类爆炸
复杂文法会导致大量小类,增加系统复杂度。
2. 性能开销
递归解释执行比直接代码执行效率低。
3. 难以维护复杂文法
文法规则变化可能涉及多个类的修改。
12. 什么时候不要使用解释器模式?
明确的反对信号
1. 文法非常复杂
考虑使用成熟的解析器生成工具如Yacc/Bison。
2. 性能关键路径
考虑将解释器转换为编译器或直接编码实现。
3. 一次性简单解析
直接编写解析代码可能更简单高效。
反模式警示
1. 滥用解释器处理复杂文法
导致类数量爆炸,难以维护。
2. 忽略性能影响
在关键路径使用解释器导致性能瓶颈。
3. 硬编码语法规则
违背了模式的可扩展性初衷。
13. 总结
核心价值总结
解释器模式为解决领域特定语言解释问题提供了优雅的面向对象解决方案,特别适合需要灵活表示和执行领域规则的场景。
最佳实践建议
-
保持文法简单
-
考虑性能优化变体
-
合理使用智能指针管理资源
-
为复杂文法考虑解析器生成器
进阶学习路径
-
学习编译原理相关技术
-
研究Boost.Spirit库实现
-
实践更复杂的DSL实现
-
探索解释器模式与访问者模式的组合使用
更多推荐

所有评论(0)