Skip to content

解释器模式(Interpreter)

前言

解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器使用这个表示来解释语言中的句子。它是 DSL(领域特定语言)、规则引擎、表达式求值的核心模式。本文将详细讲解解释器模式的核心原理及实际应用。


一、核心概念

1.1 定义

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

1.2 核心角色

角色说明
抽象表达式(AbstractExpression)声明 interpret 方法
终结符表达式(TerminalExpression)解释最小单元(变量、常量)
非终结符表达式(NonterminalExpression)组合表达式(与、或、加、减)
上下文(Context)全局信息,变量存储
客户端(Client)构建语法树并解释

1.3 BNF 文法示例

表达式 ::= 加法 | 减法 | 变量 | 数字
加法   ::= 表达式 '+' 表达式
减法   ::= 表达式 '-' 表达式
变量   ::= [a-z]+
数字   ::= [0-9]+

二、代码实现

2.1 基础实现:布尔表达式解释器

php
<?php
// 上下文:变量存储
class Context
{
    private array $variables = [];

    public function assign(string $name, bool $value): void
    {
        $this->variables[$name] = $value;
    }

    public function lookup(string $name): bool
    {
        if (!array_key_exists($name, $this->variables)) {
            throw new RuntimeException("Variable {$name} not defined");
        }
        return $this->variables[$name];
    }
}

// 抽象表达式
interface BooleanExpression
{
    public function interpret(Context $context): bool;
}

// 终结符:变量
class VariableExpression implements BooleanExpression
{
    public function __construct(private string $name) {}

    public function interpret(Context $context): bool
    {
        return $context->lookup($this->name);
    }
}

// 终结符:常量
class ConstantExpression implements BooleanExpression
{
    public function __construct(private bool $value) {}

    public function interpret(Context $context): bool
    {
        return $this->value;
    }
}

// 非终结符:与操作
class AndExpression implements BooleanExpression
{
    public function __construct(
        private BooleanExpression $left,
        private BooleanExpression $right
    ) {}

    public function interpret(Context $context): bool
    {
        return $this->left->interpret($context) && $this->right->interpret($context);
    }
}

// 非终结符:或操作
class OrExpression implements BooleanExpression
{
    public function __construct(
        private BooleanExpression $left,
        private BooleanExpression $right
    ) {}

    public function interpret(Context $context): bool
    {
        return $this->left->interpret($context) || $this->right->interpret($context);
    }
}

// 非终结符:非操作
class NotExpression implements BooleanExpression
{
    public function __construct(private BooleanExpression $expr) {}

    public function interpret(Context $context): bool
    {
        return !$this->expr->interpret($context);
    }
}

// 使用
// 表达式:(x AND y) OR (NOT z)
$expr = new OrExpression(
    new AndExpression(new VariableExpression('x'), new VariableExpression('y')),
    new NotExpression(new VariableExpression('z'))
);

$ctx = new Context();
$ctx->assign('x', true);
$ctx->assign('y', false);
$ctx->assign('z', false);

echo 'Result: ' . ($expr->interpret($ctx) ? 'true' : 'false') . PHP_EOL;
// (T AND F) OR (NOT F) = F OR T = true

2.2 实际案例:算术表达式求值

php
<?php
// 上下文:变量存储
class MathContext
{
    private array $variables = [];

    public function setVariable(string $name, float $value): void
    {
        $this->variables[$name] = $value;
    }

    public function getVariable(string $name): float
    {
        if (!array_key_exists($name, $this->variables)) {
            throw new RuntimeException("Unknown: {$name}");
        }
        return $this->variables[$name];
    }
}

// 抽象表达式
interface Expression
{
    public function interpret(MathContext $context): float;
}

// 终结符:数字
class NumberExpression implements Expression
{
    public function __construct(private float $value) {}

    public function interpret(MathContext $context): float
    {
        return $this->value;
    }
}

// 终结符:变量
class VariableExpression implements Expression
{
    public function __construct(private string $name) {}

    public function interpret(MathContext $context): float
    {
        return $context->getVariable($this->name);
    }
}

// 非终结符:加法
class AddExpression implements Expression
{
    public function __construct(
        private Expression $left,
        private Expression $right
    ) {}

    public function interpret(MathContext $context): float
    {
        return $this->left->interpret($context) + $this->right->interpret($context);
    }
}

// 非终结符:减法
class SubtractExpression implements Expression
{
    public function __construct(
        private Expression $left,
        private Expression $right
    ) {}

    public function interpret(MathContext $context): float
    {
        return $this->left->interpret($context) - $this->right->interpret($context);
    }
}

// 非终结符:乘法
class MultiplyExpression implements Expression
{
    public function __construct(
        private Expression $left,
        private Expression $right
    ) {}

    public function interpret(MathContext $context): float
    {
        return $this->left->interpret($context) * $this->right->interpret($context);
    }
}

// 非终结符:除法
class DivideExpression implements Expression
{
    public function __construct(
        private Expression $left,
        private Expression $right
    ) {}

    public function interpret(MathContext $context): float
    {
        $divisor = $this->right->interpret($context);
        if ($divisor == 0) {
            throw new RuntimeException('Division by zero');
        }
        return $this->left->interpret($context) / $divisor;
    }
}

// 使用:构建语法树 (x + 5) * (y - 2)
$expression = new MultiplyExpression(
    new AddExpression(new VariableExpression('x'), new NumberExpression(5)),
    new SubtractExpression(new VariableExpression('y'), new NumberExpression(2))
);

$ctx = new MathContext();
$ctx->setVariable('x', 10);
$ctx->setVariable('y', 8);

echo 'Result: ' . $expression->interpret($ctx) . PHP_EOL; // (10+5) * (8-2) = 90

2.3 实际案例:规则引擎

php
<?php
// 上下文:用户信息
class UserContext
{
    public function __construct(
        public int $age,
        public int $vipLevel,
        public float $purchaseAmount,
        public int $registeredDays
    ) {}
}

// 抽象规则
interface Rule
{
    public function interpret(UserContext $ctx): bool;
}

// 终结符:年龄判断
class AgeRule implements Rule
{
    public function __construct(
        private int $min,
        private int $max
    ) {}

    public function interpret(UserContext $ctx): bool
    {
        return $ctx->age >= $this->min && $ctx->age <= $this->max;
    }
}

// 终结符:VIP 等级判断
class VipLevelRule implements Rule
{
    public function __construct(private int $minLevel) {}

    public function interpret(UserContext $ctx): bool
    {
        return $ctx->vipLevel >= $this->minLevel;
    }
}

// 终结符:消费金额判断
class PurchaseAmountRule implements Rule
{
    public function __construct(private float $minAmount) {}

    public function interpret(UserContext $ctx): bool
    {
        return $ctx->purchaseAmount >= $this->minAmount;
    }
}

// 非终结符:AND
class AndRule implements Rule
{
    /** @param Rule[] $rules */
    public function __construct(private array $rules) {}

    public function interpret(UserContext $ctx): bool
    {
        foreach ($this->rules as $rule) {
            if (!$rule->interpret($ctx)) {
                return false;
            }
        }
        return true;
    }
}

// 非终结符:OR
class OrRule implements Rule
{
    /** @param Rule[] $rules */
    public function __construct(private array $rules) {}

    public function interpret(UserContext $ctx): bool
    {
        foreach ($this->rules as $rule) {
            if ($rule->interpret($ctx)) {
                return true;
            }
        }
        return false;
    }
}

// 非终结符:NOT
class NotRule implements Rule
{
    public function __construct(private Rule $rule) {}

    public function interpret(UserContext $ctx): bool
    {
        return !$this->rule->interpret($ctx);
    }
}

// 使用:构建规则
// 规则:(年龄 18-65 AND VIP>=3) OR (消费 >= 10000)
$rule = new OrRule([
    new AndRule([new AgeRule(18, 65), new VipLevelRule(3)]),
    new PurchaseAmountRule(10000),
]);

$user1 = new UserContext(25, 4, 500);  // 满足第一个条件
$user2 = new UserContext(30, 1, 15000); // 满足第二个条件
$user3 = new UserContext(70, 5, 500);   // 都不满足

echo 'User1 eligible: ' . ($rule->interpret($user1) ? 'true' : 'false') . PHP_EOL; // true
echo 'User2 eligible: ' . ($rule->interpret($user2) ? 'true' : 'false') . PHP_EOL; // true
echo 'User3 eligible: ' . ($rule->interpret($user3) ? 'true' : 'false') . PHP_EOL; // false

三、适用场景

场景说明
DSL领域特定语言
规则引擎业务规则评估
表达式求值计算器、公式
查询语言SQL 解析
正则表达式模式匹配
配置解析配置文件解释

四、优缺点分析

优点缺点
易于扩展文法复杂文法类爆炸
灵活组合规则性能较差
符合开闭原则不适合复杂语法
易于实现简单 DSL调试困难
与组合模式结合维护成本高

五、常见踩坑与问题排查

5.1 文法过复杂

php
<?php
// 问题:复杂文法导致表达式类过多
// 解决:使用解析器生成器(如 ANTLR、PHPEG.js)

// 简化:用访问者模式替代解释器
class Evaluator
{
    public function evaluate(ASTNode $node): float
    {
        switch ($node->type) {
            case 'Number':
                return $node->value;
            case 'Add':
                return $this->evaluate($node->left) + $this->evaluate($node->right);
            // ...
        }
    }
}

5.2 性能问题

php
<?php
// 问题:每次解释都遍历语法树,性能差
// 解决:编译为闭包,缓存结果
class CompiledExpression
{
    /** @var callable(Context): bool */
    private $fn;

    public function __construct(BooleanExpression $expr)
    {
        // 编译为闭包
        $this->fn = fn(Context $ctx) => $expr->interpret($ctx);
    }

    public function evaluate(Context $ctx): bool
    {
        return ($this->fn)($ctx);
    }
}

5.3 递归过深

php
<?php
// 问题:嵌套表达式过深导致栈溢出
// 解决:使用迭代代替递归,或限制深度
class SafeInterpreter
{
    private int $maxDepth = 100;
    private int $currentDepth = 0;
    private MathContext $context;

    public function interpret(Expression $expr): float
    {
        if (++$this->currentDepth > $this->maxDepth) {
            throw new RuntimeException('Max depth exceeded');
        }
        $result = $expr->interpret($this->context);
        $this->currentDepth--;
        return $result;
    }
}

六、优化方案与进阶

6.1 结合解析器

php
<?php
// 解析器:将字符串转为语法树
class ExpressionParser
{
    public function parse(string $input): Expression
    {
        $tokens = $this->tokenize($input);
        return $this->parseExpression($tokens);
    }

    private function tokenize(string $input): array
    {
        preg_match_all('/\d+|[+\-*\/()]|[a-z]+/', $input, $matches);
        return $matches[0];
    }

    private function parseExpression(array $tokens): Expression
    {
        // 简化:递归下降解析
        // ...
        return new NumberExpression(0);
    }
}

// 使用
$parser = new ExpressionParser();
$expr = $parser->parse('3 + 5 * 2');
$result = $expr->interpret(new MathContext());

6.2 字节码解释

php
<?php
// 编译为字节码,再解释执行
class BytecodeInterpreter
{
    private array $code = [];

    public function compile(Expression $expr): void
    {
        // 编译为字节码
    }

    public function execute(Context $ctx): float
    {
        // 解释字节码
        $stack = [];
        foreach ($this->code as $op) {
            switch ($op) {
                case 0x01:
                    array_push($stack, 1);
                    break; // PUSH 1
                // ...
            }
        }
        return $stack[0];
    }
}

七、全文总结

解释器模式的核心是 定义文法表示,递归解释语言中的句子

核心要点

  1. 抽象表达式声明 interpret,终结符表示最小单元,非终结符组合表达式
  2. 适用于 DSL、规则引擎、表达式求值等场景
  3. 复杂文法会导致类爆炸,考虑使用访问者模式或解析器生成器
  4. 性能敏感场景需编译为函数或字节码
  5. 是编译原理在设计模式中的应用