设计模式之解释器模式

时间:2022-02-12 14:44:48

解释器模式

  解释器(Interpreter)模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

  解释器模式需要解决的问题是:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。正则表达式就是一种解释器模式的应用。早期字符串匹配都是为某种特殊的应用场景单独写一个函数来做,后来使用通用的搜索算法来解释执行一个正则表达式,该正则表达式定义了带匹配字符串的集合。而所谓的解释器模式,正则表达式就是它的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。

解释器模式UML类图

设计模式之解释器模式

Context:上下文,通常包含各个解释器所需要的公共功能或者数据,这个Context在解释器模式中起着非常重要的作用,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。

Client:客户端,指的是使用解释器的客户端。通常在这里将按照语言的语法做的表达式转换成解释器对象描述的抽象语法树,然后调用解释操作。

AbstractEcpression:定义解释器的接口,约定解释器的解释操作,其中的Interpret方法,正如其名字一样,专门用来解释该解释器所要实现的功能。这个解释操作为抽象语法树中所有的结点所共享。

TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其它的解释器。如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子节对象,可以有多种终结符解释器。文法中,每一个终结符都有一个具体的终结表达式与之相对应

NonterminalExpression:非终结符解释器,用来实现语法树中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其它解释器。如果用组合模式构建抽象语法树的话,就相当于组合模式中的分支节点。可以有多种非终结符解释器。对文法中每一条规则R1、R2、……Rn都需要一个具体的非终结符表达式类。通过实现抽象表达式中的Interpret方法来实现解释操作。解释操作以递归方式调用上面提到的代表R1、R2……Rn中各个符号的实例变量。

客户端代码:

  构建表示该文法定义的语言中一个特定的句子的抽象语法树。调用解释操作。

(本段内容来自于:https://blog.csdn.net/zang141588761/article/details/53483645         感觉讲的比《大话模式》这个书里的要好一些,就摘了过来)

解释器模式的优缺点

优点:

  1.可以很容易的扩展和改变文法,因为该模式使用类来表示文法规则,你可以使用继承来改变或者扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。

缺点:

  1.解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多负责的文法可能难以管理和维护,建议当文法非常复杂时,使用其它的技术如语法分析程序或者编译器生成器来处理。

使用场景:

  通常当有一个语言需要解释执行,并且你可将该语言中的句子表示成一个抽象语法树时,可以使用解释器模式。

代码示例

  实现《大话设计模式》里讲的音乐解释器。规则是:O表示音阶,“O 1“”表示低音阶,“O 2“”表示中音阶,“O 3“”表示高音阶,P表示休止符,CDEFGAB表示“Do-Re-Mi-Fa-So-La-Ti”;音符长度1表示一拍,2表示二拍,0.5表示半拍,0.25表示四分之一拍,一次类推;所有的字母和数字都要用半角空格分开。输出为音乐的简谱。

1.AbstractExpression类

设计模式之解释器模式设计模式之解释器模式
#ifndef ABSTRACTEXPRESSION_H_
#define ABSTRACTEXPRESSION_H_

#include "Context.h"
#include <sstream>
#include <vector>

class AbstractExpression
{
public:
    //解释器
    void interpret(Context &musicContext)
    {
    std::string strPlayContext = musicContext.getPlayContext();
    if(true == strPlayContext.empty())
        return;
    else
    {
        //原始输入形如:"O 1 E 0.5 G 0.5 A 3 ",每个字母后跟一个空格,开始的"O 1"表示的是音阶,在对原始输入进行解释的首,第一步就是先把音阶部分提取出来
        //这里利用了stringstream流以空格作为结束标志的特性来提取音阶,可以看到音阶完整的音阶是4个字符(不考虑不够四个字符的情况,就是简单写一下)
        std::vector<std::string> vecScale;
        std::string strBuff;
        std::stringstream ssIn(strPlayContext);
        while(ssIn >> strBuff)
        vecScale.push_back(strBuff);
        std::string strPlayKey = vecScale.at(0);
        std::string strPlayValue = vecScale.at(1);
        execute(strPlayKey,strPlayValue);        //演奏
        //把演奏完的部分从演奏内容中去掉
        vecScale.erase(vecScale.begin(),vecScale.begin()+2);
        //形成新的演奏内容,并更新Context保有的演奏内容
        std::vector<std::string>::iterator it;
        std::string strNewPlayContext;
        for(it = vecScale.begin();it != vecScale.end();++it)
        {
        strNewPlayContext += *it;
        if(it != vecScale.end()-1)
            strNewPlayContext += " ";
        }
        musicContext.setPlayContext(strNewPlayContext);
        }
    }
    virtual void execute(std::string strKey,std::string strValue) const = 0;
    AbstractExpression() = default;
    virtual ~AbstractExpression() = default;
};
#endif
AbstractExpression

2.Context类

设计模式之解释器模式设计模式之解释器模式
#ifndef CONTEXT_H_
#define CONTEXT_H_
//演奏内容类,这里是解释器需要的公共数据
#include <string>

class Context
{
private:
    std::string m_strPlayContext;   //演奏文本
public:
    void setPlayContext(const std::string &strPlayContext)
    {
    m_strPlayContext = strPlayContext;
    }
    const std::string& getPlayContext()const
    {
    return m_strPlayContext;
    }
};
#endif
Context

3.进行表达式解析的两个子规则表达式解析器

设计模式之解释器模式设计模式之解释器模式
#ifndef NOTE_H_
#define NOTE_H_

#include "AbstractExpression.h"
#include <iostream>

//音符类----叶子结点
class TerminalExpression : public AbstractExpression
{
public:
    void execute(std::string strPlayKey,std::string strPlayValue) const override;
    TerminalExpression() = default;
    ~TerminalExpression() = default;
};
#endif 

#include "Note.h"

void TerminalExpression::execute(std::string strPlayKey,std::string strPlayValue) const 
{
    //如果key是C则演奏1,依次类推
    std::string strNote = "";
    switch(strPlayKey[0])
    {
    case 'C':
        strNote = "1";
    break;
    case 'D':
        strNote = "2";
    break;
    case 'E':
        strNote = "3";
    break;
    case 'F':
        strNote = "4";
    break;
    case 'G':
        strNote = "5";
    break;
    case 'A':
        strNote = "6";
    break;
    case 'B':
        strNote = "7";
    break;
    default:
    break;
    }
    std::cout << strNote << " ";
}

#ifndef SCALE_H_
#define SCALE_H_

#include "AbstractExpression.h"
#include <iostream>

class Scale : public AbstractExpression
{
public:
    void execute(std::string strPlayKey,std::string strPlayValue) const override;
    Scale() = default;
    ~Scale() = default;
};
#endif

#include "Scale.h"

//音阶类
void Scale::execute(std::string strPlayKey,std::string strPlayValue) const
{
    //如果获得的key是O,并且value是1则演奏低音,value是2则演奏中音,value是3则演奏高音
    std::string strScale="";
    switch(strPlayValue[0])
    {
    case '1':
        strScale = "Di Yin";
    break;
    case '2':
        strScale = "Zhong Yin";
    break;
    case '3':
        strScale = "Gao Yin";
    break;
    default:
    break;
    }
    std::cout << strScale << " ";
}
TerminalExpression

4.Client

设计模式之解释器模式设计模式之解释器模式
#include "Context.h"
#include "AbstractExpression.h"
#include "Note.h"
#include "Scale.h"

using namespace std;

int main(int argc,char *argv[])
{
    std::cout <<"Shang hai tan :" ;
    Context objContext;
    objContext.setPlayContext("O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 ");
    AbstractExpression *objExpression = nullptr;
    while(objContext.getPlayContext().size() > 0)
    {
    //这段while循环的意思其实就是不同的输入按照不同的规则解释
    char c = objContext.getPlayContext()[0];
    switch(c)
    {
    case 'O':
        //如果是音阶,则将objExpression实例化为音阶类
    {
        objExpression = new Scale;
    }
    break;
    case 'C':case 'D':case 'E':case 'F':case'G':case 'A':case 'B':case 'P':
    {
        objExpression = new TerminalExpression;   //如果字母CDEFGABP,则实例化为Note
    }
    break;
    default:
    break;
    }
    objExpression->interpret(objContext);
    if(nullptr != objExpression)
    {
        delete objExpression;
        objExpression = nullptr;
    }
    }
    std::cout << std::endl;
    return(1);
}
Client