解釋器模式
在GOF的《設計模式:可複用面向對象軟體的基礎》一書中對解釋器模式是這樣說的:給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。如果一種特定類型的問題發生的頻率足夠高,那麼可能就值得将該問題的各個執行個體表述為一個簡單語言中的句子。這樣就可以建構一個解釋器,該解釋器通過解釋這些句子來解決該問題。
就如上面說的那個遊戲,我輸入up walk 5,我必須按照:移動方向+移動方式+移動距離這種格式輸入我的指令,而這種格式的指令就是一種文法,隻有按照了我定義的這種文法去輸入,才能控制螢幕上的小狗去移動。當然了,我輸入up walk 5,螢幕上的小狗肯定是聽不懂的,它不知道我輸入的是什麼,這個時候需要怎麼辦?我需要一個工具去将我輸入的内容翻譯成小狗能聽懂的東西,而這個工具就是定義中提到的解釋器,解釋器對我輸入的指令進行解釋,然後将解釋得到的指令發送給螢幕上的小狗,小狗聽懂了,就進行實際的移動。
我們在開發中經常用到的正規表達式也是這類問題的代表。我們有的時候需要去比對電話号碼、身份證号;我們不用為了每一種比對都寫一個特定的算法,我們可以為每一種比對定義一種文法,然後去解釋這種文法定義的句子就ok了。
文法規則和抽象文法樹
上面對于解釋器模式的定義中,提及到了一個詞:文法。在使用代碼實作解釋器模式之前,是非常有必要去學習一下文法的概念,以及如何表示一個語言的文法規則。再拿上面的遊戲這個例子進行說明,我可以定義以下五條文法:
expression ::= direction action distance | composite //表達式
composite ::= expression 'and' expression //複合表達式
direction ::= 'up' | 'down' | 'left' | 'right' //移動方向
action ::= 'move' | 'walk' //移動方式
distance ::= an integer //移動距離
上面的5條文法規則,對應5個語言機關,這些語言機關可以分為兩大類:一類為終結符(也叫做終結符表達式),例如上面的direction、action和distance,它們是語言的最小組成機關,不能再進行拆分;另一類為非終結符(也叫做非終結符表達式),例如上面的expression和composite,它們都是一個完整的句子,包含一系列終結符或非終結符。
我們就是根據上面定義的一些文法可以構成更多複雜的語句,計算機程式将根據這些語句進行某種操作;而我們這裡列出的文法,計算機是無法直接看懂的,是以,我們需要對我們定義的文法進行解釋;就好比,我們編寫的C++代碼,計算機是看不懂的,我們需要進行編譯一樣。解釋器模式,就提供一種模式去給計算機解釋我們定義的文法,讓計算機根據我們的文法去進行工作。
在文法規則定義中可以使用一些符号來表示不同的含義,如使用“|”表示或,使用“{”和“}”表示組合,使用“*”表示出現0次或多次等,其中使用頻率最高的符号是表示“或”關系的“|”,如文法規則“bool Value ::= 0 | 1”表示終結符表達式bool Value的取值可以為0或者1。
除了使用文法規則來定義一個語言,在解釋器模式中還可以通過一種稱之為抽象文法樹的圖形方式來直覺地表示語言的構成,每一棵文法樹對應一個語言執行個體,對于上面的遊戲文法規則,可以通過以下的抽象文法樹來進行表示:

在抽象文法樹種,可以通過終結符表達式和非終結符表達式組成複雜的語句,每個文法規則的語言執行個體都可以表示為一個抽象文法樹,就是說每一條具體的語句都可以用類似上圖所示的抽象文法樹來表示,在圖中終結符表達式類的執行個體作為樹的葉子節點,而非終結符表達式類的執行個體作為非葉子節點。抽象文法樹描述了如何構成一個複雜的句子。
UML類圖
AbstractExpression:聲明一個抽象的解釋操作,這個接口被抽象文法樹中所有的節點所共享;
TernimalExpression:一個句子中的每個終結符需要該類的一個執行個體,它實作與文法中的終結符相關聯的解釋操作;
NonternimalExpression:
- 對于文法中的每一條規則都需要一個NonternimalExpression類;
- 為文法中的的每個符号都維護一個AbstractExpression類型的執行個體變量;
- 為文法中的非終結符實作解釋操作,在實作時,一般要遞歸地調用表示文法符号的那些對象的解釋操作;
Context:包含解釋器之外的一些全局資訊;
Client:建構一個需要進行解釋操作的文法句子,然後調用解釋操作進行解釋。
實際進行解釋時,按照以下時序進行的:
- Client建構一個句子,它是NonterminalExpression和TerminalExpression的執行個體的一個抽象文法樹,然後初始化上下文并調用解釋操作;
- 每一非終結符表達式節點定義相應子表達式的解釋操作。而各終結符表達式的解釋操作構成了遞歸的基礎;
- 每一節點的解釋操作用作用上下文來存儲和通路解釋器的狀态。
使用場合
在以下情況下可以考慮使用解釋器模式:
- 可以将一個需要解釋執行的語言中的句子表示為一個抽象文法樹;
- 一些重複出現的問題可以用一種簡單的語言來進行表達;
- 一個語言的文法較為簡單;
- 執行效率不是關鍵問題。【注:高效的解釋器通常不是通過直接解釋抽象文法樹來實作的,而是需要将它們轉換成其他形式,使用解釋器模式的執行效率并不高。】
代碼實作
我們這裡用代碼來實作上面的遊戲,隻不過不是控制小狗在螢幕上移動了,而是将對應的控制指令翻譯成漢語進行表示,這和翻譯成控制小狗移動的指令的原理是一樣的。比如現在有指令:down run 10;那麼,經過解釋器模式得到的結果為:向下跑動10。
1 #include <iostream>
2 #include <vector>
3 using namespace std;
4
5 #define MAX_SIZE 256
6 #define SAFE_DELETE(p) if (p) { delete p; p = NULL; }
7
8 const wchar_t *const DOWN = L"down";
9 const wchar_t *const UP = L"up";
10 const wchar_t *const LEFT = L"left";
11 const wchar_t *const RIGHT = L"right";
12
13 const wchar_t *const MOVE = L"move";
14 const wchar_t *const WALK = L"walk";
15
16 class AbstractNode
17 {
18 public:
19 virtual wchar_t *Interpret() = 0;
20 };
21
22 class AndNode : public AbstractNode
23 {
24 public:
25 AndNode(AbstractNode *left, AbstractNode *right) : m_pLeft(left), m_pRight(right){}
26
27 wchar_t *Interpret()
28 {
29 wchar_t *pResult = new wchar_t[MAX_SIZE];
30 memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));
31
32 wchar_t *pLeft = m_pLeft->Interpret();
33 wchar_t *pRight = m_pRight->Interpret();
34 wcscat_s(pResult, MAX_SIZE, pLeft);
35 wcscat_s(pResult, MAX_SIZE, pRight);
36
37 SAFE_DELETE(pLeft);
38 SAFE_DELETE(m_pRight);
39
40 return pResult;
41 }
42
43 private:
44 AbstractNode *m_pLeft;
45 AbstractNode *m_pRight;
46 };
47
48 class SentenceNode : public AbstractNode
49 {
50 public:
51 SentenceNode(AbstractNode *direction, AbstractNode *action, AbstractNode *distance) :
52 m_pDirection(direction), m_pAction(action), m_pDistance(distance){}
53
54 wchar_t *Interpret()
55 {
56 wchar_t *pResult = new wchar_t[MAX_SIZE];
57 memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));
58
59 wchar_t *pDirection = m_pDirection->Interpret();
60 wchar_t *pAction = m_pAction->Interpret();
61 wchar_t *pDistance = m_pDistance->Interpret();
62 wcscat_s(pResult, MAX_SIZE, pDirection);
63 wcscat_s(pResult, MAX_SIZE, pAction);
64 wcscat_s(pResult, MAX_SIZE, pDistance);
65
66 SAFE_DELETE(pDirection);
67 SAFE_DELETE(pAction);
68 SAFE_DELETE(pDistance);
69
70 return pResult;
71 }
72
73 private:
74 AbstractNode *m_pDirection;
75 AbstractNode *m_pAction;
76 AbstractNode *m_pDistance;
77 };
78
79 class DirectionNode : public AbstractNode
80 {
81 public:
82 DirectionNode(wchar_t *direction) : m_pDirection(direction){}
83
84 wchar_t *Interpret()
85 {
86 wchar_t *pResult = new wchar_t[MAX_SIZE];
87 memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));
88
89 if (!_wcsicmp(m_pDirection, DOWN))
90 {
91 wcscat_s(pResult, MAX_SIZE, L"向下");
92 }
93 else if (!_wcsicmp(m_pDirection, UP))
94 {
95 wcscat_s(pResult, MAX_SIZE, L"向上");
96 }
97 else if (!_wcsicmp(m_pDirection, LEFT))
98 {
99 wcscat_s(pResult, MAX_SIZE, L"向左");
100 }
101 else if (!_wcsicmp(m_pDirection, RIGHT))
102 {
103 wcscat_s(pResult, MAX_SIZE, L"向右");
104 }
105 else
106 {
107 wcscat_s(pResult, MAX_SIZE, L"無效指令");
108 }
109
110 SAFE_DELETE(m_pDirection);
111 return pResult;
112 }
113
114 private:
115 wchar_t *m_pDirection;
116 };
117
118 class ActionNode : public AbstractNode
119 {
120 public:
121 ActionNode(wchar_t *action) : m_pAction(action){}
122
123 wchar_t *Interpret()
124 {
125 wchar_t *pResult = new wchar_t[MAX_SIZE];
126 memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));
127
128 if (!_wcsicmp(m_pAction, MOVE))
129 {
130 wcscat_s(pResult, MAX_SIZE, L"移動");
131 }
132 else if (!_wcsicmp(m_pAction, WALK))
133 {
134 wcscat_s(pResult, MAX_SIZE, L"走動");
135 }
136 else
137 {
138 wcscat_s(pResult, MAX_SIZE, L"無效指令");
139 }
140
141 SAFE_DELETE(m_pAction);
142 return pResult;
143 }
144
145 private:
146 wchar_t *m_pAction;
147 };
148
149 class DistanceNode : public AbstractNode
150 {
151 public:
152 DistanceNode(wchar_t *distance) : m_pDistance(distance){}
153
154 wchar_t *Interpret()
155 {
156 wchar_t *pResult = new wchar_t[MAX_SIZE];
157 memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));
158
159 wcscat_s(pResult, MAX_SIZE, m_pDistance);
160
161 SAFE_DELETE(m_pDistance);
162 return pResult;
163 }
164
165 private:
166 wchar_t *m_pDistance;
167 };
168
169 class InstructionHandler
170 {
171 public:
172 InstructionHandler(wchar_t *instruction) : m_pInstruction(instruction), m_pTree(NULL){}
173
174 void Handle();
175 void Output();
176
177 private:
178 void SplitInstruction(wchar_t **&instruction, int &size);
179
180 wchar_t *m_pInstruction;
181 AbstractNode *m_pTree;
182 };
183
184 void InstructionHandler::Handle()
185 {
186 AbstractNode *pLeft = NULL;
187 AbstractNode *pRight = NULL;
188 AbstractNode *pDirection = NULL;
189 AbstractNode *pAction = NULL;
190 AbstractNode *pDistance = NULL;
191
192 vector<AbstractNode *> node; // Store the instruction expression
193
194 // Split the instruction by " "
195 wchar_t **InstructionArray = NULL;
196 int size;
197 SplitInstruction(InstructionArray, size);
198 for (int i = 0; i < size; ++i)
199 {
200 if (!_wcsicmp(InstructionArray[i], L"and")) // The instruction is composited by two expressions
201 {
202 wchar_t *pDirectionStr = InstructionArray[++i];
203 pDirection = new DirectionNode(pDirectionStr);
204
205 wchar_t *pActionStr = InstructionArray[++i];
206 pAction = new ActionNode(pActionStr);
207
208 wchar_t *pDistanceStr = InstructionArray[++i];
209 pDistance = new DistanceNode(pDistanceStr);
210
211 pRight = new SentenceNode(pDirection, pAction, pDistance);
212 node.push_back(new AndNode(pLeft, pRight));
213 }
214 else
215 {
216 wchar_t *pDirectionStr = InstructionArray[i];
217 pDirection = new DirectionNode(pDirectionStr);
218
219 wchar_t *pActionStr = InstructionArray[++i];
220 pAction = new ActionNode(pActionStr);
221
222 wchar_t *pDistanceStr = InstructionArray[++i];
223 pDistance = new DistanceNode(pDistanceStr);
224
225 pLeft = new SentenceNode(pDirection, pAction, pDistance);
226 node.push_back(pLeft);
227 }
228 }
229
230 m_pTree = node[node.size() - 1];
231 }
232
233 void InstructionHandler::Output()
234 {
235 wchar_t *pResult = m_pTree->Interpret();
236
237 setlocale(LC_ALL,"");
238 wprintf_s(L"%s\n", pResult);
239
240 SAFE_DELETE(pResult);
241 }
242
243 void InstructionHandler::SplitInstruction(wchar_t **&instruction, int &size)
244 {
245 instruction = new wchar_t*[10];
246 memset(instruction, 0, 10 * sizeof( wchar_t*));
247
248 for (int i = 0; i < 10; ++i)
249 {
250 instruction[i] = new wchar_t[10];
251 memset(instruction[i], 0, 10 * sizeof(wchar_t));
252 }
253
254 size = 0;
255 int n = 0;
256 while (*m_pInstruction != L'\0')
257 {
258 if (*m_pInstruction == L' ')
259 {
260 size++;
261 m_pInstruction++;
262 n = 0;
263 continue;
264 }
265
266 instruction[size][n++] = *m_pInstruction++;
267 }
268 size++;
269 }
270
271 int main()
272 {
273 wchar_t *pInstructionStr = L"up move 5 and down walk 10";
274
275 InstructionHandler *pInstructionHandler = new InstructionHandler(pInstructionStr);
276 pInstructionHandler->Handle();
277 pInstructionHandler->Output();
278
279 SAFE_DELETE(pInstructionHandler);
280 }
在上面的代碼中,我沒有用到Context類,一般Context類作為環境上下文類,用于存儲解釋器之外的一些全局資訊,它通常作為參數被傳遞到所有表達式的解釋方法interpret中,可以在Context對象中存儲和通路表達式解釋器的狀态,向表達式解釋器提供一些全局的、公共的資料,此外還可以在Context中增加一些所有表達式解釋器都共有的功能,減輕解釋器的職責。而我們在代碼中定義的一些常量,完全可以放入到Context類中,作為上下文的全局資料。
主要優點
- 易于改變和擴充文法。由于在解釋器模式中使用類來表示語言的文法規則,是以可以通過繼承等機制來改變或擴充文法;
- 每一條文法規則都可以表示為一個類,是以可以友善地實作一個簡單的語言;
- 實作文法較為容易;在抽象文法樹中每一個表達式節點類的實作方式都是相似的,這些類的代碼編寫都不會特别複雜;
- 增加新的解釋表達式較為友善。如果使用者需要增加新的解釋表達式隻需要對應增加一個新的終結符表達式類或非終結符表達式類,原有表達式類代碼無須修改,符合“開閉原則”。
主要缺點
- 對于複雜文法難以維護;在解釋器模式中,每一條規則至少需要定義一個類,是以如果一個語言包含太多文法規則,類的個數将會急劇增加,導緻系統難以管理和維護,此時可以考慮使用文法分析程式等方式來取代解釋器模式;
- 執行效率低;由于在解釋器模式中使用了大量的循環和遞歸調用,是以在解釋較為複雜的句子時其速度很慢,而且代碼的調試過程也很麻煩。
總結
解釋器模式在實際的系統開發中使用的非常少,因為它會引起效率、性能以及維護方面的問題,并且難度較大,一般在一些大中型的架構型項目中能夠找到它的身影。而現在又有很多的開源庫提供了對實際需要的支援,是以,我們在實際開發中沒有必要再去重複造輪子,能夠了解了解釋器模式就好了。