[設計模式]解釋器(Interpreter)之大膽向MM示愛吧

爲方便讀者,本文已添加至索引:html

寫在前面

「我剛寫了個小程序,須要你來參與下。」我把MM叫到個人電腦旁,「來把下面這條命令打進去,這是個練習打(Pian)符(ni)號(de)的小程序,看看你能不能所有打正確」。ios

[*_]_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詫異地看看我,而後可憐巴巴地坐到屏幕前,對着鍵盤一個字一個字地敲。她打字超慢的,各類符號都是用兩個食指打進去的。她打着打着,說想哭了。我趕緊告訴她,加油,全打正確了有驚喜。正則表達式

終於,她敲下了回車鍵。映入眼簾的是:express

       _         _
     *   *     *   *     
    *      * *      *    
    *       *       *    
     *             *     
       *         *       
          *   *          
            *            
See Result

她突然就開心起來,問我這個是怎麼回事。我告訴她,「這說明你剛纔的命令輸對了,電腦按照命令畫出了它~。要再也不接再厲,試試下面這個更有挑戰性的?」編程

[#*]_@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

……小程序

是否是讀者你也想知道這個會是什麼結果了吧?這固然跟咱們今天的主題,解釋器模式有關啦!會在示例一節展開。設計模式

其實,咱們平時接觸到的解釋器模式相關的實際例子並不太多,最多見的莫過於正則表達式了。它經過規定一系列的文法規則,並給予了相關的解釋操做,從而成爲處理字符串的通用強大的工具。首先咱們瞭解下解釋器模式的相關技術要點,而後在示例部分,咱們將解釋上文中所出現的莫名的式子。ide

要點梳理

  • 目的分類
    • 類行爲型模式
  • 範圍準則
    • 類(該模式處理類和子類之間的關係,這些關係經過繼承創建,是靜態的,在編譯時刻便肯定下來了)
  • 主要功能
    • 給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
  • 適用狀況
    • 當有一個語言須要解釋執行, 而且咱們可將該語言中的句子表示爲一個抽象語法樹時,可以使用解釋器模式。當存在如下狀況時,效果最好:
      • 該文法簡單。對於複雜的文法, 文法的類層次變得龐大而沒法管理
      • 效率不是一個關鍵問題。最高效的解釋器一般不是經過直接解釋語法分析樹實現。
  • 參與部分
    • 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
expression.h
 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 ...
expression.cpp

到了這裏,咱們若是想生成一個字符畫: "~~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
Translator.h
  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 }
Translator.cpp

再次強調,這個語法分析器,並非解釋器模式所講的內容。好了,寫個簡單的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解釋器模式有如下優勢和缺點:

  1. 易於改變和擴展文法。由於該模式使用類來表示文法規則,咱們可使用繼承來改變或擴展該文法。多加一種文法就新增一個類。
  2. 也易於實現文法。定義抽象語法樹中各個節點的類的實現大致相似。一般它們也可用一個編譯器或語法分析程序生成器自動生成。
  3. 複雜的文法難以維護。解釋器模式爲文法中的每一條規則至少定義了一個類,所以包含許多規則的文法可能難以管理和維護。

同時咱們能夠看到,它和其餘設計模式:Composite(組合)模式有着許多相通的地方。具體能夠參見以前的筆記。

寫在最後

今天的筆記就到這裏了,歡迎你們批評指正!若是以爲能夠的話,好文推薦一下,我會很是感謝的!

相關文章
相關標籤/搜索