AIGC标识 解释器模式(Interpreter Pattern)

解释器模式(Interpreter Pattern)

一、核心思想

解释器模式为某种语言定义一套语法规则,然后用对象把这些规则表示成一棵抽象语法树(AST),再通过递归遍历这棵树来"解释"并执行表达式。

通俗来说:把"解释一句话"这件事拆成"每个语法成分各管一段",然后用树形组合把它们拼起来。

生活中的类比

想象你在计算 3 + (2 * 5)

  1. 你先看最外层——是加法,左边是 3,右边是 (2 * 5)
  2. 右边的括号里是乘法,左边是 2,右边是 5
  3. 你先算最底层(叶子节点)的数字,再逐层往上算

这棵树就是 AST,你计算的过程就是"解释"这棵树。

二、模式结构

角色 职责 Go 中的体现
AbstractExpression(抽象表达式) 声明 Interpret() 方法 interface
TerminalExpression(终结符表达式) 不可再分的叶子节点(数字、变量、关键字) 实现 interface 的 struct
NonterminalExpression(非终结符表达式) 可组合的内部节点(加法、与/或逻辑) 持有子 Expression 的 struct
Context(上下文) 存储解释过程中需要的全局信息(如变量映射) map 或自定义 struct
Client(客户端) 构造 AST 并调用 Interpret() main 或调用方

类关系示意

         ┌─────────────────────┐
         │  Expression (interface) │
         │  + Interpret(ctx) → int │
         └──────────┬──────────┘
                    │
      ┌─────────────┼──────────────┐
      │             │              │
  Terminal      Add(非终结)    Subtract(非终结)
  (Number)    left, right    left, right

三、关键设计要点

1. 递归组合是灵魂

每个非终结符表达式内部持有子 ExpressionInterpret() 调用时递归向下求值。这就是组合模式的天然契合点——AST 本身就是一棵递归树。

2. 终结符 vs 非终结符

  • 终结符:表达式树的叶子,不可再拆。如数字字面量 42、布尔值 true、变量引用 x
  • 非终结符:树的内部节点,组合多个子表达式。如加法 a + b、逻辑与 a AND b

3. Context 的作用

Context 负责传递"环境信息",最常见的是变量名→值的映射。终结符表达式从 Context 中查找变量值,非终结符表达式把 Context 往下传给子表达式。

4. 扩展性优势

新增一种运算符(如乘法)只需要新增一个 struct 实现 Expression 接口,完全不需要修改现有代码——符合开闭原则。

四、适用场景

场景 说明
数学表达式求值 计算器、公式引擎
规则引擎 "金额 > 100 AND 用户等级 = VIP" 这类业务规则
DSL 解析 领域特定语言的简单解释器
字符串模式匹配 布尔搜索 "hello AND (world OR earth)"
SQL/正则引擎 基于语法的查询解析(工业级实现更复杂)

注意:解释器模式不适合复杂语法——每条规则一个类,类数量会爆炸。真正的编译器/解释器会用更高效的方式(如编译成字节码再执行)。

五、与组合模式的关系

解释器模式可以看作组合模式 + 递归求值

  • 组合模式关注的是树形结构的统一操作(如文件系统遍历)
  • 解释器模式关注的是按语法规则递归求值

两者结构几乎一样,区别在于解释器给每个节点赋予了"解释"的语义。

六、Go 实现示例

下面的示例实现一个支持加减乘除四则运算的表达式解释器。

package main

import (
	"fmt"
	"strings"
)

// ============================================================
// 抽象表达式接口
// ============================================================

// Expression 是所有表达式节点的抽象接口。
// Context 是变量名→值的映射表。
type Expression interface {
	Interpret(ctx map[string]float64) float64
	String() string // 打印表达式结构,方便调试
}

// ============================================================
// 终结符表达式
// ============================================================

// Number 表示一个数字字面量,如 3.14
type Number struct {
	Value float64
}

func (n *Number) Interpret(_ map[string]float64) float64 {
	return n.Value
}

func (n *Number) String() string {
	return fmt.Sprintf("%g", n.Value)
}

// Variable 表示一个变量引用,如 x
type Variable struct {
	Name string
}

func (v *Variable) Interpret(ctx map[string]float64) float64 {
	val, ok := ctx[v.Name]
	if !ok {
		panic(fmt.Sprintf("未定义的变量: %s", v.Name))
	}
	return val
}

func (v *Variable) String() string {
	return v.Name
}

// ============================================================
// 非终结符表达式 —— 四则运算
// ============================================================

// Add 加法表达式: left + right
type Add struct {
	Left, Right Expression
}

func (a *Add) Interpret(ctx map[string]float64) float64 {
	return a.Left.Interpret(ctx) + a.Right.Interpret(ctx)
}

func (a *Add) String() string {
	return fmt.Sprintf("(%s + %s)", a.Left.String(), a.Right.String())
}

// Subtract 减法表达式: left - right
type Subtract struct {
	Left, Right Expression
}

func (s *Subtract) Interpret(ctx map[string]float64) float64 {
	return s.Left.Interpret(ctx) - s.Right.Interpret(ctx)
}

func (s *Subtract) String() string {
	return fmt.Sprintf("(%s - %s)", s.Left.String(), s.Right.String())
}

// Multiply 乘法表达式: left * right
type Multiply struct {
	Left, Right Expression
}

func (m *Multiply) Interpret(ctx map[string]float64) float64 {
	return m.Left.Interpret(ctx) * m.Right.Interpret(ctx)
}

func (m *Multiply) String() string {
	return fmt.Sprintf("(%s * %s)", m.Left.String(), m.Right.String())
}

// Divide 除法表达式: left / right
type Divide struct {
	Left, Right Expression
}

func (d *Divide) Interpret(ctx map[string]float64) float64 {
 divisor := d.Right.Interpret(ctx)
 if divisor == 0 {
 	panic("除数不能为零")
 }
 return d.Left.Interpret(ctx) / divisor
}

func (d *Divide) String() string {
	return fmt.Sprintf("(%s / %s)", d.Left.String(), d.Right.String())
}

// ============================================================
// 简易解析器 —— 将字符串解析成 AST
// 本例实现一个递归下降解析器,支持 +、-、*、/、括号、数字
// 文法:
//   expr   → term (('+' | '-') term)*
//   term   → factor (('*' | '/') factor)*
//   factor → NUMBER | VARIABLE | '(' expr ')'
// ============================================================

// Parser 递归下降解析器
type Parser struct {
	tokens []string
	pos    int
}

// Tokenize 将输入字符串分词
func Tokenize(input string) []string {
	// 在运算符和括号前后加空格,再按空白分割
	input = strings.ReplaceAll(input, "+", " + ")
	input = strings.ReplaceAll(input, "-", " - ")
	input = strings.ReplaceAll(input, "*", " * ")
	input = strings.ReplaceAll(input, "/", " / ")
	input = strings.ReplaceAll(input, "(", " ( ")
	input = strings.ReplaceAll(input, ")", " ) ")
	return strings.Fields(input)
}

// NewParser 创建解析器
func NewParser(input string) *Parser {
	return &Parser{tokens: Tokenize(input)}
}

// peek 查看当前 token
func (p *Parser) peek() string {
	if p.pos >= len(p.tokens) {
		return ""
	}
	return p.tokens[p.pos]
}

// consume 消费当前 token
func (p *Parser) consume() string {
	tok := p.peek()
	p.pos++
	return tok
}

// Parse 开始解析,返回表达式 AST 根节点
func (p *Parser) Parse() Expression {
	return p.parseExpr()
}

// parseExpr 解析加减法: expr → term (('+' | '-') term)*
func (p *Parser) parseExpr() Expression {
	expr := p.parseTerm()
	for {
		op := p.peek()
		if op == "+" {
			p.consume()
			right := p.parseTerm()
			expr = &Add{Left: expr, Right: right}
		} else if op == "-" {
			p.consume()
			right := p.parseTerm()
			expr = &Subtract{Left: expr, Right: right}
		} else {
			break
		}
	}
	return expr
}

// parseTerm 解析乘除法: term → factor (('*' | '/') factor)*
func (p *Parser) parseTerm() Expression {
	expr := p.parseFactor()
	for {
		op := p.peek()
		if op == "*" {
			p.consume()
			right := p.parseFactor()
			expr = &Multiply{Left: expr, Right: right}
		} else if op == "/" {
			p.consume()
			right := p.parseFactor()
			expr = &Divide{Left: expr, Right: right}
		} else {
			break
		}
	}
	return expr
}

// parseFactor 解析因子: NUMBER | VARIABLE | '(' expr ')'
func (p *Parser) parseFactor() Expression {
	tok := p.peek()
	if tok == "(" {
		p.consume()               // 消费 '('
		expr := p.parseExpr()     // 递归解析括号内表达式
		if p.peek() == ")" {
			p.consume() // 消费 ')'
		}
		return expr
	}
	// 尝试解析为数字
	var num float64
	if n, err := fmt.Sscanf(tok, "%g", &num); err == nil && n == 1 {
		p.consume()
		return &Number{Value: num}
	}
	// 否则视为变量
	p.consume()
	return &Variable{Name: tok}
}

// ============================================================
// 客户端使用
// ============================================================

func main() {
	// 场景1:纯数字表达式
	// 表达式: 3 + 5 * 2 = 13
	p1 := NewParser("3 + 5 * 2")
	ast1 := p1.Parse()
	ctx := map[string]float64{}
	fmt.Printf("表达式: %s\n", ast1.String())
	fmt.Printf("结果: %g\n\n", ast1.Interpret(ctx))

	// 场景2:带括号的表达式
	// 表达式: (3 + 5) * 2 = 16
	p2 := NewParser("(3 + 5) * 2")
	ast2 := p2.Parse()
	fmt.Printf("表达式: %s\n", ast2.String())
	fmt.Printf("结果: %g\n\n", ast2.Interpret(ctx))

	// 场景3:带变量的表达式
	// 表达式: x * y + 10,其中 x=3, y=4 → 22
	p3 := NewParser("x * y + 10")
	ast3 := p3.Parse()
	ctx3 := map[string]float64{"x": 3, "y": 4}
	fmt.Printf("表达式: %s\n", ast3.String())
	fmt.Printf("变量: x=3, y=4\n")
	fmt.Printf("结果: %g\n\n", ast3.Interpret(ctx3))

	// 场景4:复杂混合表达式
	// 表达式: (x + y) * (x - y) / 2,其中 x=10, y=3 → 45.5
	p4 := NewParser("(x + y) * (x - y) / 2")
	ast4 := p4.Parse()
	ctx4 := map[string]float64{"x": 10, "y": 3}
	fmt.Printf("表达式: %s\n", ast4.String())
	fmt.Printf("变量: x=10, y=3\n")
	fmt.Printf("结果: %g\n", ast4.Interpret(ctx4))
}

运行结果

表达式: (3 + (5 * 2))
结果: 13

表达式: ((3 + 5) * 2)
结果: 16

表达式: ((x * y) + 10)
变量: x=3, y=4
结果: 22

表达式: (((x + y) * (x - y)) / 2)
变量: x=10, y=3
结果: 45.5

七、优缺点总结

优点

  1. 易于扩展:新增运算符只需新增一个 struct,不改已有代码
  2. 语法与解释分离:语法规则用类型表示,解释逻辑在 Interpret() 中,清晰分离
  3. 天然递归:AST 的树形结构与递归求值完美契合

缺点

  1. 类爆炸:每条语法规则一个 struct,复杂语法导致大量类型
  2. 执行效率低:递归遍历 + 接口调用开销大,不适合高频执行
  3. 复杂语法维护难:真正复杂的语法更适合用 parser generator 生成

八、与其他模式的关系

对比项 区别
vs 组合模式 结构相同,但解释器给节点赋予了"求值"语义
vs 策略模式 策略是同一层面的替换选择;解释器是嵌套组合的语法树
vs 访问者模式 访问者把操作从对象结构中剥离;解释器把操作内聚在每个节点

九、学习小结

解释器模式是 GoF 23 种设计模式中使用频率最低的模式之一,但理解它对学习编译原理和 DSL 设计很有价值。本次实现了一个完整的四则运算表达式解释器,包含:

  • 抽象表达式接口(Expression
  • 终结符表达式(NumberVariable
  • 非终结符表达式(AddSubtractMultiplyDivide
  • 递归下降解析器(Parser)将字符串解析为 AST
  • Context 变量映射(map[string]float64
posted @ 2026-08-10 10:37  FfHUCisI  阅读(3)  评论(0)    收藏  举报