天天看點

C++設計模式——解釋器模式

解釋器模式

在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。

除了使用文法規則來定義一個語言,在解釋器模式中還可以通過一種稱之為抽象文法樹的圖形方式來直覺地表示語言的構成,每一棵文法樹對應一個語言執行個體,對于上面的遊戲文法規則,可以通過以下的抽象文法樹來進行表示:

C++設計模式——解釋器模式

在抽象文法樹種,可以通過終結符表達式和非終結符表達式組成複雜的語句,每個文法規則的語言執行個體都可以表示為一個抽象文法樹,就是說每一條具體的語句都可以用類似上圖所示的抽象文法樹來表示,在圖中終結符表達式類的執行個體作為樹的葉子節點,而非終結符表達式類的執行個體作為非葉子節點。抽象文法樹描述了如何構成一個複雜的句子。

UML類圖

C++設計模式——解釋器模式

AbstractExpression:聲明一個抽象的解釋操作,這個接口被抽象文法樹中所有的節點所共享;

TernimalExpression:一個句子中的每個終結符需要該類的一個執行個體,它實作與文法中的終結符相關聯的解釋操作;

NonternimalExpression:

  • 對于文法中的每一條規則都需要一個NonternimalExpression類;
  • 為文法中的的每個符号都維護一個AbstractExpression類型的執行個體變量;
  • 為文法中的非終結符實作解釋操作,在實作時,一般要遞歸地調用表示文法符号的那些對象的解釋操作;

Context:包含解釋器之外的一些全局資訊;

Client:建構一個需要進行解釋操作的文法句子,然後調用解釋操作進行解釋。

實際進行解釋時,按照以下時序進行的:

    1. Client建構一個句子,它是NonterminalExpression和TerminalExpression的執行個體的一個抽象文法樹,然後初始化上下文并調用解釋操作;
    2. 每一非終結符表達式節點定義相應子表達式的解釋操作。而各終結符表達式的解釋操作構成了遞歸的基礎;
    3. 每一節點的解釋操作用作用上下文來存儲和通路解釋器的狀态。

使用場合

在以下情況下可以考慮使用解釋器模式:

  • 可以将一個需要解釋執行的語言中的句子表示為一個抽象文法樹;
  • 一些重複出現的問題可以用一種簡單的語言來進行表達;
  • 一個語言的文法較為簡單;
  • 執行效率不是關鍵問題。【注:高效的解釋器通常不是通過直接解釋抽象文法樹來實作的,而是需要将它們轉換成其他形式,使用解釋器模式的執行效率并不高。】

代碼實作

我們這裡用代碼來實作上面的遊戲,隻不過不是控制小狗在螢幕上移動了,而是将對應的控制指令翻譯成漢語進行表示,這和翻譯成控制小狗移動的指令的原理是一樣的。比如現在有指令: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類中,作為上下文的全局資料。

主要優點

  1. 易于改變和擴充文法。由于在解釋器模式中使用類來表示語言的文法規則,是以可以通過繼承等機制來改變或擴充文法;
  2. 每一條文法規則都可以表示為一個類,是以可以友善地實作一個簡單的語言;
  3. 實作文法較為容易;在抽象文法樹中每一個表達式節點類的實作方式都是相似的,這些類的代碼編寫都不會特别複雜;
  4. 增加新的解釋表達式較為友善。如果使用者需要增加新的解釋表達式隻需要對應增加一個新的終結符表達式類或非終結符表達式類,原有表達式類代碼無須修改,符合“開閉原則”。

主要缺點

  1. 對于複雜文法難以維護;在解釋器模式中,每一條規則至少需要定義一個類,是以如果一個語言包含太多文法規則,類的個數将會急劇增加,導緻系統難以管理和維護,此時可以考慮使用文法分析程式等方式來取代解釋器模式;
  2. 執行效率低;由于在解釋器模式中使用了大量的循環和遞歸調用,是以在解釋較為複雜的句子時其速度很慢,而且代碼的調試過程也很麻煩。

總結

解釋器模式在實際的系統開發中使用的非常少,因為它會引起效率、性能以及維護方面的問題,并且難度較大,一般在一些大中型的架構型項目中能夠找到它的身影。而現在又有很多的開源庫提供了對實際需要的支援,是以,我們在實際開發中沒有必要再去重複造輪子,能夠了解了解釋器模式就好了。