為方便讀者,本文已添加至索引:
寫在前面
“我剛寫了個小程序,需要你來參與下。”我把MM叫到我的電腦旁,“來把下面這條命令打進去,這是個練習打(Pian)符(ni)號(de)的小程序,看看你能不能全部打正確”。
[*_]_7@1_9@1/(_5@0_3@0)*2/((_4@0)_2$1)_$2^/$1+(_7@0)*2/_$1_6$3/$3_2$3/_3$3_3@0/_5$3
MM詫異地看看我,然后可憐巴巴地坐到屏幕前,對着鍵盤一個字一個字地敲。她打字超慢的,各種符號都是用兩個食指打進去的。她打着打着,說想哭了。我趕忙告訴她,加油,全打正確了有驚喜。
終於,她敲下了回車鍵。映入眼簾的是:

_ _
* * * *
* * * *
* * *
* *
* *
* *
*
她忽然就開心起來,問我這個是怎么回事。我告訴她,“這說明你剛才的命令輸對了,電腦按照命令畫出了它~。要不再接再厲,試試下面這個更有挑戰性的?”
[#*]_@1*5/_(_2@1*2)/$0_9@1*6_(_@1*4)*2_3@1*5/$0_6$0_2$0*2+(_$0)*3/$0_5$0_3$0*3_3@1*8/(_2@0*2)_4@0+$3_3$3*2+(_@0*2)_2$3/$4_4@0_$3_2$3_4@0*3_3$3_2$3/@0*7_5@0*5_4$3_7@0*6
……
是不是讀者你也想知道這個會是什么結果了吧?這當然跟我們今天的主題,解釋器模式有關啦!會在示例一節展開。
其實,我們平時接觸到的解釋器模式相關的實際例子並不太多,最常見的莫過於正則表達式了。它通過規定一系列的文法規則,並給予了相關的解釋操作,從而成為處理字符串的通用強大的工具。首先我們了解下解釋器模式的相關技術要點,然后在示例部分,我們將解釋上文中所出現的莫名的式子。
要點梳理
- 目的分類
- 類行為型模式
- 范圍准則
- 類(該模式處理類和子類之間的關系,這些關系通過繼承建立,是靜態的,在編譯時刻便確定下來了)
- 主要功能
- 給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
- 適用情況
- 當有一個語言需要解釋執行, 並且我們可將該語言中的句子表示為一個抽象語法樹時,可使用解釋器模式。當存在以下情況時,效果最好:
- 該文法簡單。對於復雜的文法, 文法的類層次變得龐大而無法管理
- 效率不是一個關鍵問題。最高效的解釋器通常不是通過直接解釋語法分析樹實現。
- 當有一個語言需要解釋執行, 並且我們可將該語言中的句子表示為一個抽象語法樹時,可使用解釋器模式。當存在以下情況時,效果最好:
- 參與部分
- AbstractExpression(抽象表達式):聲明一個抽象的解釋操作,這個接口為抽象語法樹中所有的節點所共享
- TerminalExpression(終結符表達式):實現與文法中的終結符相關聯的解釋操作,一個句子中的每個終結符需要該類的一個實例
- NonterminalExpression(非終結符表達式):為文法中的非終結符實現解釋操作。解釋時,一般要遞歸調用它所維護的AbstractExpression類型對象的解釋操作
- Context(上下文):包含解釋器之外的一些全局信息
- Client(用戶):構建(或被給定) 表示該文法定義的語言中一個特定的句子的抽象語法樹。該抽象語法樹由TerminalExpression和NonterminalExpression的實例裝配而成。
- 協作過程
- Client構建一個句子,它是TerminalExpression和NonterminalExpression的實例的一個抽象語法樹,然后初始化上下文,並調用解釋操作。
- 每一非終結符表達式節點定義相應子表達式的解釋操作。
- 每一節點的解釋操作用上下文來存儲和訪問解釋器的狀態。
- UML圖
示例分析 - 字符畫解釋器
為了讓MM不明覺厲,我想到了通過簡單的解釋器來實現,從字符串到一個字符畫的轉換過程。我覺得利用stringstream流可以方便地構建一個字符畫,因此,我們首先確定我們實現這個模式的上下文(Context)就是stringstream對象。然后我們定義一些具體的字符操作表達式。它們是可以畫出字符畫的一些基本操作:
TerminalExpression:
- Constant:常量表達式。它也是終結符表達式。它的解釋操作就是將一個固定的string插入到Context流中。
NonterminalExpression:
- RepeatExpression:重復表達式。它是非終結符表達式。它的解釋操作就是使一個Expression重復N次。
- AddExpression:加法表達式。非終結符表達式。它的解釋操作是使兩個Expression拼接在一起。
- ReverseExpression:反轉表達式。非終結符表達式。它的解釋操作是使一個Expression逆序。
可以看到這幾個表達式是可以構成抽象語法樹的。讓我們看看代碼:

1 #ifndef EXPRESSION_H_INCLUDED 2 #define EXPRESSION_H_INCLUDED 3 4 #include <string> 5 #include <sstream> 6 7 using namespace std; 8 9 // ... Abstract Class ... 10 class Expression { 11 public: 12 Expression() {} 13 virtual ~Expression() {} 14 15 virtual void eval(stringstream&) = 0; 16 }; 17 18 // ... RepeatExpression Class ... 19 class RepeatExpression : public Expression { 20 public: 21 RepeatExpression(Expression*, int); 22 23 void eval(stringstream&); 24 private: 25 Expression* _oper; 26 int _mNum; 27 }; 28 29 // ... AddExpression Class ... 30 class AddExpression : public Expression { 31 public: 32 AddExpression(Expression*, Expression*); 33 34 void eval(stringstream&); 35 private: 36 Expression* _oper1; 37 Expression* _oper2; 38 }; 39 40 // ... ReverseExpression Class ... 41 class ReverseExpression : public Expression { 42 public: 43 ReverseExpression(Expression*); 44 45 void eval(stringstream&); 46 private: 47 Expression* _oper; 48 }; 49 50 // ... Constant Class ... 51 class Constant : public Expression { 52 public: 53 Constant(const char*); 54 Constant(const char*, int); 55 56 void eval(stringstream&); 57 private: 58 string _mStr; 59 }; 60 61 #endif // EXPRESSION_H_INCLUDED

1 #include "expression.h" 2 #include <algorithm> 3 using namespace std; 4 5 // ... RepeatExpression BEGIN ... 6 RepeatExpression::RepeatExpression(Expression* oper, int m) { 7 _oper = oper; 8 _mNum = m; 9 } 10 11 void RepeatExpression::eval(stringstream& ss) { 12 stringstream t_str; 13 _oper->eval(t_str); 14 for (int i = 0; i < _mNum; i++) { 15 ss << t_str.str(); 16 } 17 } 18 // ... RepeatExpression END ... 19 20 // ... AddExpression BEGIN ... 21 AddExpression::AddExpression(Expression* oper1, Expression* oper2) { 22 _oper1 = oper1; 23 _oper2 = oper2; 24 } 25 26 void AddExpression::eval(stringstream& ss) { 27 stringstream t_str; 28 _oper1->eval(t_str); 29 _oper2->eval(t_str); 30 ss << t_str.str(); 31 } 32 // ... AddExpression END ... 33 34 // ... ReverseExpression BEGIN ... 35 ReverseExpression::ReverseExpression(Expression* o) { 36 _oper = o; 37 } 38 39 void ReverseExpression::eval(stringstream& ss) { 40 stringstream t_str; 41 _oper->eval(t_str); 42 string str = t_str.str(); 43 reverse(str.begin(), str.end()); 44 ss << str; 45 } 46 // ... ReverseExpression END ... 47 48 // ... Constant BEGIN ... 49 Constant::Constant(const char* str) { 50 _mStr = string(str); 51 } 52 53 Constant::Constant(const char* str, int len) { 54 _mStr = string(str, len); 55 } 56 57 void Constant::eval(stringstream& ss) { 58 ss << _mStr; 59 } 60 // ... Constant END ...
到了這里,我們如果想生成一個字符畫: "~~o>_<o~~",可以這么做:
1 stringstream ss; 2 3 Expression* e1 = new RepeatExpression(new Constant("~"), 2); 4 Expression* e2 = new AddExpression(e1, new Constant("o>")); 5 Expression* e3 = new AddExpression(e2, new Constant("_")); 6 Expression* result = new AddExpression(e3, new ReverseExpression(e2)); 7 8 result->eval(ss); 9 cout << ss.str() << endl;
其實解釋器模式部分的編程已經結束了。但顯然這個並沒有達到前言中翻譯那串莫名字符串的目的。為此,我們還需在此基礎上,定義一些語法,寫一個語法分析器來將那串字符構建成抽象語法樹。這里,我就偷懶了,寫了個非常簡單,沒有什么優化的語法分析器:
// 定義的一些符號含義: // [] ---- 字符集 // () ---- 分組 // @N ---- 取字符集中第N個字符(N從0開始) // *N ---- *前面的表達式重復N次
// $N ---- 取第N個分組(N從0開始,分組由括號順序確定,嵌套的括號以從里到外的規則遞增) // + ---- 加號兩邊的表達式拼接 // ^ ---- ^前面的表達式逆序 // _N ---- 拼接N個空格 // / ---- 拼接一個換行符
具體代碼如下:

1 #ifndef TRANSLATOR_H_INCLUDED 2 #define TRANSLATOR_H_INCLUDED 3 4 #include <string> 5 #include <vector> 6 using namespace std; 7 8 class Expression; 9 10 class Translator { 11 public: 12 Translator(); 13 ~Translator(); 14 Expression* translate(string& str); 15 16 private: 17 Expression* translateExp(string& str); 18 char* _mCharSet; 19 vector<Expression*> _mExpGroup; 20 }; 21 22 #endif // TRANSLATOR_H_INCLUDED

1 #include "Translator.h" 2 #include "expression.h" 3 #include <cstring> 4 #include <cstdlib> 5 using namespace std; 6 7 Translator::Translator() { 8 _mCharSet = 0; 9 } 10 11 Translator::~Translator() { 12 if (_mCharSet) delete[] _mCharSet; 13 } 14 15 Expression* Translator::translate(string& str) { 16 Expression* result = 0; 17 for(unsigned int i = 0; i < str.size(); i++ ) { 18 if (str.at(i) == '[') { 19 int sEnd = str.find_last_of("]"); 20 int sLen = sEnd - i - 1; 21 if (_mCharSet) delete[] _mCharSet; 22 _mCharSet = new char[sLen]; 23 strcpy(_mCharSet, str.substr(i+1, sLen).data()); 24 i = sEnd; 25 } else if (str.at(i) == '@') { 26 int sChar = atoi(str.substr(i + 1, 1).c_str()); 27 Expression* tmp = new Constant(&_mCharSet[sChar], 1); 28 result = tmp; 29 i = i + 1; 30 } else if (str.at(i) == '(') { 31 int pos = i + 1; 32 int left = 0; 33 for (;pos < str.size(); pos++) { 34 if (str.at(pos) == ')') { 35 if (left == 0) 36 break; 37 else 38 left--; 39 } 40 if (str.at(pos) == '(') 41 left++; 42 } 43 string t_str = str.substr(i + 1, pos - i - 1); 44 Expression* tmp = translate(t_str); 45 _mExpGroup.push_back(tmp); 46 result = tmp; 47 i = pos; 48 } else if (str.at(i) == '+') { 49 string t_str = str.substr(i + 1); 50 result = new AddExpression(result, translate(t_str)); 51 break; 52 } else if (str.at(i) == '*') { 53 int pos = i+1; 54 for (;pos < str.size();pos++) { 55 if (str.at(pos) > '9' || str.at(pos) < '0') break; 56 } 57 pos--; 58 int sRep = atoi(str.substr(i + 1, pos - i).c_str()); 59 Expression* tmp = new RepeatExpression(result, sRep); 60 result = tmp; 61 i = pos; 62 } else if (str.at(i) == '^') { 63 Expression* tmp = new ReverseExpression(result); 64 result = tmp; 65 } else if (str.at(i) == '$') { 66 int pos = i+1; 67 for (;pos < str.size();pos++) { 68 if (str.at(pos) > '9' || str.at(pos) < '0') break; 69 } 70 pos--; 71 int nGroup = atoi(str.substr(i + 1, pos - i).c_str()); 72 if (nGroup >= _mExpGroup.size()) return 0; 73 result = _mExpGroup[nGroup]; 74 i = pos; 75 } else if (str.at(i) == '/') { 76 string t_str = str.substr(i + 1); 77 Expression* tmp = new Constant("\n"); 78 if (!result) { 79 result = new AddExpression(tmp, translate(t_str)); 80 } 81 else { 82 result = new AddExpression(new AddExpression(result, tmp), translate(t_str)); 83 } 84 break; 85 } else if (str.at(i) == '_') { 86 int pos = i+1; 87 for (;pos < str.size();pos++) { 88 if (str.at(pos) > '9' || str.at(pos) < '0') break; 89 } 90 pos--; 91 int sRep = (pos == i) ? 1 : atoi(str.substr(i + 1, pos - i).c_str()); 92 string t_str = str.substr(pos + 1); 93 Expression* tmp = new RepeatExpression(new Constant(" "), sRep); 94 if (!result) { 95 result = new AddExpression(tmp, translate(t_str)); 96 } 97 else { 98 result = new AddExpression(new AddExpression(result, tmp), translate(t_str)); 99 } 100 break; 101 } 102 } 103 return result; 104 }
再次強調,這個語法分析器,並不是解釋器模式所講的內容。好了,寫個簡單的main函數就可以運行了:
1 #include <iostream> 2 #include "expression.h" 3 #include "Translator.h" 4 5 using namespace std; 6 7 int main() 8 { 9 cout << "Input your command below: " << endl; 10 string str; 11 getline(cin, str); 12 Translator translator; 13 14 // ... Generate the Abstract Grammar Tree by Translator 15 Expression* myExp = translator.translate(str); 16 if (!myExp) return 1; 17 18 // ... Call Its Interpret Operation 19 stringstream ss; 20 myExp->eval(ss); 21 22 cout << ss.str() << endl; 23 return 0; 24 }
那么我們輸入之前第二串字符試試:
*****
**
** ****** **** **** *****
** ** ** ** ** ** **
** ** ** ** ** ********
## # ## ## ## ## ##
## # ## ## ### ## ##
####### ##### ## ######
MM表示很開心。對於這個示例的UML圖:
特點總結
我們可以看到,Interpreter解釋器模式有以下優點和缺點:
- 易於改變和擴展文法。因為該模式使用類來表示文法規則,我們可以使用繼承來改變或擴展該文法。多加一種文法就新增一個類。
- 也易於實現文法。定義抽象語法樹中各個節點的類的實現大體類似。通常它們也可用一個編譯器或語法分析程序生成器自動生成。
- 復雜的文法難以維護。解釋器模式為文法中的每一條規則至少定義了一個類,因此包含許多規則的文法可能難以管理和維護。
同時我們可以看到,它和其他設計模式:Composite(組合)模式有着許多相通的地方。具體可以參見之前的筆記。
寫在最后
今天的筆記就到這里了,歡迎大家批評指正!如果覺得可以的話,好文推薦一下,我會非常感謝的!