引子:你不懂老闆的心#
老闆難得找小菜談話,誇獎了一句「在公司表現格外出色,要繼續好好努力」,又評價同事梅星是個「普通員工」。
大鳥揭密職場潛台詞:
- 老闆私下大誇某員工 → 多半意味著「最近有更多的任務需要你完成」
- 老闆說某員工「普通」→ 其實是說這員工不夠聰明、能力不足
「要是有一個翻譯機或解釋器就好了」——這正好對應解釋器模式。
解釋器模式#
解釋器模式(Interpreter Pattern):給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。[DP]
何時使用:
- 一種特定類型的問題發生頻率足夠高,值得將該問題的各個實例表述為一個簡單語言中的句子
- 構建一個解釋器,透過解釋這些句子來解決該問題[DP]
經典應用:
- 正規表達式(Regular Expression):與其為每個比對需求都寫一個演算法函式,不如使用通用的正規表達式來解釋執行
- 瀏覽器:解釋 HTML 文法,把標記轉成網頁
- 機器人控制:用「向前走 10 步、左轉 90 度」這樣的語言取代呼叫具體方法
結構#
- AbstractExpression(抽象表達式):宣告一個抽象的解釋操作
- TerminalExpression(終結符表達式):實作與文法中終結符相關聯的解釋操作;文法中每個終結符都有一個對應的具體終結表達式
- NonterminalExpression(非終結符表達式):為文法中的非終結符實作解釋操作;遞迴呼叫各符號的實例變數
- Context:包含解釋器之外的一些全域資訊
classDiagram
class Client
class Context
class AbstractExpression {
<<abstract>>
+Interpret(Context)*
}
class TerminalExpression
class NonterminalExpression
Client --> AbstractExpression
Client --> Context
AbstractExpression <|-- TerminalExpression
AbstractExpression <|-- NonterminalExpression
NonterminalExpression o--> AbstractExpressionabstract class AbstractExpression
{
public abstract void Interpret(Context context);
}
class TerminalExpression : AbstractExpression { /* ... */ }
class NonterminalExpression : AbstractExpression { /* ... */ }範例:迷你音樂解釋器#
模擬 QBASIC 的 PLAY 語句。文法規則:
O表示音階:O 1低音、O 2中音、O 3高音P表示休止符C D E F G A B表示 Do-Re-Mi-Fa-So-La-Ti- 數字表示節拍(1 = 一拍、0.5 = 半拍……)
- 所有字母與數字都用半角空格分開
例如「上海灘」第一句「浪奔」可寫成:O 2 E 0.5 G 0.5 A 3。
演奏內容(Context)#
class PlayContext
{
public string PlayText { get; set; }
}抽象表達式#
abstract class Expression
{
public void Interpret(PlayContext context)
{
if (context.PlayText.Length == 0) return;
string playKey = context.PlayText.Substring(0, 1);
context.PlayText = context.PlayText.Substring(2);
double playValue = Convert.ToDouble(
context.PlayText.Substring(0, context.PlayText.IndexOf(" ")));
context.PlayText = context.PlayText.Substring(context.PlayText.IndexOf(" ") + 1);
Excute(playKey, playValue);
}
public abstract void Excute(string key, double value);
}終結符:音符與音階#
class Note : Expression
{
public override void Excute(string key, double value)
{
string note = key switch
{
"C" => "1", "D" => "2", "E" => "3", "F" => "4",
"G" => "5", "A" => "6", "B" => "7",
_ => ""
};
Console.Write($"{note} ");
}
}
class Scale : Expression
{
public override void Excute(string key, double value)
{
string scale = (int)value switch
{
1 => "低音", 2 => "中音", 3 => "高音",
_ => ""
};
Console.Write($"{scale} ");
}
}客戶端#
PlayContext context = new PlayContext();
context.PlayText = "O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 ...";
Expression expression = null;
while (context.PlayText.Length > 0)
{
string str = context.PlayText.Substring(0, 1);
switch (str)
{
case "O":
expression = new Scale();
break;
case "C": case "D": case "E": case "F":
case "G": case "A": case "B": case "P":
expression = new Note();
break;
}
expression.Interpret(context);
}擴展:新增「音速」文法#
要增加 T 1000(每節拍一秒)這樣的速度設置:
- 加一個
Speed表達式類 - 在客戶端
switch加一個case "T"
class Speed : Expression
{
public override void Excute(string key, double value)
{
string speed = value < 500 ? "快速"
: value >= 1000 ? "慢速"
: "中速";
Console.Write($"{speed} ");
}
}客戶端的
switch還可進一步用簡單工廠 + 反射重構,達到不改客戶端就能擴展的目的(見第 15 章)。
解釋器模式的優缺點#
優點#
- 容易改變和擴展文法——使用類別表示文法規則,可用繼承擴展
- 容易實作文法——抽象語法樹中各節點類的實作大體類似
說白了,解釋器模式就是「用迷你語言來表現程式要解決的問題,以迷你語言寫成迷你程式」。
缺點#
解釋器模式為文法中的每一條規則至少定義了一個類——包含許多規則的文法可能難以管理和維護。
當文法非常複雜時,建議改用其他技術(語法分析程式或編譯器生成器)。