給定一種語言,為它的文法定義一個表示法,再定義一個直譯器用這個表示法來解釋語言中的句子。做法是每條文法規則對應一個類別,句子由這些類別的實例組成一棵抽象語法樹(AST),靠遞迴呼叫
interpret解釋整棵樹。
🧠 When to Use
TIP
訊號:某類問題反覆出現,且每個實例都能寫成「一種簡單語言的句子」(正規表達式、瀏覽器解 HTML、機器人指令「前進 10 步、左轉 90 度」)——與其為每個實例寫一個演算法,不如寫一次直譯器來解釋它們。
- 有一個文法簡單的語言要解釋,且句子可表示成抽象語法樹。
- 效率不是首要考量(最高效的直譯器通常會先把 parse tree 轉成別的形式,如狀態機)。
- 這類「小語言」的句子多到值得抽象出一套文法來統一處理。
⚖️ Structure & Variants
經典結構 — 每條規則一個類別,組成 AST
// AbstractExpression:所有節點共有的 interpret
fun interface Expr { fun interpret(ctx: Map<String, Int>): Int }
// TerminalExpression:遞迴的基本情況(終結符)
class Num(val value: Int) : Expr {
override fun interpret(ctx: Map<String, Int>) = value
}
class Var(val name: String) : Expr {
override fun interpret(ctx: Map<String, Int>) = ctx.getValue(name)
}
// NonterminalExpression:規則 R ::= R1 op R2,持有子表達式、遞迴解釋
class Add(val l: Expr, val r: Expr) : Expr {
override fun interpret(ctx: Map<String, Int>) = l.interpret(ctx) + r.interpret(ctx)
}
class Mul(val l: Expr, val r: Expr) : Expr {
override fun interpret(ctx: Map<String, Int>) = l.interpret(ctx) * r.interpret(ctx)
}
// Client 建 AST(此處直接手組)→ 傳入 Context → 從根呼叫 interpret
val ast = Add(Var("x"), Mul(Num(2), Var("y"))) // x + 2*y
ast.interpret(mapOf("x" to 5, "y" to 3)) // 11Map<String,Int>就是 Context(存直譯器的全域狀態,如變數表);Num/Var是終結符,Add/Mul是非終結符,interpret沿樹遞迴。
Interpreter 不管 parsing
- 模式只負責「解釋 AST」,不負責「把字串解析成 AST」。建樹可由手寫的 recursive descent parser、table-driven parser,或客戶端直接組。
- 實務最常見的組合:用 parser generator 處理 parsing,用 Interpreter 模式處理 AST 的解釋——不必全部手寫。
和三個近親的關係
⚠️ Misuse & Anti-patterns
- 文法一複雜就類別爆炸:每條規則至少一個類別,規則一多,類別階層龐大到難維護。這時該換 parser generator,而不是硬套此模式。
- 拿它來 parsing:模式不處理解析。想用它把字串轉成樹,是誤用了它的職責。
- 為「只會出現一次」的解析需求開一整套文法:句子種類不夠多、不夠反覆,直接寫個函式更划算——先 YAGNI。
🔑 Takeaways
- 一條文法規則 → 一個類別;句子 → 由這些類別組成的 AST;解釋 → 沿樹遞迴呼叫
interpret。 - 好處是擴展文法容易(加規則=加類別、用繼承定變體);代價是複雜文法會類別爆炸。
- 只負責解釋、不負責解析;常與 Composite(樹)、Visitor(多種解釋)、Flyweight(共享終結符)合用。是 GoF 23 個模式裡最偏門、最該謹慎採用的一個。
No notes yet — jot your takeaways or Q&A here.