zoukankan      html  css  js  c++  java
  • [设计模式]解释器(Interpreter)之大胆向MM示爱吧

    为方便读者,本文已添加至索引:

    写在前面

    “我刚写了个小程序,需要你来参与下。”我把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诧异地看看我,然后可怜巴巴地坐到屏幕前,对着键盘一个字一个字地敲。她打字超慢的,各种符号都是用两个食指打进去的。她打着打着,说想哭了。我赶忙告诉她,加油,全打正确了有惊喜。

    终于,她敲下了回车键。映入眼帘的是:

           _         _
         *   *     *   *     
        *      * *      *    
        *       *       *    
         *             *     
           *         *       
              *   *          
                *            
    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

    ……

    是不是读者你也想知道这个会是什么结果了吧?这当然跟我们今天的主题,解释器模式有关啦!会在示例一节展开。

    其实,我们平时接触到的解释器模式相关的实际例子并不太多,最常见的莫过于正则表达式了。它通过规定一系列的文法规则,并给予了相关的解释操作,从而成为处理字符串的通用强大的工具。首先我们了解下解释器模式的相关技术要点,然后在示例部分,我们将解释上文中所出现的莫名的式子。

    要点梳理

    • 目的分类
      • 类行为型模式
    • 范围准则
      • 类(该模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了)
    • 主要功能
      • 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
    • 适用情况
      • 当有一个语言需要解释执行, 并且我们可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。当存在以下情况时,效果最好:
        • 该文法简单。对于复杂的文法, 文法的类层次变得庞大而无法管理
        • 效率不是一个关键问题。最高效的解释器通常不是通过直接解释语法分析树实现。
    • 参与部分
      • 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("
    ");
     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(组合)模式有着许多相通的地方。具体可以参见之前的笔记。

    写在最后

    今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!

  • 相关阅读:
    念奴娇·登多景楼
    转载《“精”、“气”、“神”解》
    三伏天,人体内有一个“冰箱”
    《抓住“三伏天”习武健身的黄金季节》--胡俭雷
    孙氏内家拳中的桩功
    清净布气门功夫介绍
    孙式太极拳的站桩功--无极式
    [Android Tips] 25. ADB Command Note
    [Python] 删除指定目录下后缀为 xxx 的过期文件
    [Git] Ubuntu 升级 git 版本
  • 原文地址:https://www.cnblogs.com/xieziyu/p/3645410.html
Copyright © 2011-2022 走看看