一、定義 定義一個語言的文法,並且建立一個解釋器來解釋該語言中的句子,這裡的“語言”是指使用規定格式和語法的代碼。解釋器模式是一種行為型模式。 二、描述 解釋器模式是一種使用頻率相對較低但學習難度較大的設計模式,它主要用於描述如何使用面向對象語言構成一個簡單的語言解釋器,包含以下四個角色: 1、Ab ...
一、定義
定義一個語言的文法,並且建立一個解釋器來解釋該語言中的句子,這裡的“語言”是指使用規定格式和語法的代碼。解釋器模式是一種行為型模式。
二、描述
解釋器模式是一種使用頻率相對較低但學習難度較大的設計模式,它主要用於描述如何使用面向對象語言構成一個簡單的語言解釋器,包含以下四個角色:
1、AbstractExpression(抽象表達式)在抽象表達式中聲明瞭抽象的解釋操作,它是所有終結符表達式和非終結表達式的公共父類。
2、TerminalExpression(終結符表達式):TerminalExpression(終結符表達式):終結符表達式是抽象表達式的子類,它實現了與文法中的終結符相關聯的解釋操作,在句子中的每一個終結符都是該類的一個實例。通常,在一個解釋器模式中只有少數幾個終結符表達式類,它們的實例可以通過非終結符表達式組成較為複雜的句子。
3、NonterminalExpression(非終結符表達式):非終結符表達式也是抽象表達式的子類,它實現了文法中非終結符的解釋操作,由於在非終結符表達式中可以包含終結符表達式,也可以繼續包含非終結符表達式,因此其解釋操作一般通過遞歸的方式來完成。
4、Context(環境類):環境類又稱為上下文類,它用於存儲解釋器之外的一些全局信息,通常臨時存儲了需要解釋的語句。
三、例子
X公司開發了一套簡單的基於字元界面的格式化指令,可以根據輸入的指令在字元界面輸出一些格式化內容,例如輸入“LOOP 2 PRINT 楊過 SPACE SPACE PRINT 小龍女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黃蓉”,將輸出以下結果:其中,關鍵詞LOOP表示迴圈,後面的數字表示迴圈次數;PRINT表示列印,後面的字元串表示列印的內容;SPACE表示空格;BREAK表示換行;END表示迴圈結束。每一個關鍵詞對應一條指令,電腦程式將根據關鍵詞執行相應的處理操作。
Context:環境類
/// <summary>
/// 環境類:用於存儲和操作需要解釋的語句,
/// 在本實例中每一個需要解釋的單詞都可以稱為一個動作標記(ActionToker)或命令
/// </summary>
public class Context
{
private int index = -1;
private string[] tokens;
private string currentToken;
public Context(string text)
{
text = text.Replace(" ", " ");
tokens = text.Split(' ');
NextToken();
}
// 獲取下一個標記
public string NextToken()
{
if (index < tokens.Length - 1)
{
currentToken = tokens[++index];
}
else
{
currentToken = null;
}
return currentToken;
}
// 返回當前的標記
public string GetCurrentToken()
{
return currentToken;
}
// 跳過一個標記
public void SkipToken(string token)
{
if (!token.Equals(currentToken, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("錯誤提示:{0} 解釋錯誤!", currentToken);
}
NextToken();
}
// 如果當前的標記是一個數字,則返回對應的數值
public int GetCurrentNumber()
{
int number = 0;
try
{
// 將字元串轉換為整數
number = Convert.ToInt32(currentToken);
}
catch (Exception ex)
{
Console.WriteLine("錯誤提示:{0}", ex.Message);
}
return number;
}
}
Node:抽象節點類,充當抽象表達式
public abstract class Node
{
// 聲明一個方法用於解釋語句
public abstract void Interpret(Context context);
// 聲明一個方法用於執行標記對應的命令
public abstract void Execute();
}
ExpressionNode、CommandNode、LoopCommandNode:表達式節點類、語句命令節點類、迴圈命令類,充當非終結符表達式
public class ExpressionNode : Node
{
// 用於存儲多條命令的集合
private IList<Node> nodeList = new List<Node>();
public override void Interpret(Context context)
{
// 迴圈處理Context中的標記
while (true)
{
// 如果已經沒有任何標記,則退出解釋
if (context.GetCurrentToken() == null)
{
break;
}
// 如果標記為END,則不解釋END並結束本次解釋過程,可以繼續之後的解釋
else if (context.GetCurrentToken().Equals("END", StringComparison.OrdinalIgnoreCase))
{
context.SkipToken("END");
break;
}
// 如果為其它標記,則解釋標記並加入命令集合
else
{
Node node = new CommandNode();
node.Interpret(context);
nodeList.Add(node);
}
}
}
// 迴圈執行命令集合中的每一條指令
public override void Execute()
{
foreach (var node in nodeList)
{
node.Execute();
}
}
}
public class CommandNode : Node
{
private Node node;
public override void Interpret(Context context)
{
// 處理LOOP指令
if (context.GetCurrentToken().Equals("LOOP", StringComparison.OrdinalIgnoreCase))
{
node = new LoopCommand();
node.Interpret(context);
}
// 處理其他指令
else
{
node = new PrimitiveCommand();
node.Interpret(context);
}
}
public override void Execute()
{
node.Execute();
}
}
public class LoopCommand : Node
{
// 迴圈次數
private int number;
// 迴圈語句中的表達式
private Node commandNode;
public override void Interpret(Context context)
{
context.SkipToken("LOOP");
number = context.GetCurrentNumber();
context.NextToken();
// 迴圈語句中的表達式
commandNode = new ExpressionNode();
commandNode.Interpret(context);
}
public override void Execute()
{
for (int i = 0; i < number; i++)
{
commandNode.Execute();
}
}
}
PrimitiveCommandNode:基本命令類,充當終結符表達式
public class PrimitiveCommand : Node
{
private string name;
private string text;
public override void Interpret(Context context)
{
name = context.GetCurrentToken();
context.SkipToken(name);
if (!name.Equals("PRINT", StringComparison.OrdinalIgnoreCase)
&& !name.Equals("BREAK", StringComparison.OrdinalIgnoreCase)
&& !name.Equals("SPACE", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("非法命令!");
}
if (name.Equals("PRINT", StringComparison.OrdinalIgnoreCase))
{
text = context.GetCurrentToken();
context.NextToken();
}
}
public override void Execute()
{
if (name.Equals("PRINT", StringComparison.OrdinalIgnoreCase))
{
Console.Write(text);
}
else if (name.Equals("SPACE", StringComparison.OrdinalIgnoreCase))
{
Console.Write(" ");
}
else if (name.Equals("BREAK", StringComparison.OrdinalIgnoreCase))
{
Console.Write("\r\n");
}
}
}
Program:客戶端測試類
string instruction = "LOOP 2 PRINT 楊過 SPACE SPACE PRINT 小龍女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黃蓉";
Context context = new Context(instruction);
Node node = new ExpressionNode();
node.Interpret(context);
Console.WriteLine("源指令 : {0}", instruction);
Console.WriteLine("解釋後 : ");
node.Execute();
Console.ReadLine();
四、總結
1、優點
(1)解釋器模式易於改變和擴展文法。由於在解釋器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴展文法。
(2)在解釋器模式中,每一條文法規則都可以表示為一個類,因此可以方便地實現一個簡單的語言。
(3)實現文法較為容易。在抽象語法樹中每一個表達式結點類的實現方式都是相似的,這些類的代碼編寫都不會特別複雜,還可以通過一些工具自動生成結點類代碼。
(4)增加新的解釋表達式較為方便。如果用戶需要增加新的解釋表達式只需要對應增加一個新的終結符表達式或非終結符表達式類,原有表達式類代碼無須修改,符合開閉原則。
2、缺點
(1)解釋器模式對於複雜文法難以維護。在解釋器模式中,每一條規則至少需要定義-個類,因此如果一個語言包含太多的文法規則,類的個數將會急劇增加,從而導致系統難以管理和維護,此時可以考慮使用語法分析程式等方式來取代解釋器模式。
(2)其執行效率較低。由於在解釋器模式中使用了大量的迴圈和遞歸調用,因此在解釋較為複雜的句子時其速度很慢,而且代碼的調試過程也比較麻煩。