看過《大明王朝1566》嗎?這是Jungle所看過的歷史劇當中最最喜歡和推崇的一部劇。看過這部劇的小夥伴們都知道,嘉靖皇帝說話從來不會明明白白說出來,而是喜歡繞着說,或者說暗語,若不細細揣測,根本不知道嘉靖說的真實含義是什麼。比如他跟陳洪說“行到水窮處,坐看雲起時”,陳洪就意會到皇上是讓他除草;太子喜獲兒子,嘉靖給了棗和慄……要是Jungle生活在那時候,腦殼真得變大啊,整天揣測皇帝的意圖都夠了。要是有個解釋器就好了,能夠把皇帝的話解釋爲明明白白的語言!
1.解釋器模式概述
解釋器模式用於描述一個簡單的語言解釋器,主要應用於使用面嚮對象語言開發的解釋器的設計。當需要開發一個新的語言是,可以使用解釋器模式。
解釋器模式:
給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
解釋器模式需要解決的是,如果一種特定類型的問題發生的頻率足夠高,那麼可能就值得將該問題的各個實例表述爲一個簡單語言中的句子。這樣就可以構件一個解釋器,該解釋器通過解釋這些句子,來解決該問題。解釋器模式描述瞭如何爲簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子。
2.解釋器模式結構
解釋器模式的結構由抽象表達式、終結符表達式、非終結符表達式和環境類組成:
- AbstractExpression(抽象表達式):聲明瞭抽象的解釋操作interpret(),是所有終結符表達式和非終結符表達式的基類;
- TerminalExpression(終結符表達式):終結符是文法規則的組成元素中最基本的語言單位,不能再分解。終結符表達式實現了與文法規則中終結符相關的解釋操作,句子中的每一個終結符都是該類的一個實例。
- NonterminalExpression(非終結符表達式):實現了文法規則中非終結符的解釋操作,因爲非終結符表達式同樣可以包含終結符表達式,所以終結符表達式可以是非終結符表達式的成員。
- Context(環境類):即上下文類,用於存儲解釋器之外的一些全局信息,通常臨時存儲需要解釋的語句。
解釋器模式的UML圖如上所示。抽象表達式聲明瞭抽象接口interpret(),終結符表達式和非終結符表達式式具體實現了該接口。其中,終結符表達式的interpret()接口實現了具體的解釋操作,而非終結符表達式中可能包含終結符表達式或者非終結符表達式,所以非終結符表達式的interpret()接口中可能是遞歸調用每一個組成部分的interpret()方法。
3.解釋器模式代碼實例
本節Jungle使用解釋器模式實現下面一個小功能:
設計一個簡單的解釋器,使得系統可以解釋0和1的或運算和與運算(不考慮或運算和與運算的優先級,即從左往右依次運算),語句表達式和輸出結果的幾個實例如下表:
表達式及輸出結果部分實例表 表達式 輸出結果 表達式 輸出結果 1 and 1 1 0 or 0 0 1 or 1 1 1 and 1 or 0 1 1 or 0 1 0 or 1 and 0 0 1 and 0 0 0 or 1 and 1 or 1 1 0 and 0 0 1 or 0 and 1 and 0 or 0 0
結合前面敘述的解釋器模式的結構和本例,可以劃分出以下角色:
- 終結符表達式角色——值節點(ValueNode):0、1,因爲它們是表達式的基本組成元素,不可再細分
- 終結符表達式角色——運算符節點(OperatorNode):運算符號“and”和“or” ,同樣也是表達式的基本組成元素
- 非終結符表達式角色——句子節點(SentenceNode):類似於“1 and 1”這樣的表達式或者更長的組合表達式
- 上下文類角色——處理者(Handler):保存輸入的表達式和輸出的結果
由此,本例的UML實例圖如下:
3.1.抽象表達式
// 抽象表達式類
class AbstractNode
{
public:
AbstractNode(){}
// 聲明抽象接口
virtual char interpret() = 0;
};
3.2.終結符表達式角色——值節點
// 終結符表達式:ValueNode
class ValueNode :public AbstractNode
{
public :
ValueNode(){}
ValueNode(int iValue){
this->value = iValue;
}
// 實現解釋操作
char interpret(){
return value;
}
private:
int value;
};
3.3.終結符表達式角色——運算符節點
// 終結符表達式:OperationNode
class OperatorNode :public AbstractNode
{
public:
OperatorNode(){}
OperatorNode(string iOp){
this->op = iOp;
}
// 實現解釋操作
char interpret(){
if (op == "and"){
return '&';
}
else if (op == "or"){
return '|';
}
return 0;
}
private:
string op;
};
3.4.非終結符表達式角色——句子節點
每一個句子節點由“左值節點+運算符節點+右值節點”組成。
// 非終結符表達式:SentenceNode
class SentenceNode :public AbstractNode
{
public:
SentenceNode(){}
SentenceNode(AbstractNode *iLeftNode,
AbstractNode *iRightNode, AbstractNode* iOperatorNode){
this->leftNode = iLeftNode;
this->rightNode = iRightNode;
this->operatorNode = iOperatorNode;
}
char interpret(){
if (operatorNode->interpret() == '&'){
return leftNode->interpret()&rightNode->interpret();
}
else{
return leftNode->interpret()|rightNode->interpret();
}
return 0;
}
private:
AbstractNode *leftNode;
AbstractNode *rightNode;
AbstractNode *operatorNode;
};
3.5.上下文角色——處理者
處理者將處理輸入的表達式,並解釋出表達式最終的結果。
// 處理者
class Handler
{
public:
Handler(){}
void setInput(string iInput){
this->input = iInput;
}
void handle(){
AbstractNode *left = NULL;
AbstractNode *right = NULL;
AbstractNode *op = NULL;
AbstractNode *sentence = NULL;
string iInput = this->input;
vector<string>inputList;
char* inputCh = const_cast<char*>(iInput.c_str());
char *token = strtok(inputCh, " ");
while (token != NULL){
inputList.push_back(token);
token = strtok(NULL, " ");
}
for (int i = 0; i < inputList.size() - 2; i += 2){
left = new ValueNode(*(inputList[i].c_str()));
op = new OperatorNode(inputList[i + 1]);
right = new ValueNode(*(inputList[i+2].c_str()));
sentence = new SentenceNode(left, right, op);
inputList[i + 2] = string(1, sentence->interpret());
}
string tmpRes = inputList[inputList.size() - 1];
if (tmpRes == "1"){
result = 1;
}
else if (tmpRes == "0"){
result = 0;
}
else{
result = -1;
}
this->output();
}
void output(){
printf("%s = %d\n", input.c_str(), result);
}
private:
string input;
char result;
};
3.6.客戶端代碼示例和結果
#include <iostream>
#include "InterpreterPattern.h"
int main()
{
Handler *handler = new Handler();
string input_1 = "1 and 1";
string input_2 = "1 and 0";
string input_3 = "0 and 1";
string input_4 = "0 and 0";
string input_5 = "0 or 0";
string input_6 = "0 or 1";
string input_7 = "1 or 0";
string input_8 = "1 or 1";
string input_9 = "1 and 0 or 1";
string input_10 = "0 or 0 and 1";
string input_11 = "1 or 1 and 1 and 0";
string input_12 = "0 and 1 and 1 and 1";
string input_13 = "0 and 1 and 1 and 1 or 1 or 0 and 1";
handler->setInput(input_1); handler->handle();
handler->setInput(input_2); handler->handle();
handler->setInput(input_3); handler->handle();
handler->setInput(input_4); handler->handle();
handler->setInput(input_5); handler->handle();
handler->setInput(input_6); handler->handle();
handler->setInput(input_7); handler->handle();
handler->setInput(input_8); handler->handle();
handler->setInput(input_9); handler->handle();
handler->setInput(input_10); handler->handle();
handler->setInput(input_11); handler->handle();
handler->setInput(input_12); handler->handle();
handler->setInput(input_13); handler->handle();
printf("\n\n");
system("pause");
return 0;
}
運行結果如下:
4.總結
優點:
- 易於改變和擴展文法,在解釋器中使用類表示語言的文法規則,可以通過繼承等機制類改變或擴展文法;
- 每一條文法規則都可以表示爲一個類,因此可以方便地實現一個簡單的語言;
- 如果要增加新的解釋表達式,只需增加一個新的終結符表達式或非終結符表達式類,無需修改原有代碼,符合開閉原則。
缺點:
- 對於複雜文法難以維護。在解釋器模式中每一條規則至少需要定義一個類,因此如果一個語言包含太多文法規則,類的個數將會大量增加,導致系統難以管理和維護;
- 執行效率低,因爲解釋器模式中有大量循環和遞歸調用。
適用環境:
- 一些重複出現的問題可以用一種簡單的語言進行表達;
- 一個語言的文法較爲簡單;
- 不考慮執行效率的問題時可以使用解釋器模式。
歡迎關注知乎專欄:Jungle是一個用Qt的工業Robot
歡迎關注Jungle的微信公衆號:Jungle筆記