解释器模式
大约 11 分钟约 3377 字
解释器模式
简介
解释器(Interpreter)给定一个语言,定义其文法的一种表示,并定义一个解释器来解释语言中的句子。理解解释器模式,有助于构建 DSL(领域特定语言)、规则引擎和表达式解析器。
解释器模式的思想来源于编译原理 —— 给定一种语言的文法描述,为其构造一个解释器。在 GoF 的 23 种设计模式中,解释器模式是最"学术"的一个,它直接反映了编译原理中抽象语法树(AST)的概念。每个文法规则对应一个类,解释过程就是 AST 的递归遍历。
在实际开发中,解释器模式常用于构建小型 DSL(领域特定语言)、规则引擎、配置表达式解析器等。但需要注意,当文法规则复杂时,使用 ANTLR、Irony 等解析器生成工具比手写解释器更高效。
特点
结构分析
UML 类图
+--------------------+
| IExpression | <-- 抽象表达式
+--------------------+
| +Interpret(ctx) |
+--------------------+
^
|
+----------------+----------------+
| | |
+----------+ +-----------+ +----------+
| Terminal | | NonTermi | | Context |
| Expr | | nal Expr | | |
+----------+ +-----------+ +----------+
| 常量/变量| | AND/OR/ | | 变量存储 |
+----------+ | 比较/算术 | +----------+
+-----------+语法树示例
表达式: (age > 18) AND (role == "admin")
AND
/ \
GT EQ
/ \ / \
age 18 role "admin"实现
简易规则引擎
// 上下文
public class EvaluationContext
{
public Dictionary<string, object> Variables { get; } = new();
public object Get(string name) => Variables.TryGetValue(name, out var v) ? v : throw new KeyNotFoundException(name);
public void Set(string name, object value) => Variables[name] = value;
}
// 抽象表达式
public interface IExpression { object Evaluate(EvaluationContext ctx); }
// 终结符 — 变量
public class VariableExpression : IExpression
{
private readonly string _name;
public VariableExpression(string name) => _name = name;
public object Evaluate(EvaluationContext ctx) => ctx.Get(_name);
}
// 终结符 — 常量
public class ConstantExpression : IExpression
{
private readonly object _value;
public ConstantExpression(object value) => _value = value;
public object Evaluate(EvaluationContext ctx) => _value;
}
// 非终结符 — 比较
public class EqualsExpression : IExpression
{
private readonly IExpression _left, _right;
public EqualsExpression(IExpression left, IExpression right) { _left = left; _right = right; }
public object Evaluate(EvaluationContext ctx) => Equals(_left.Evaluate(ctx), _right.Evaluate(ctx));
}
public class GreaterThanExpression : IExpression
{
private readonly IExpression _left, _right;
public GreaterThanExpression(IExpression left, IExpression right) { _left = left; _right = right; }
public object Evaluate(EvaluationContext ctx) =>
Convert.ToDouble(_left.Evaluate(ctx)) > Convert.ToDouble(_right.Evaluate(ctx));
}
// 非终结符 — 逻辑
public class AndExpression : IExpression
{
private readonly IExpression _left, _right;
public AndExpression(IExpression left, IExpression right) { _left = left; _right = right; }
public object Evaluate(EvaluationContext ctx) =>
(bool)_left.Evaluate(ctx) && (bool)_right.Evaluate(ctx);
}
public class OrExpression : IExpression
{
private readonly IExpression _left, _right;
public OrExpression(IExpression left, IExpression right) { _left = left; _right = right; }
public object Evaluate(EvaluationContext ctx) =>
(bool)_left.Evaluate(ctx) || (bool)_right.Evaluate(ctx);
}
public class NotExpression : IExpression
{
private readonly IExpression _expr;
public NotExpression(IExpression expr) => _expr = expr;
public object Evaluate(EvaluationContext ctx) => !(bool)_expr.Evaluate(ctx);
}
// 规则引擎
public class RuleEngine
{
public string Name { get; }
public IExpression Condition { get; }
public Action<EvaluationContext> Action { get; }
public RuleEngine(string name, IExpression condition, Action<EvaluationContext> action)
{ Name = name; Condition = condition; Action = action; }
public bool Evaluate(EvaluationContext ctx)
{
if ((bool)Condition.Evaluate(ctx)) { Action(ctx); return true; }
return false;
}
}
// 使用 — 构建规则: age > 18 AND role == "admin"
var ctx = new EvaluationContext();
ctx.Set("age", 25);
ctx.Set("role", "admin");
ctx.Set("loginCount", 5);
var rule = new RuleEngine(
"管理员权限检查",
new AndExpression(
new GreaterThanExpression(new VariableExpression("age"), new ConstantExpression(18)),
new EqualsExpression(new VariableExpression("role"), new ConstantExpression("admin"))
),
c => Console.WriteLine("授予管理员权限")
);
rule.Evaluate(ctx); // 授予管理员权限简易表达式解析器
// 递归下降解析器 — 简单算术
public class SimpleParser
{
private readonly string _input;
private int _pos;
public SimpleParser(string input) { _input = input.Replace(" ", ""); _pos = 0; }
public double Parse() => ParseExpression();
// expression = term (('+' | '-') term)*
private double ParseExpression()
{
var result = ParseTerm();
while (_pos < _input.Length && (_input[_pos] == '+' || _input[_pos] == '-'))
{
var op = _input[_pos++];
var right = ParseTerm();
result = op == '+' ? result + right : result - right;
}
return result;
}
// term = factor (('*' | '/') factor)*
private double ParseTerm()
{
var result = ParseFactor();
while (_pos < _input.Length && (_input[_pos] == '*' || _input[_pos] == '/'))
{
var op = _input[_pos++];
var right = ParseFactor();
result = op == '*' ? result * right : result / right;
}
return result;
}
// factor = NUMBER | '(' expression ')'
private double ParseFactor()
{
if (_input[_pos] == '(')
{
_pos++; // skip '('
var result = ParseExpression();
_pos++; // skip ')'
return result;
}
return ParseNumber();
}
private double ParseNumber()
{
var start = _pos;
while (_pos < _input.Length && (char.IsDigit(_input[_pos]) || _input[_pos] == '.')) _pos++;
return double.Parse(_input[start.._pos]);
}
}
Console.WriteLine(new SimpleParser("2 + 3 * 4").Parse()); // 14
Console.WriteLine(new SimpleParser("(2 + 3) * 4").Parse()); // 20
Console.WriteLine(new SimpleParser("10 / 2 + 3").Parse()); // 8实战:价格计算表达式
在电商系统中,商品价格经常需要根据规则动态计算(折扣、税费、运费等)。使用解释器模式可以构建灵活的价格表达式。
public class PricingContext
{
public Dictionary<string, decimal> Variables { get; } = new();
public decimal Get(string name) => Variables.TryGetValue(name, out var v) ? v : 0m;
public void Set(string name, decimal value) => Variables[name] = value;
}
// 价格表达式接口
public interface IPriceExpression
{
decimal Evaluate(PricingContext ctx);
}
// 常量价格
public class ConstantPrice : IPriceExpression
{
private readonly decimal _value;
public ConstantPrice(decimal value) => _value = value;
public decimal Evaluate(PricingContext ctx) => _value;
}
// 变量引用
public class VariablePrice : IPriceExpression
{
private readonly string _name;
public VariablePrice(string name) => _name = name;
public decimal Evaluate(PricingContext ctx) => ctx.Get(_name);
}
// 加法
public class AddPrice : IPriceExpression
{
private readonly IPriceExpression _left, _right;
public AddPrice(IPriceExpression left, IPriceExpression right) { _left = left; _right = right; }
public decimal Evaluate(PricingContext ctx) => _left.Evaluate(ctx) + _right.Evaluate(ctx);
}
// 乘法(用于折扣)
public class MultiplyPrice : IPriceExpression
{
private readonly IPriceExpression _left, _right;
public MultiplyPrice(IPriceExpression left, IPriceExpression right) { _left = left; _right = right; }
public decimal Evaluate(PricingContext ctx) => _left.Evaluate(ctx) * _right.Evaluate(ctx);
}
// 最大值
public class MaxPrice : IPriceExpression
{
private readonly IPriceExpression _left, _right;
public MaxPrice(IPriceExpression left, IPriceExpression right) { _left = left; _right = right; }
public decimal Evaluate(PricingContext ctx) => Math.Max(_left.Evaluate(ctx), _right.Evaluate(ctx));
}
// 使用
// 价格 = (单价 * 数量 * 折扣) + 运费
var pricing = new AddPrice(
new MultiplyPrice(
new MultiplyPrice(
new VariablePrice("unitPrice"),
new VariablePrice("quantity")
),
new VariablePrice("discount")
),
new VariablePrice("shipping")
);
var ctx = new PricingContext();
ctx.Set("unitPrice", 99.9m);
ctx.Set("quantity", 3);
ctx.Set("discount", 0.85m);
ctx.Set("shipping", 10m);
Console.WriteLine($"最终价格: {pricing.Evaluate(ctx):C}"); // 254.68C# Expression Tree 实现
使用表达式树构建动态规则
using System.Linq.Expressions;
// 表达式树比手写 AST 更高效 — 可编译为委托
public class ExpressionRuleEngine
{
private readonly Dictionary<string, Func<EvaluationContext, bool>> _rules = new();
// 动态构建规则表达式
public void AddRule(string name, string expression)
{
// expression 示例: "Age > 18 && Role == \"admin\""
var parameter = Expression.Parameter(typeof(EvaluationContext), "ctx");
var body = ParseExpression(expression, parameter);
var lambda = Expression.Lambda<Func<EvaluationContext, bool>>(body, parameter);
_rules[name] = lambda.Compile();
}
private Expression ParseExpression(string expr, ParameterExpression param)
{
// 简化示例:实际项目中应使用完整的词法分析
var parts = expr.Split("&&");
Expression? combined = null;
foreach (var part in parts)
{
var trimmed = part.Trim();
Expression condition;
if (trimmed.Contains(">="))
{
var tokens = trimmed.Split(">=");
var left = Expression.PropertyOrField(param, tokens[0].Trim());
var right = Expression.Constant(Convert.ChangeType(tokens[1].Trim(), left.Type));
condition = Expression.GreaterThanOrEqual(left, right);
}
else if (trimmed.Contains(">"))
{
var tokens = trimmed.Split('>');
var left = Expression.PropertyOrField(param, tokens[0].Trim());
var right = Expression.Constant(Convert.ChangeType(tokens[1].Trim(), left.Type));
condition = Expression.GreaterThan(left, right);
}
else if (trimmed.Contains("=="))
{
var tokens = trimmed.Split("==");
var left = Expression.PropertyOrField(param, tokens[0].Trim());
var rightStr = tokens[1].Trim().Trim('"');
condition = Expression.Equal(left, Expression.Constant(rightStr));
}
else continue;
combined = combined == null ? condition : Expression.AndAlso(combined, condition);
}
return combined ?? Expression.Constant(true);
}
public bool Evaluate(string name, EvaluationContext context)
{
if (_rules.TryGetValue(name, out var rule))
return rule(context);
return false;
}
}
// 使用 — 编译后的委托比递归 AST 求值快 10-100 倍
var engine = new ExpressionRuleEngine();
engine.AddRule("成年人检查", "Age > 18");
engine.AddRule("管理员检查", "Age > 18 && Role == \"admin\"");
var ctx = new EvaluationContext();
ctx.Set("Age", 25);
ctx.Set("Role", "admin");
Console.WriteLine(engine.Evaluate("成年人检查", ctx)); // true
Console.WriteLine(engine.Evaluate("管理员检查", ctx)); // true动态查询构建器
// 利用表达式树构建动态查询条件
public class DynamicQueryBuilder<T> where T : class
{
private Expression<Func<T, bool>>? _filter;
public DynamicQueryBuilder<T> Where(string propertyName, object value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.PropertyOrField(parameter, propertyName);
var constant = Expression.Constant(value);
var comparison = Expression.Equal(property, constant);
var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);
if (_filter == null)
_filter = lambda;
else
{
var combinedBody = Expression.AndAlso(_filter.Body, lambda.Body);
_filter = Expression.Lambda<Func<T, bool>>(combinedBody, _filter.Parameters);
}
return this;
}
public DynamicQueryBuilder<T> GreaterThan(string propertyName, object value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.PropertyOrField(parameter, propertyName);
var constant = Expression.Constant(Convert.ChangeType(value, property.Type));
var comparison = Expression.GreaterThan(property, constant);
var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);
if (_filter == null)
_filter = lambda;
else
{
var combinedBody = Expression.AndAlso(_filter.Body, lambda.Body);
_filter = Expression.Lambda<Func<T, bool>>(combinedBody, _filter.Parameters);
}
return this;
}
public Expression<Func<T, bool>>? Build() => _filter;
}
// 使用
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public bool IsActive { get; set; }
}
var query = new DynamicQueryBuilder<Product>()
.Where("IsActive", true)
.GreaterThan("Price", 50)
.GreaterThan("Stock", 0)
.Build();
// 可以直接用于 EF Core 或 LINQ
// var results = dbContext.Products.Where(query).ToList();解释器模式的边界与陷阱
性能问题与优化
// 问题:递归求值对深 AST 性能差
// 解决方案1:编译为委托(Expression Tree)
// 解决方案2:缓存编译结果
public class CachedRuleEngine
{
private readonly ConcurrentDictionary<string, Func<EvaluationContext, bool>> _cache = new();
public bool Evaluate(string expression, EvaluationContext context)
{
var compiled = _cache.GetOrAdd(expression, expr =>
{
// 首次使用时编译,后续直接调用缓存
return CompileExpression(expr);
});
return compiled(context);
}
private Func<EvaluationContext, bool> CompileExpression(string expression)
{
// 解析并编译为委托
Console.WriteLine($"编译表达式: {expression}");
return ctx => true; // 简化示例
}
}
// 问题2:循环引用导致无限递归
// expression = "A && B" where B references A again
// 解决:在 AST 构建时检测循环引用
public class CircularReferenceDetector
{
private readonly HashSet<string> _visiting = new();
private readonly HashSet<string> _visited = new();
public bool HasCycle(IExpression expression)
{
if (expression is VariableExpression ve)
{
if (_visiting.Contains(ve.Name)) return true;
_visiting.Add(ve.Name);
// 递归检查引用的表达式...
_visiting.Remove(ve.Name);
}
return false;
}
}安全性考量
// 安全的规则执行 — 沙箱限制
public class SandboxedRuleEngine
{
private static readonly HashSet<string> AllowedProperties = new()
{
"age", "role", "level", "score", "status"
};
private static readonly TimeSpan MaxExecutionTime = TimeSpan.FromSeconds(1);
public bool EvaluateSafely(IExpression expression, EvaluationContext ctx)
{
// 1. 检查表达式是否只引用允许的属性
ValidateExpression(expression);
// 2. 限制执行时间
var cts = new CancellationTokenSource(MaxExecutionTime);
try
{
var task = Task.Run(() => (bool)expression.Evaluate(ctx), cts.Token);
return task.Result;
}
catch (AggregateException)
{
Console.WriteLine("规则执行超时");
return false;
}
}
private void ValidateExpression(IExpression expression)
{
if (expression is VariableExpression ve)
{
if (!AllowedProperties.Contains(ve.Name))
throw new SecurityException($"不允许访问属性: {ve.Name}");
}
// 递归验证子表达式...
}
}解释器模式 vs 其他方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手写解释器 | 灵活、可控 | 文法复杂时维护困难 | 简单 DSL(<10 条规则) |
| ANTLR | 功能强大、支持复杂文法 | 学习成本、外部依赖 | 复杂语言、编译器 |
| Expression Tree | .NET 原生、可编译为委托 | 表达式有限 | 动态查询、规则 |
| 嵌入式语言 (Lua/JS) | 完整语言能力 | 集成复杂 | 游戏脚本、复杂规则 |
最佳实践
- 文法规则少时使用:当规则数量 < 10 时手写解释器可行,规则多时使用解析器生成工具。
- 使用 Expression Tree 优化:C# 的 Expression Tree 可以编译为委托,执行效率更高。
- 缓存编译结果:如果表达式会被重复执行,缓存编译后的委托。
- 分离解析和执行:先解析为 AST,再执行 AST,两个阶段独立。
- 提供友好的错误信息:解析失败时给出清晰的错误位置和原因。
优点
缺点
总结
解释器模式用类层次表示文法规则,通过 AST 递归求值。适合规则引擎(条件判断)、简单查询语言、配置表达式等小型 DSL 场景。复杂语法建议使用 ANTLR 等解析器生成工具。C# 中可利用 Expression Tree 实现更高效的解释器。建议在文法简单、规则频繁变化的业务场景使用解释器模式。
解释器模式的本质价值在于:当你需要在应用中嵌入一种小型语言或规则系统时,解释器模式提供了一种从文法到代码的直接映射方式。每条文法规则对应一个类,解释过程就是 AST 的递归遍历。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《解释器模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《解释器模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《解释器模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《解释器模式》最大的收益和代价分别是什么?
