[设计模式]解释器(Interpreter)之大胆向MM示爱吧
为方便读者,本文已添加至索引:
设计模式
学习笔记索引
写在前面
“我刚写了个小程序,需要你来参与下。”我把MM叫到我的电脑旁,“来把下面这条命令打进去,这是个练习打(Pian)符(ni)号(de)的小程序,看看你能不能全部打正确”。
- [*_]_7@1_9@/(_5@0_3@)*/((_4@)_2$)_$^/$+(_7@)*/_$1_6$/$3_2$/_3$3_3@/_5$
MM诧异地看看我,然后可怜巴巴地坐到屏幕前,对着键盘一个字一个字地敲。她打字超慢的,各种符号都是用两个食指打进去的。她打着打着,说想哭了。我赶忙告诉她,加油,全打正确了有惊喜。
终于,她敲下了回车键。映入眼帘的是:
- _ _
- * * * *
- * * * *
- * * *
- * *
- * *
- * *
- *
See Result
她忽然就开心起来,问我这个是怎么回事。我告诉她,“这说明你刚才的命令输对了,电脑按照命令画出了它~。要不再接再厉,试试下面这个更有挑战性的?”
- [#*]_@*/_(_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逆序。
可以看到这几个表达式是可以构成抽象语法树的。让我们看看代码:
- #ifndef EXPRESSION_H_INCLUDED
- #define EXPRESSION_H_INCLUDED
- #include <string>
- #include <sstream>
- using namespace std;
- // ... Abstract Class ...
- class Expression {
- public:
- Expression() {}
- virtual ~Expression() {}
- virtual void eval(stringstream&) = ;
- };
- // ... RepeatExpression Class ...
- class RepeatExpression : public Expression {
- public:
- RepeatExpression(Expression*, int);
- void eval(stringstream&);
- private:
- Expression* _oper;
- int _mNum;
- };
- // ... AddExpression Class ...
- class AddExpression : public Expression {
- public:
- AddExpression(Expression*, Expression*);
- void eval(stringstream&);
- private:
- Expression* _oper1;
- Expression* _oper2;
- };
- // ... ReverseExpression Class ...
- class ReverseExpression : public Expression {
- public:
- ReverseExpression(Expression*);
- void eval(stringstream&);
- private:
- Expression* _oper;
- };
- // ... Constant Class ...
- class Constant : public Expression {
- public:
- Constant(const char*);
- Constant(const char*, int);
- void eval(stringstream&);
- private:
- string _mStr;
- };
- #endif // EXPRESSION_H_INCLUDED
expression.h
- #include "expression.h"
- #include <algorithm>
- using namespace std;
- // ... RepeatExpression BEGIN ...
- RepeatExpression::RepeatExpression(Expression* oper, int m) {
- _oper = oper;
- _mNum = m;
- }
- void RepeatExpression::eval(stringstream& ss) {
- stringstream t_str;
- _oper->eval(t_str);
- for (int i = ; i < _mNum; i++) {
- ss << t_str.str();
- }
- }
- // ... RepeatExpression END ...
- // ... AddExpression BEGIN ...
- AddExpression::AddExpression(Expression* oper1, Expression* oper2) {
- _oper1 = oper1;
- _oper2 = oper2;
- }
- void AddExpression::eval(stringstream& ss) {
- stringstream t_str;
- _oper1->eval(t_str);
- _oper2->eval(t_str);
- ss << t_str.str();
- }
- // ... AddExpression END ...
- // ... ReverseExpression BEGIN ...
- ReverseExpression::ReverseExpression(Expression* o) {
- _oper = o;
- }
- void ReverseExpression::eval(stringstream& ss) {
- stringstream t_str;
- _oper->eval(t_str);
- string str = t_str.str();
- reverse(str.begin(), str.end());
- ss << str;
- }
- // ... ReverseExpression END ...
- // ... Constant BEGIN ...
- Constant::Constant(const char* str) {
- _mStr = string(str);
- }
- Constant::Constant(const char* str, int len) {
- _mStr = string(str, len);
- }
- void Constant::eval(stringstream& ss) {
- ss << _mStr;
- }
- // ... Constant END ...
expression.cpp
到了这里,我们如果想生成一个字符画: "~~o>_<o~~",可以这么做:
- stringstream ss;
- Expression* e1 = new RepeatExpression(new Constant("~"), );
- Expression* e2 = new AddExpression(e1, new Constant("o>"));
- Expression* e3 = new AddExpression(e2, new Constant("_"));
- Expression* result = new AddExpression(e3, new ReverseExpression(e2));
- result->eval(ss);
- cout << ss.str() << endl;
其实解释器模式部分的编程已经结束了。但显然这个并没有达到前言中翻译那串莫名字符串的目的。为此,我们还需在此基础上,定义一些语法,写一个语法分析器来将那串字符构建成抽象语法树。这里,我就偷懒了,写了个非常简单,没有什么优化的语法分析器:
- // 定义的一些符号含义:
- // [] ---- 字符集
- // () ---- 分组
- // @N ---- 取字符集中第N个字符(N从0开始)
- // *N ---- *前面的表达式重复N次
// $N ---- 取第N个分组(N从0开始,分组由括号顺序确定,嵌套的括号以从里到外的规则递增)- // + ---- 加号两边的表达式拼接
- // ^ ---- ^前面的表达式逆序
- // _N ---- 拼接N个空格
- // / ---- 拼接一个换行符
具体代码如下:
- #ifndef TRANSLATOR_H_INCLUDED
- #define TRANSLATOR_H_INCLUDED
- #include <string>
- #include <vector>
- using namespace std;
- class Expression;
- class Translator {
- public:
- Translator();
- ~Translator();
- Expression* translate(string& str);
- private:
- Expression* translateExp(string& str);
- char* _mCharSet;
- vector<Expression*> _mExpGroup;
- };
- #endif // TRANSLATOR_H_INCLUDED
Translator.h
- #include "Translator.h"
- #include "expression.h"
- #include <cstring>
- #include <cstdlib>
- using namespace std;
- Translator::Translator() {
- _mCharSet = ;
- }
- Translator::~Translator() {
- if (_mCharSet) delete[] _mCharSet;
- }
- Expression* Translator::translate(string& str) {
- Expression* result = ;
- for(unsigned int i = ; i < str.size(); i++ ) {
- if (str.at(i) == '[') {
- int sEnd = str.find_last_of("]");
- int sLen = sEnd - i - ;
- if (_mCharSet) delete[] _mCharSet;
- _mCharSet = new char[sLen];
- strcpy(_mCharSet, str.substr(i+, sLen).data());
- i = sEnd;
- } else if (str.at(i) == '@') {
- int sChar = atoi(str.substr(i + , ).c_str());
- Expression* tmp = new Constant(&_mCharSet[sChar], );
- result = tmp;
- i = i + ;
- } else if (str.at(i) == '(') {
- int pos = i + ;
- int left = ;
- for (;pos < str.size(); pos++) {
- if (str.at(pos) == ')') {
- if (left == )
- break;
- else
- left--;
- }
- if (str.at(pos) == '(')
- left++;
- }
- string t_str = str.substr(i + , pos - i - );
- Expression* tmp = translate(t_str);
- _mExpGroup.push_back(tmp);
- result = tmp;
- i = pos;
- } else if (str.at(i) == '+') {
- string t_str = str.substr(i + );
- result = new AddExpression(result, translate(t_str));
- break;
- } else if (str.at(i) == '*') {
- int pos = i+;
- for (;pos < str.size();pos++) {
- if (str.at(pos) > '' || str.at(pos) < '') break;
- }
- pos--;
- int sRep = atoi(str.substr(i + , pos - i).c_str());
- Expression* tmp = new RepeatExpression(result, sRep);
- result = tmp;
- i = pos;
- } else if (str.at(i) == '^') {
- Expression* tmp = new ReverseExpression(result);
- result = tmp;
- } else if (str.at(i) == '$') {
- int pos = i+;
- for (;pos < str.size();pos++) {
- if (str.at(pos) > '' || str.at(pos) < '') break;
- }
- pos--;
- int nGroup = atoi(str.substr(i + , pos - i).c_str());
- if (nGroup >= _mExpGroup.size()) return ;
- result = _mExpGroup[nGroup];
- i = pos;
- } else if (str.at(i) == '/') {
- string t_str = str.substr(i + );
- Expression* tmp = new Constant("\n");
- if (!result) {
- result = new AddExpression(tmp, translate(t_str));
- }
- else {
- result = new AddExpression(new AddExpression(result, tmp), translate(t_str));
- }
- break;
- } else if (str.at(i) == '_') {
- int pos = i+;
- for (;pos < str.size();pos++) {
- if (str.at(pos) > '' || str.at(pos) < '') break;
- }
- pos--;
- int sRep = (pos == i) ? : atoi(str.substr(i + , pos - i).c_str());
- string t_str = str.substr(pos + );
- Expression* tmp = new RepeatExpression(new Constant(" "), sRep);
- if (!result) {
- result = new AddExpression(tmp, translate(t_str));
- }
- else {
- result = new AddExpression(new AddExpression(result, tmp), translate(t_str));
- }
- break;
- }
- }
- return result;
- }
Translator.cpp
再次强调,这个语法分析器,并不是解释器模式所讲的内容。好了,写个简单的main函数就可以运行了:
- #include <iostream>
- #include "expression.h"
- #include "Translator.h"
- using namespace std;
- int main()
- {
- cout << "Input your command below: " << endl;
- string str;
- getline(cin, str);
- Translator translator;
- // ... Generate the Abstract Grammar Tree by Translator
- Expression* myExp = translator.translate(str);
- if (!myExp) return ;
- // ... Call Its Interpret Operation
- stringstream ss;
- myExp->eval(ss);
- cout << ss.str() << endl;
- return ;
- }
那么我们输入之前第二串字符试试:
- *****
- **
- ** ****** **** **** *****
- ** ** ** ** ** ** **
- ** ** ** ** ** ********
- ## # ## ## ## ## ##
- ## # ## ## ### ## ##
- ####### ##### ## ######
MM表示很开心。对于这个示例的UML图:
特点总结
我们可以看到,Interpreter解释器模式有以下优点和缺点:
- 易于改变和扩展文法。因为该模式使用类来表示文法规则,我们可以使用继承来改变或扩展该文法。多加一种文法就新增一个类。
- 也易于实现文法。定义抽象语法树中各个节点的类的实现大体类似。通常它们也可用一个编译器或语法分析程序生成器自动生成。
- 复杂的文法难以维护。解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。
同时我们可以看到,它和其他设计模式:Composite(组合)模式有着许多相通的地方。具体可以参见之前的笔记。
写在最后
今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!
[设计模式]解释器(Interpreter)之大胆向MM示爱吧的更多相关文章
- python 设计模式之解释器(Interpreter)模式
#写在前面 关于解释器模式,我在网上转了两三圈,心中有了那么一点概念 ,也不知道自己理解的是对还是错. 其实关于每一种设计模式,我总想找出一个答案,那就是为什么要用这种设计模式, 如果不用会怎么样,会 ...
- 设计模式(15)--Interpreter(解释器模式)--行为型
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.模式定义: 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解 ...
- Java设计模式----解释器模式
计算器中,我们输入“20 + 10 - 5”,计算器会得出结果25并返回给我们.可你有没有想过计算器是怎样完成四则运算的?或者说,计算器是怎样识别你输入的这串字符串信息,并加以解析,然后执行之,得出结 ...
- 深入浅出设计模式——解释器模式(Interpreter Pattern)
模式动机 如果在系统中某一特定类型的问题发生的频率很高,此时可以考虑将这些问题的实例表述为一个语言中的句子,因此可以构建一个解释器,该解释器通过解释这些句子来解决这些问题.解释器模式描述了如何构成一个 ...
- [工作中的设计模式]解释器模式模式Interpreter
一.模式解析 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 以上是解释器模式的类图,事实上我 ...
- 面向对象设计模式之Interpreter解释器模式(行为型)
动机:在软件构建过程中 ,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化.在这种情况下,将特定领域的问题表达为某种语法规则的句子,然后构建一个 ...
- 设计模式 -- 解释器模式(Interpreter Pattern)
2015年12月15日00:19:02 今天只看了理论和demo,明天再写文章,跑步好累 2015年12月15日21:36:00 解释器模式用来解释预先定义的文法. <大话设计模式>里面这 ...
- 设计模式之Interpreter(解释器)(转)
Interpreter定义: 定义语言的文法 ,并且建立一个解释器来解释该语言中的句子. Interpreter似乎使用面不是很广,它描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造 ...
- javascript设计模式 - 解释器模式(interpreter)
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
随机推荐
- Ubuntu 下安装Kibana和logstash
原文地址:http://www.cnblogs.com/saintaxl/p/3946667.html 简单来讲他具体的工作流程就是 logstash agent 监控并过滤日志,将过滤后的日志内容发 ...
- php非阻塞执行系统命令
大家都知道php调用系统命令常用的主要有以下几种方法: 如exec(), system(), passthru(), shell_exec() 这几个函数的用法在此不做说明,有需要的请查阅php相关手 ...
- MySQL数据库设计复习笔记及项目实战
最近手头上有3个项目开动,其他2个都是从底层开始的,一个已经开始了一段时间的了,在小城市小团队开发的条件下,都没有专门的DBA来做数据库的设计和维护,往往都是开发人员顶上,可是看了很多的数据库的设计, ...
- iptables的实战整理
一.iptables使用场景: 内网情况下使用:在大并发的情况下不要开iptables否则影响性能 二.iptables出现下面的问题: 在yewufa ...
- java数据结构-非线性结构之树
一.树状图 树状图是一种数据结构,它是由n(n>=1)个有限节点组成的具有层次关系的集合.因其结构看起来想个倒挂的树,即根朝上,叶子在下,故被称为"树". 特点: 1. 每个 ...
- CSS实现圆角,三角,五角星,五边形,爱心,12角星,8角星,圆,椭圆,圆圈,八卦
转自:http://blog.csdn.net/chenhongwu666/article/details/38905803 CSS实现圆角,三角,五角星,五边形,爱心,12角星,8角星,圆,椭圆,圆 ...
- MKDOCS在线文档编辑器
http://www.mkdocs.org/ api接口文档编写 ,效果非常不错
- 采用subversion管理iOS资源
1.装和配置subversionserver 在windows server上安装VisualSVN-Server.下载地址http://www.visualsvn.com/server/downl ...
- Linux多网卡多IP配置
echo "210 local100" >> /etc/iproute2/rt_tables echo "220 local200" >> ...
- RedHat7搭建无人值守自动安装Linux操作系统(PXE+Kickstart)
Kickstart服务器 IP: 192.168.136.253 掩码:255.255.255.0 网关:192.168.136.2 DNS:192.168.136.2 安装部署HTTP服 ...