解釋器模式

1.動機

  • 若是在系統中某一特定類型的問題發生的頻率很高,此時能夠考慮將這些問題的實例表述爲一個語言中的句子,所以能夠構建一個解釋器,該解釋器經過解釋這些句子來解決這些問題。
  • 解釋器模式描述瞭如何構成一個簡單的語言解釋器,主要應用在使用面嚮對象語言開發的編譯器中。

2.定義

解釋器模式 (Interpreter Pattern)給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。若是一種特定類型的問題發生的頻率足夠高,那麼可能就值得將該問題的各個實例表述爲一個簡單語言中的句子。這樣就能夠構建一個解釋器,該解釋器經過解釋這些句子來解決該問題。ios

  • 有個遊戲,輸入up walk 5,玩家必須按照:移動方向+移動方式+移動距離這種格式輸入個人指令,而這種格式的指令就是一種文法,只有按照了我定義的這種文法去輸入,才能控制屏幕上的小狗去移動。固然了,我輸入up walk 5,屏幕上的小狗確定是聽不懂的,它不知道我輸入的是什麼,這個時候須要怎麼辦?我須要一個工具去將我輸入的內容翻譯成小狗能聽懂的東西,而這個工具就是定義中提到的解釋器,解釋器對我輸入的指令進行解釋,而後將解釋獲得的指令發送給屏幕上的小狗,小狗聽懂了,就進行實際的移動。

3.文法規則和抽象語法樹

再拿上面的遊戲這個例子進行說明,我能夠定義如下五條文法:express

expression ::= direction action distance | composite //表達式
composite ::= expression 'and' expression //複合表達式
direction ::= 'up' | 'down' | 'left' | 'right' //移動方向
action ::= 'move' | 'walk' //移動方式
distance ::= an integer //移動距離
  • 上面的5條文法規則,對應5個語言單位,這些語言單位能夠分爲兩大類:一類爲終結符(也叫作終結符表達式),例如上面的direction、action和distance,它們是語言的最小組成單位,不能再進行拆分;另外一類爲非終結符(也叫作非終結符表達式),例如上面的expression和composite,它們都是一個完整的句子,包含一系列終結符或非終結符。
  • 咱們就是根據上面定義的一些文法能夠構成更多複雜的語句,計算機程序將根據這些語句進行某種操做;而咱們這裏列出的文法,計算機是沒法直接看懂的,因此,咱們須要對咱們定義的文法進行解釋;就比如,咱們編寫的C++代碼,計算機是看不懂的,咱們須要進行編譯同樣。解釋器模式,就提供一種模式去給計算機解釋咱們定義的文法,讓計算機根據咱們的文法去進行工做。
  • 在文法規則定義中能夠使用一些符號來表示不一樣的含義,如使用「|」表示或,使用「{」和「}」表示組合,使用「*」表示出現0次或屢次等,其中使用頻率最高的符號是表示「或」關係的「|」,如文法規則「bool Value ::= 0 | 1」表示終結符表達式bool Value的取值能夠爲0或者1。
  • 除了使用文法規則來定義一個語言,在解釋器模式中還能夠經過一種稱之爲抽象語法樹的圖形方式來直觀地表示語言的構成,每一棵語法樹對應一個語言實例,對於上面的遊戲文法規則,能夠經過如下的抽象語法樹來進行表示:

clipboard.png

4.模式結構

clipboard.png

AbstractExpression:聲明一個抽象的解釋操做,這個接口被抽象語法樹中全部的節點所共享;工具

TernimalExpression:一個句子中的每一個終結符須要該類的一個實例,它實現與文法中的終結符相關聯的解釋操做;spa

NonternimalExpression:翻譯

  • 對於文法中的每一條規則都須要一個NonternimalExpression類;
  • 爲文法中的的每一個符號都維護一個AbstractExpression類型的實例變量;
  • 爲文法中的非終結符實現解釋操做,在實現時,通常要遞歸地調用表示文法符號的那些對象的解釋操做;

Context:包含解釋器以外的一些全局信息;
Client:構建一個須要進行解釋操做的文法句子,而後調用解釋操做進行解釋。調試

實際進行解釋時,按照如下時序進行的:code

  • Client構建一個句子,它是NonterminalExpression和TerminalExpression的實例的一個抽象語法樹,而後初始化上下文並調用解釋操做;
  • 每一非終結符表達式節點定義相應子表達式的解釋操做。而各終結符表達式的解釋操做構成了遞歸的基礎;
  • 每一節點的解釋操做用上下文來存儲和訪問解釋器的狀態

5.代碼實現

#include <string> 
#include <iostream> 
using namespace std;

/************************************************************************
* description: 演奏內容
* remark:
************************************************************************/
class playContext
{
public:
    string getPlayText()
    {
        return m_strText;
    }
    void setPlayText(const string& strText)
    {
        m_strText = strText;
    }
private:
    string m_strText;
};


/************************************************************************
* description: 表達式類
* remark:
************************************************************************/
class expression
{
public:
    // 解釋器 
    void interpret(playContext& PlayContext)
    {
        if (PlayContext.getPlayText().empty())
        {
            return;
        }
        else
        {
            string strPlayKey = PlayContext.getPlayText().substr(0, 1);
            string strtemp = PlayContext.getPlayText().substr(2);
            PlayContext.setPlayText(strtemp);

            size_t nPos = PlayContext.getPlayText().find(" ");
            string strPlayValue = PlayContext.getPlayText().substr(0, nPos);
            int  nPlayValue = stoi(strPlayValue);
            
            nPos = PlayContext.getPlayText().find(" ");
            //遞歸處理
            PlayContext.setPlayText(PlayContext.getPlayText().substr(nPos + 1));
            //cout << PlayContext.getPlayText() << endl;
            excute(strPlayKey, nPlayValue);
            
        }
    }
    // 執行 
    virtual void excute(string& strKey, const int nValue) = 0;
};


/************************************************************************
* description: 音符類
* remark:
************************************************************************/
class note : public expression
{
public:
    virtual void excute(string& strKey, const int nValue)
    {
        char szKey = strKey.substr(0, 1)[0];

        string strNote;
        switch (szKey)
        {
        case 'C':
            strNote = "N1";
            break;
        case 'D':
            strNote = "N2";
            break;
        case 'E':
            strNote = "N3";
            break;
        case 'F':
            strNote = "N4";
            break;
        case 'G':
            strNote = "N5";
            break;
        case 'A':
            strNote = "N6";
            break;
        case 'B':
            strNote = "N7";
            break;
        default:
            strNote = "error";
            break;
        }
        cout << strNote << " ";
    }
};


/************************************************************************
* description: 音階類
* remark:
************************************************************************/
class scale : public expression
{
public:
    virtual void excute(string& strKey, const int nValue)
    {
        string strScale;
        switch (nValue)
        {
        case 1:
            strScale = "低音";
            break;
        case 2:
            strScale = "中音";
            break;
        case 3:
            strScale = "高音";
            break;
        default:
            strScale = "error";
            break;
        }
        cout << strScale << " ";
    }
private:
};

//音樂速度類
class speed : public expression
{
public:
    virtual void excute(string& strKey, const int nValue)
    {
        string strSpeed;
        if (nValue < 3)
        {
            strSpeed = "慢速";
        }
        else if (nValue >= 6)
        {
            strSpeed = "快速";
        }
        else
        {
            strSpeed = "中速";
        }
        cout << strSpeed << " ";
    }
};


int main()
{
    playContext context;
    cout << "Music:";

    context.setPlayText("T 2 O 2 E 2 G 3 G 4 ");
    expression* expressObj = nullptr;

    while (!context.getPlayText().empty())
    {
        char szKey = context.getPlayText().substr(0, 1)[0];

        switch (szKey)
        {
        case 'O':
            expressObj = new scale();
            break;
        case 'T':
            expressObj = new speed();
            break;
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'G':
        case 'A':
        case 'B':
        case 'P':
            expressObj = new note();
            break;
        default:
            break;
        }
        if (nullptr != expressObj)
        {
            expressObj->interpret(context);
        }
    }

    system("pause");
    return 0;
}

6.優勢

  • 易於改變和擴展文法。因爲在解釋器模式中使用類來表示語言的文法規則,所以能夠經過繼承等機制來改變或擴展文法;
  • 每一條文法規則均可以表示爲一個類,所以能夠方便地實現一個簡單的語言;
  • 實現文法較爲容易;在抽象語法樹中每個表達式節點類的實現方式都是類似的,這些類的代碼編寫都不會特別複雜;
  • 增長新的解釋表達式較爲方便。若是用戶須要增長新的解釋表達式只須要對應增長一個新的終結符表達式類或非終結符表達式類,原有表達式類代碼無須修改,符合「開閉原則」。

7.缺點

  • 對於複雜文法難以維護;在解釋器模式中,每一條規則至少須要定義一個類,所以若是一個語言包含太多文法規則,類的個數將會急劇增長,致使系統難以管理和維護,此時能夠考慮使用語法分析程序等方式來取代解釋器模式;
  • 執行效率低;因爲在解釋器模式中使用了大量的循環和遞歸調用,所以在解釋較爲複雜的句子時其速度很慢,並且代碼的調試過程也很麻煩。

8.適用場景

  • 能夠將一個須要解釋執行的語言中的句子表示爲一個抽象語法樹;
  • 一些重複出現的問題能夠用一種簡單的語言來進行表達;
  • 一個語言的文法較爲簡單
  • 執行效率不是關鍵問題
相關文章
相關標籤/搜索