瞭解23種設計模式之解釋器模式
什麼是解釋器
Interpreter模式 也叫解釋器模式,是 行爲模式之一,它是一種特殊的設計模式,它建立一個解釋器,對於特定的計算機程序設計語言,用來解釋預先定義的方法。簡單地說, Interpreter模式是一種簡單的語法解釋器構架。
解釋器的定義與特點以及結構
定義:給分析對象定義一個語言,並定義該語言的文法表示,再設計一個解析器來解釋語言中的句子。也就是說,用編譯語言的方式來分析應用中的實例。這種模式實現了文法表達式處理的接口,該接口解釋一個特定的上下文(這裏提到的文法和句子的概念同編譯原理中的描述相同,“文法”指語言的語法規則,而“句子”是語言集中的元素。例如,漢語中的句子有很多,“我是中國人”是其中的一個句子,可以用一棵語法樹來直觀地描述語言中的句子。)
-
文法是用於描述語言的語法結構的形式規則。沒有規矩不成方圓,例如,有些人認爲完美愛情的準則是“相互吸引、感情專一、任何一方都沒有戀愛經歷”,雖然最後一條準則較苛刻,但任何事情都要有規則,語言也一樣,不管它是機器語言還是自然語言,都有它自己的文法規則。例如,中文中的“句子”的文法如下。
〈句子〉::=〈主語〉〈謂語〉〈賓語〉
〈主語〉::=〈代詞〉|〈名詞〉
〈謂語〉::=〈動詞〉
〈賓語〉::=〈代詞〉|〈名詞〉
〈代詞〉你|我|他
〈名詞〉7大學生I筱霞I英語
〈動詞〉::=是|學習注:這裏的符號“::=”表示“定義爲”的意思,用“〈”和“〉”括住的是非終結符,沒有括住的是終結符。
- 句子是語言的基本單位,是語言集中的一個元素,它由終結符構成,能由“文法”推導出。例如,上述文法可以推出“我是大學生”,所以它是句子。
- 語法樹是句子結構的一種樹型表示,它代表了句子的推導結果,它有利於理解句子語法結構的層次。圖 1 所示是“我是大學生”的語法樹。
優點
- 擴展性好。由於在解釋器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴展文法。
- 容易實現。在語法樹中的每個表達式節點類都是相似的,所以實現其文法較爲容易。
缺點
- 執行效率較低。解釋器模式中通常使用大量的循環和遞歸調用,當要解釋的句子較複雜時,其運行速度很慢,且代碼的調試過程也比較麻煩。
- 會引起類膨脹。解釋器模式中的每條規則至少需要定義一個類,當包含的文法規則很多時,類的個數將急劇增加,導致系統難以管理與維護。
- 可應用的場景比較少。在軟件開發中,需要定義語言文法的應用實例非常少,所以這種模式很少被使用到。
- 抽象表達式(Abstract Expression)角色:定義解釋器的接口,約定解釋器的解釋操作,主要包含解釋方法 interpret()。
- 終結符表達式(Terminal Expression)角色:是抽象表達式的子類,用來實現文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表達式與之相對應。
非終結符表達式(Nonterminal Expression)角色:也是抽象表達式的子類,用來實現文法中與非終結符相關的操作,文法中的每條規則都對應於一個非終結符表達式。 - 環境(Context)角色:通常包含各個解釋器需要的數據或是公共的功能,一般用來傳遞被所有解釋器共享的數據,後面的解釋器可以從這裏獲取這些值。
- 客戶端(Client):主要任務是將需要分析的句子或表達式轉換成使用解釋器對象描述的抽象語法樹,然後調用解釋器的解釋方法,當然也可以通過環境角色間接訪問解釋器的解釋方法。
解析器結構
- Context : 解釋器上下文環境類,用來存儲解析器的上下文環境,比如需要解釋的文法等。
- AbstratctExpression 解釋器抽象類。
- ConcreteExpression 解釋器具體實現類。
代碼實現
下面展示一些 簡單實現
。
//上下文用來保存文法
public class Context {
private String input;
private int output;
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public int getOutput() {
return output;
}
public void setOutput(int output) {
this.output = output;
}
}
//抽象解釋器
public abstract class Expression extends Context{
public abstract void interpret(Context context);
}
//具體實現遞增解釋器
public class PlusExpression extends Expression{
@Override
public void interpret(Context context) {
//提示信息
System.out.println("自動遞增");
//獲得上下文
String input = context.getInput();
//進行 類型轉換
int intInput = Integer.parseInt(input);
//進行遞增
++intInput;
//對上下文環境重新賦值,保持最新的值
context.setInput(String.valueOf(intInput));
context.setOutput(intInput);
}
}
// 遞減具體實現解釋器 代碼相同,此處省略
//測試
public class MainClass {
public static void main(String[] args) {
Context context = new Context();
context.setInput("10");
PlusExpression plusExpression = new PlusExpression();
plusExpression.interpret(context);
System.out.println(context.getInput());
}
}
結果
示例2:
package interpreterPattern;
import java.util.*;
/*文法規則
<expression> ::= <city>的<person>
<city> ::= 韶關|廣州
<person> ::= 老人|婦女|兒童
*/
public class InterpreterPatternDemo
{
public static void main(String[] args)
{
Context bus=new Context();
bus.freeRide("韶關的老人");
bus.freeRide("韶關的年輕人");
bus.freeRide("廣州的婦女");
bus.freeRide("廣州的兒童");
bus.freeRide("山東的兒童");
}
}
//抽象表達式類
interface Expression
{
public boolean interpret(String info);
}
//終結符表達式類
class TerminalExpression implements Expression
{
private Set<String> set= new HashSet<String>();
public TerminalExpression(String[] data)
{
for(int i=0;i<data.length;i++)set.add(data[i]);
}
public boolean interpret(String info)
{
if(set.contains(info))
{
return true;
}
return false;
}
}
//非終結符表達式類
class AndExpression implements Expression
{
private Expression city=null;
private Expression person=null;
public AndExpression(Expression city,Expression person)
{
this.city=city;
this.person=person;
}
public boolean interpret(String info)
{
String s[]=info.split("的");
return city.interpret(s[0])&&person.interpret(s[1]);
}
}
//環境類
class Context
{
private String[] citys={"韶關","廣州"};
private String[] persons={"老人","婦女","兒童"};
private Expression cityPerson;
public Context()
{
Expression city=new TerminalExpression(citys);
Expression person=new TerminalExpression(persons);
cityPerson=new AndExpression(city,person);
}
public void freeRide(String info)
{
boolean ok=cityPerson.interpret(info);
if(ok) System.out.println("您是"+info+",您本次乘車免費!");
else System.out.println(info+",您不是免費人員,本次乘車扣費2元!");
}
}
解釋器模式的應用場景
1.當語言的文法較爲簡單,且執行效率不是關鍵問題時。
2.當問題重複出現,且可以用一種簡單的語言來進行表達時。
3.當一個語言需要解釋執行,並且語言中的句子可以表示爲一個抽象語法樹的時候,如 XML 文檔解釋。
注意:解釋器模式在實際的軟件開發中使用比較少,因爲它會引起效率、性能以及維護等問題。如果碰到對錶達式的解釋,在 Java 中可以用 Expression4J 或 Jep 等來設計。
解釋器模式的擴展
在項目開發中,如果要對數據表達式進行分析與計算,無須再用解釋器模式進行設計了,Java 提供了以下強大的數學公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它們可以解釋一些複雜的文法,功能強大,使用簡單。
現在以 Jep 爲例來介紹該工具包的使用方法。Jep 是 Java expression parser 的簡稱,即 Java 表達式分析器,它是一個用來轉換和計算數學表達式的 Java 庫。通過這個程序庫,用戶可以以字符串的形式輸入一個任意的公式,然後快速地計算出其結果。而且 Jep 支持用戶自定義變量、常量和函數,它包括許多常用的數學函數和常量。使用前先下載 Jep 壓縮包,解壓後,將 jep-x.x.x.jar 文件移到選擇的目錄中,在 Eclipse 的“Java 構建路徑”對話框的“庫”選項卡中選擇“添加外部 JAR(X)…”,將該 Jep 包添加項目中後即可使用其中的類庫。
package interpreterPattern;
import com.singularsys.jep.*;
public class JepDemo{
public static void main(String[] args) throws JepException{
Jep jep=new Jep();
//定義要計算的數據表達式
String 存款利息="本金*利率*時間";
//給相關變量賦值
jep.addVariable("本金",10000);
jep.addVariable("利率",0.038);
jep.addVariable("時間",2);
jep.parse(存款利息); //解析表達式
Object accrual=jep.evaluate(); //計算
System.out.println("存款利息:"+accrual);
}
}
程序運行結果如下: