解释器模式
什么是解释器模式
解释器模式是属于行为型模式,它提供了评估语言的语法或表达式的方式,这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
为什么使用解释器模式
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的
解释器模式实现步骤
1.提供一个解释器上下文环境类:用来存储解释器的上下文环境,比如需要解释的文法,一般用来传递被所有解释器共享的数据
2.提供一个解释器抽象类:定义解释器的接口,约定解释器的解释操作
3.提供一个终结符解释器的具体实现类:用来实现语法规则中和终结符相关的操作
4.提供一个非终结符解释器:用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他解释器
// a+b-c 表达式求值运算
#include <iostream>
#include <map>
#include <stack>
#include <string>
#include <typeinfo>
using namespace std;
//抽象表达式
class Expression {
public:
virtual int Interpreter(map<string, int>& var) = 0;
};
//变量解析器
class VarExpression : public Expression {
public:
VarExpression(string key) { this->key = key; }
int Interpreter(map<string, int>& var) { return var[key]; }
private:
string key;
};
//运算符解析器
class SymbolExpression : public Expression {
protected:
Expression* left;
Expression* right;
public:
SymbolExpression(Expression* left, Expression* right)
: left(left), right(right) {}
Expression* GetLeft() { return left; }
Expression* GetRight() { return right; }
};
//加法解析器
class AddExpresion : public SymbolExpression {
public:
AddExpresion(Expression* left, Expression* right)
: SymbolExpression(left, right) {}
int Interpreter(map<string, int>& var) {
return left->Interpreter(var) + right->Interpreter(var);
}
};
//减法解析器
class SubExpresion : public SymbolExpression {
public:
SubExpresion(Expression* left, Expression* right)
: SymbolExpression(left, right) {}
int Interpreter(map<string, int>& var) {
return left->Interpreter(var) - right->Interpreter(var);
}
};
//解析器封装类 封装调用接口
class Calculator {
private:
Expression* expression;
public:
//解析表达式,构建语法树 a+b-c
Calculator(string expStr) {
expression = NULL;
stack<Expression*> stkExp;
Expression* left = NULL;
Expression* right = NULL;
for (int i = 0; i < expStr.length(); i++) {
switch (expStr[i]) {
case '+':
//先从栈中取出左操作数
left = stkExp.top();
stkExp.pop();
//从表达式中取出+号后面的右操作数,并生成终结符解析对象
right = new VarExpression(expStr.substr(++i, 1));
//将左右操作数相加,并把结果放入栈中
stkExp.push(new AddExpresion(left, right));
break;
case '-':
//先从栈中取出左操作数
left = stkExp.top();
stkExp.pop();
//从表达式中取出+号后面的右操作数,并生成终结符解析对象
right = new VarExpression(expStr.substr(++i, 1));
//将左右操作数相加,并把结果放入栈中
stkExp.push(new SubExpresion(left, right));
break;
default:
stkExp.push(new VarExpression(expStr.substr(i, 1)));
}
}
//栈中保存的就是最终语法树的根结点
//本例为SubExpression对象
if (!stkExp.empty()) {
expression = stkExp.top();
stkExp.pop();
}
}
int Run(map<string, int>& var) {
return (expression == NULL) ? 0 : expression->Interpreter(var);
}
};
int main() {
string expstr = "a+b-c";
map<string, int> var;
var["a"] = 300;
var["b"] = 20;
var["c"] = 30;
Calculator cal(expstr);
cout << cal.Run(var) << endl;
return 0;
}
解释器模式优缺点
优点
-
扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
-
容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易
缺点
-
执行效率较低。解释器模式中通常使用大量的循环和递归调用,句子较复杂时,运行速度很慢,且代码的调试过程也比较麻烦
-
会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护
迭代器模式
什么是迭代器模式
迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式 (C++STL中的迭代器) 的情况下遍历集合中所有的元素
为什么使用迭代器模式
迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。
重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。
迭代器模式实现步骤
1.提供一个迭代器类抽象类:抽象实现迭代功能的最小定义方法集
2.提供一个迭代器类:定义实现迭代功能的最小定义方法集
3.提供一个容器抽象类:抽象基本功能以及提供类似迭代器类的方法
4.提供一个容器具体类:定义基本功能以及提供类似迭代器类的方法
//迭代器模式
#include <iostream>
#include <string>
#include <vector>
using namespace std;
template <typename T, typename U>
class Iterator {
public:
typedef typename vector<T>::iterator iter_type;
Iterator(U* data, bool reverse = false) : m_data(data) {
iter = data->m_data.begin();
}
void Begin() { iter = m_data->m_data.begin(); }
void Next() { iter++; }
bool End() { return (iter == m_data->m_data.end()); }
iter_type Current() { return iter; }
private:
U* m_data;
iter_type iter;
};
template <class T>
class Container {
friend class Iterator<T, Container>;
public:
void Add(T data) { m_data.push_back(data); }
Iterator<T, Container>* CreateIterator() {
return new Iterator<T, Container>(this);
}
protected:
vector<T> m_data;
};
class Data {
public:
Data(int data = 0) : m_data(data) {}
void SetData(int data) { m_data = data; }
int data() { return m_data; }
private:
int m_data;
};
int main() {
Container<int> test;
for (int i = 0; i < 10; i++) {
test.Add(i);
}
Iterator<int, Container<int>>* iter = test.CreateIterator();
for (iter->Begin(); !iter->End(); iter->Next()) {
cout << *iter->Current() << " ";
}
cout << endl;
Container<Data> test2;
test2.Add(Data(100));
test2.Add(Data(200));
test2.Add(Data(300));
Iterator<Data, Container<Data>>* iter2 = test2.CreateIterator();
for (iter2->Begin(); !iter2->End(); iter2->Next()) {
cout << iter2->Current()->data() << " ";
}
return 0;
}
迭代器模式优缺点
优点
-
单一职责原则: 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理
-
开闭原则: 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码
-
你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态
缺点
-
如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正
-
对于某些特殊集合, 使用迭代器可能比直接遍历的效率低
设计模式总结
创建型模式总结
-
工厂模式模式:单个类的对象创建工作
-
抽象工厂模式:多个类的对象创建工作
-
单例模式:类的全局对象创建工作
-
建造者模式:复杂类的对象创建工作
-
原型模式:自身类的克隆工作
结构体型模式总结
-
代理模式:提供第三方进行控制访问对象
-
装饰者模式:为对象添加新功能
-
适配器模式:不兼容的类进行融合过程
-
桥接模式:分离变化部分成为独立的一部分
-
组合模式:整体和局部进行递归组合
-
外观模式: 表面工程(对外提供统一接口访问子系统)
-
享元模式:使用对象池减少重复对象的创建
行为型模式总结
-
模板方法模式:通过定义一套流程模板进行流程化处理
-
命令模式:请求成为命令,记录下
-
责任链模式:踢皮球(对象连接成为链,通过链进行传递请求)
-
策略模式:封装不同算法,算法之间可以互相转换
-
中介者模式:租房中介(中介者类做转发操作)
-
观察者模式:交通警察指挥路口(状态发生改变通知观察者)
-
备忘录模式:保存对象状态,可以进行回复
-
访问者模式:稳定的数据结构,定义新的操作行为
-
状态模式:根据不同的状态做不同的行为
-
解释器模式:给定规则,定义语法,定义解释器,做句子分析(摩斯密码解析过程)
-
迭代器模式:提供一个中方法顺序的访问复合对象的各个元素
关于设计模式的建议
-
不存在完美的设计模式,只存在最适合的设计模式
-
原有的设计模式并不重要,设计原则才是最重要
-
有意无意注意模式问题,通过原则写出自己的设计方法才是最重要的