讀懂老闆的暗語,你需要知道解釋器模式!

看過《大明王朝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筆記

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章