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

写在前面

“我刚写了个小程序,需要你来参与下。”我把MM叫到我的电脑旁,“来把下面这条命令打进去,这是个练习打(Pian)符(ni)号(de)的小程序,看看你能不能全部打正确”。

  1. [*_]_7@1_9@/(_5@0_3@)*/((_4@)_2$)_$^/$+(_7@)*/_$1_6$/$3_2$/_3$3_3@/_5$

MM诧异地看看我,然后可怜巴巴地坐到屏幕前,对着键盘一个字一个字地敲。她打字超慢的,各种符号都是用两个食指打进去的。她打着打着,说想哭了。我赶忙告诉她,加油,全打正确了有惊喜。

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

  1. _ _
  2. * * * *
  3. * * * *
  4. * * *
  5. * *
  6. * *
  7. * *
  8. *

See Result

她忽然就开心起来,问我这个是怎么回事。我告诉她,“这说明你刚才的命令输对了,电脑按照命令画出了它~。要不再接再厉,试试下面这个更有挑战性的?”

  1. [#*]_@*/_(_2@*)/$0_9@*6_(_@*)*2_3@*/$0_6$0_2$*+(_$)*/$0_5$0_3$*3_3@*/(_2@*)_4@+$3_3$*+(_@*)_2$/$4_4@0_$3_2$3_4@*3_3$3_2$/@*7_5@*5_4$3_7@*

……

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

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

要点梳理

  • 目的分类

    • 类行为型模式
  • 范围准则
    • 类(该模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了)
  • 主要功能
    • 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
  • 适用情况
    • 当有一个语言需要解释执行, 并且我们可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。当存在以下情况时,效果最好:

      • 该文法简单。对于复杂的文法, 文法的类层次变得庞大而无法管理
      • 效率不是一个关键问题。最高效的解释器通常不是通过直接解释语法分析树实现。
  • 参与部分
    • 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&) = ;
  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 = ; 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("~"), );
  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;

其实解释器模式部分的编程已经结束了。但显然这个并没有达到前言中翻译那串莫名字符串的目的。为此,我们还需在此基础上,定义一些语法,写一个语法分析器来将那串字符构建成抽象语法树。这里,我就偷懒了,写了个非常简单,没有什么优化的语法分析器:

  1. // 定义的一些符号含义:
  2. // [] ---- 字符集
  3. // () ---- 分组
  4. // @N ---- 取字符集中第N个字符(N从0开始)
  5. // *N ---- *前面的表达式重复N次
    // $N ---- 取第N个分组(N从0开始,分组由括号顺序确定,嵌套的括号以从里到外的规则递增)
  6. // + ---- 加号两边的表达式拼接
  7. // ^ ---- ^前面的表达式逆序
  8. // _N ---- 拼接N个空格
  9. // / ---- 拼接一个换行符

具体代码如下:

  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 = ;
  9. }
  10.  
  11. Translator::~Translator() {
  12. if (_mCharSet) delete[] _mCharSet;
  13. }
  14.  
  15. Expression* Translator::translate(string& str) {
  16. Expression* result = ;
  17. for(unsigned int i = ; i < str.size(); i++ ) {
  18. if (str.at(i) == '[') {
  19. int sEnd = str.find_last_of("]");
  20. int sLen = sEnd - i - ;
  21. if (_mCharSet) delete[] _mCharSet;
  22. _mCharSet = new char[sLen];
  23. strcpy(_mCharSet, str.substr(i+, sLen).data());
  24. i = sEnd;
  25. } else if (str.at(i) == '@') {
  26. int sChar = atoi(str.substr(i + , ).c_str());
  27. Expression* tmp = new Constant(&_mCharSet[sChar], );
  28. result = tmp;
  29. i = i + ;
  30. } else if (str.at(i) == '(') {
  31. int pos = i + ;
  32. int left = ;
  33. for (;pos < str.size(); pos++) {
  34. if (str.at(pos) == ')') {
  35. if (left == )
  36. break;
  37. else
  38. left--;
  39. }
  40. if (str.at(pos) == '(')
  41. left++;
  42. }
  43. string t_str = str.substr(i + , pos - i - );
  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 + );
  50. result = new AddExpression(result, translate(t_str));
  51. break;
  52. } else if (str.at(i) == '*') {
  53. int pos = i+;
  54. for (;pos < str.size();pos++) {
  55. if (str.at(pos) > '' || str.at(pos) < '') break;
  56. }
  57. pos--;
  58. int sRep = atoi(str.substr(i + , 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+;
  67. for (;pos < str.size();pos++) {
  68. if (str.at(pos) > '' || str.at(pos) < '') break;
  69. }
  70. pos--;
  71. int nGroup = atoi(str.substr(i + , pos - i).c_str());
  72. if (nGroup >= _mExpGroup.size()) return ;
  73. result = _mExpGroup[nGroup];
  74. i = pos;
  75. } else if (str.at(i) == '/') {
  76. string t_str = str.substr(i + );
  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+;
  87. for (;pos < str.size();pos++) {
  88. if (str.at(pos) > '' || str.at(pos) < '') break;
  89. }
  90. pos--;
  91. int sRep = (pos == i) ? : atoi(str.substr(i + , pos - i).c_str());
  92. string t_str = str.substr(pos + );
  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 ;
  17.  
  18. // ... Call Its Interpret Operation
  19. stringstream ss;
  20. myExp->eval(ss);
  21.  
  22. cout << ss.str() << endl;
  23. return ;
  24. }

那么我们输入之前第二串字符试试:

  1. *****
  2. **
  3. ** ****** **** **** *****
  4. ** ** ** ** ** ** **
  5. ** ** ** ** ** ********
  6. ## # ## ## ## ## ##
  7. ## # ## ## ### ## ##
  8. ####### ##### ## ######

MM表示很开心。对于这个示例的UML图:

特点总结

我们可以看到,Interpreter解释器模式有以下优点和缺点:

  1. 易于改变和扩展文法。因为该模式使用类来表示文法规则,我们可以使用继承来改变或扩展该文法。多加一种文法就新增一个类。
  2. 也易于实现文法。定义抽象语法树中各个节点的类的实现大体类似。通常它们也可用一个编译器或语法分析程序生成器自动生成。
  3. 复杂的文法难以维护。解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。

同时我们可以看到,它和其他设计模式:Composite(组合)模式有着许多相通的地方。具体可以参见之前的笔记。

写在最后

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

[设计模式]解释器(Interpreter)之大胆向MM示爱吧的更多相关文章

  1. python 设计模式之解释器(Interpreter)模式

    #写在前面 关于解释器模式,我在网上转了两三圈,心中有了那么一点概念 ,也不知道自己理解的是对还是错. 其实关于每一种设计模式,我总想找出一个答案,那就是为什么要用这种设计模式, 如果不用会怎么样,会 ...

  2. 设计模式(15)--Interpreter(解释器模式)--行为型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义: 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解 ...

  3. Java设计模式----解释器模式

    计算器中,我们输入“20 + 10 - 5”,计算器会得出结果25并返回给我们.可你有没有想过计算器是怎样完成四则运算的?或者说,计算器是怎样识别你输入的这串字符串信息,并加以解析,然后执行之,得出结 ...

  4. 深入浅出设计模式——解释器模式(Interpreter Pattern)

    模式动机 如果在系统中某一特定类型的问题发生的频率很高,此时可以考虑将这些问题的实例表述为一个语言中的句子,因此可以构建一个解释器,该解释器通过解释这些句子来解决这些问题.解释器模式描述了如何构成一个 ...

  5. [工作中的设计模式]解释器模式模式Interpreter

    一.模式解析 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 以上是解释器模式的类图,事实上我 ...

  6. 面向对象设计模式之Interpreter解释器模式(行为型)

    动机:在软件构建过程中 ,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化.在这种情况下,将特定领域的问题表达为某种语法规则的句子,然后构建一个 ...

  7. 设计模式 -- 解释器模式(Interpreter Pattern)

    2015年12月15日00:19:02 今天只看了理论和demo,明天再写文章,跑步好累 2015年12月15日21:36:00 解释器模式用来解释预先定义的文法. <大话设计模式>里面这 ...

  8. 设计模式之Interpreter(解释器)(转)

    Interpreter定义: 定义语言的文法 ,并且建立一个解释器来解释该语言中的句子. Interpreter似乎使用面不是很广,它描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造 ...

  9. javascript设计模式 - 解释器模式(interpreter)

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

随机推荐

  1. Ubuntu 下安装Kibana和logstash

    原文地址:http://www.cnblogs.com/saintaxl/p/3946667.html 简单来讲他具体的工作流程就是 logstash agent 监控并过滤日志,将过滤后的日志内容发 ...

  2. php非阻塞执行系统命令

    大家都知道php调用系统命令常用的主要有以下几种方法: 如exec(), system(), passthru(), shell_exec() 这几个函数的用法在此不做说明,有需要的请查阅php相关手 ...

  3. MySQL数据库设计复习笔记及项目实战

    最近手头上有3个项目开动,其他2个都是从底层开始的,一个已经开始了一段时间的了,在小城市小团队开发的条件下,都没有专门的DBA来做数据库的设计和维护,往往都是开发人员顶上,可是看了很多的数据库的设计, ...

  4. iptables的实战整理

    一.iptables使用场景:             内网情况下使用:在大并发的情况下不要开iptables否则影响性能 二.iptables出现下面的问题:             在yewufa ...

  5. java数据结构-非线性结构之树

    一.树状图 树状图是一种数据结构,它是由n(n>=1)个有限节点组成的具有层次关系的集合.因其结构看起来想个倒挂的树,即根朝上,叶子在下,故被称为"树". 特点: 1. 每个 ...

  6. CSS实现圆角,三角,五角星,五边形,爱心,12角星,8角星,圆,椭圆,圆圈,八卦

    转自:http://blog.csdn.net/chenhongwu666/article/details/38905803 CSS实现圆角,三角,五角星,五边形,爱心,12角星,8角星,圆,椭圆,圆 ...

  7. MKDOCS在线文档编辑器

    http://www.mkdocs.org/  api接口文档编写 ,效果非常不错

  8. 采用subversion管理iOS资源

    1.装和配置subversionserver  在windows server上安装VisualSVN-Server.下载地址http://www.visualsvn.com/server/downl ...

  9. Linux多网卡多IP配置

    echo "210 local100" >> /etc/iproute2/rt_tables echo "220 local200" >> ...

  10. RedHat7搭建无人值守自动安装Linux操作系统(PXE+Kickstart)

    Kickstart服务器 IP: 192.168.136.253   掩码:255.255.255.0   网关:192.168.136.2   DNS:192.168.136.2 安装部署HTTP服 ...