直譯器模式(Interpreter)2——跟著cc學設計系列

xinqing010發表於2014-01-15
21.2 解決方案 21.2.1 直譯器模式來解決 用來解決上述問題的一個合理的解決方案,就是使用直譯器模式。那麼什麼是直譯器模式呢? (1)直譯器模式定義 這裡的文法,簡單點說就是我們俗稱的“語法規則”。 (2)應用直譯器模式來解決的思路 要想解決當xml的結構發生改變後,不用修改解析部分的程式碼,一個自然的思路就是要把解析部分的程式碼寫成公共的,而且還要是通用的,能夠滿足各種xml取值的需要,比如:獲取單個元素的值,獲取多個相同名稱的元素的值,獲取單個元素的屬性的值,獲取多個相同名稱的元素的屬性的值,等等。 要寫成通用的程式碼,又有幾個問題要解決,如何組織這些通用的程式碼?如何呼叫這些通用的程式碼?以何種方式來告訴這些通用程式碼,客戶端的需要? 要解決這些問題,其中的一個解決方案就是直譯器模式。在描述這個模式的解決思路之前,先解釋兩個概念,一個是解析器(不是指xml的解析器),一個是直譯器。 這裡的解析器,指的是把描述客戶端呼叫要求的表示式,經過解析,形成一個抽象語法樹的程式,不是指xml的解析器。 這裡的直譯器,指的是解釋抽象語法樹,並執行每個節點對應的功能的程式。 要解決通用解析xml的問題,第一步:需要先設計一個簡單的表示式語言,在客戶端呼叫解析程式的時候,傳入用這個表示式語言描述的一個表示式,然後把這個表示式通過解析器的解析,形成一個抽象的語法樹。 第二步:解析完成後,自動呼叫直譯器來解釋抽象語法樹,並執行每個節點所對應的功能,從而完成通用的xml解析。 這樣一來,每次當xml結構發生了更改,也就是在客戶端呼叫的時候,傳入不同的表示式即可,整個解析xml過程的程式碼都不需要再修改了。 21.2.2 模式結構和說明 直譯器模式的結構如圖21.1所示: 圖21.1 直譯器模式結構圖 AbstractExpression: 定義直譯器的介面,約定直譯器的解釋操作。 TerminalExpression: 終結符直譯器,用來實現語法規則中和終結符相關的操作,不再包含其它的直譯器,如果用組合模式來構建抽象語法樹的話,就相當於組合模式中的葉子物件,可以有多種終結符直譯器。 NonterminalExpression: 非終結符直譯器,用來實現語法規則中非終結符相關的操作,通常一個直譯器對應一個語法規則,可以包含其它的直譯器,如果用組合模式來構建抽象語法樹的話,就相當於組合模式中的組合物件,可以有多種非終結符直譯器。 Context: 上下文,通常包含各個直譯器需要的資料,或是公共的功能。 Client: 客戶端,指的是使用直譯器的客戶端,通常在這裡去把按照語言的語法做的表示式,轉換成為使用直譯器物件描述的抽象語法樹,然後呼叫解釋操作。 21.2.3 直譯器模式示例程式碼 (1)先看看抽象表示式的定義,非常簡單,定義一個執行解釋的方法,示例程式碼如下: /** * 抽象表示式 */ public abstract class AbstractExpression { /** * 解釋的操作 * @param ctx 上下文物件 */ public abstract void interpret(Context ctx); } (2)再來看看終結符表示式的定義,示例程式碼如下: /** * 終結符表示式 */ public class TerminalExpression extends AbstractExpression{ public void interpret(Context ctx) { //實現與語法規則中的終結符相關聯的解釋操作 } } (3)接下來該看看非終結符表示式的定義了,示例程式碼如下: /** * 非終結符表示式 */ public class NonterminalExpression extends AbstractExpression{ public void interpret(Context ctx) { //實現與語法規則中的非終結符相關聯的解釋操作 } } (4)上下文的定義,示例程式碼如下: /** * 上下文,包含直譯器之外的一些全域性資訊 */ public class Context { } (5)最後來看看客戶端的定義,示例程式碼如下: /** * 使用直譯器的客戶 */ public class Client { //主要按照語法規則對特定的句子構建抽象語法樹 //然後呼叫解釋操作 } 看到這裡,可能有些朋友會覺得,上面的示例程式碼裡面什麼都沒有啊。這主要是因為直譯器模式是跟具體的語法規則聯絡在一起的,沒有相應的語法規則,自然寫不出對應的處理程式碼來。 但是這些示例還是有意義的,可以通過它們看出直譯器模式實現的基本架子,只是沒有內部具體的處理罷了。 21.2.4 使用直譯器模式重寫示例 通過上面的講述可以看出,要使用直譯器模式,一個重要的前提就是要定義一套語法規則,也稱為文法。不管這套文法的規則是簡單還是複雜,必須有這麼個東西,因為直譯器模式就是來按照這些規則進行解析並執行相應的功能的。 1:為表示式設計簡單的文法 為了通用,用root表示根元素,a、b、c、d等來代表元素,一個簡單的xml如下: 12345 d1 d2 d3 d4 約定表示式的文法如下: 獲取單個元素的值:從根元素開始,一直到想要獲取值的元素,元素中間用“/”分隔,根元素前不加“/”。比如表示式“root/a/b/c”就表示獲取根元素下、a元素下、b元素下的c元素的值 獲取單個元素的屬性的值:要獲取值的屬性一定是表示式的最後一個元素的屬性,在最後一個元素後面新增“.”然後再加上屬性的名稱。比如表示式“root/a/b/c.name”就表示獲取根元素下、a元素下、b元素下、c元素的name屬性的值 獲取相同元素名稱的值,當然是多個:要獲取值的元素一定是表示式的最後一個元素,在最後一個元素後面新增“$”。比如表示式“root/a/b/d$”就表示獲取根元素下、a元素下、b元素下的多個d元素的值的集合 獲取相同元素名稱的屬性的值,當然也是多個:要獲取屬性值的元素一定是表示式的最後一個元素,在最後一個元素後面新增“$”,然後在後面新增“.”然後再加上屬性的名稱,在屬性名稱後面也新增“$”。比如表示式“root/a/b/d$.id$”就表示獲取根元素下、a元素下、b元素下的多個d元素的id屬性的值的集合 2:示例說明 為了示例的通用性,就使用上面這個xml來實現功能,不去使用前面定義的具體的xml了,解決的方法是一樣的。 另外一個問題,直譯器模式主要解決的是“解釋抽象語法樹,並執行每個節點所對應的功能”,並不包含如何從一個表示式轉換成為抽象的語法樹。因此下面的範例就先來實現直譯器模式所要求的功能。至於如何從一個表示式轉換成為相應的抽象語法樹,後面會給出一個示例。 對於抽象的語法樹這個樹狀結構,很明顯可以使用組合模式來構建。直譯器模式把需要解釋的物件分成了兩大類,一類是節點元素,就是可以包含其它元素的組合元素,比如非終結符元素,對應成為組合模式的Composite;另一類是終結符元素,相當於組合模式的葉子物件。解釋整個抽象語法樹的過程,也就是執行相應物件的功能的過程。 比如上面的xml,對應成為抽象語法樹,可能的結構如下圖21.2所示: 圖21.2 xml對應的抽象語法樹示意圖 3:具體示例 從簡單的開始,先來演示獲取單個元素的值和單個元素的屬性的值。在看具體程式碼前,先來看看此時系統的整體結構,如圖21.3所示: 圖21.3 直譯器模式示例的結構示意圖 (1)定義抽象的直譯器 要實現直譯器的功能,首先定義一個抽象的直譯器,來約束所有被解釋的語法物件,也就是節點元素和終結符元素都要實現的功能。示例程式碼如下: /** * 用於處理自定義Xml取值表示式的介面 */ public abstract class ReadXmlExpression { /** * 解釋表示式 * @param c 上下文 * @return 解析過後的值,為了通用,可能是單個值,也可能是多個值, * 因此就返回一個陣列 */ public abstract String[] interpret(Context c); } (2)定義上下文 上下文是用來封裝直譯器需要的一些全域性資料,也可以在裡面封裝一些直譯器的公共功能,可以相當於各個直譯器的公共物件,示例程式碼如下: /** * 上下文,用來包含直譯器需要的一些全域性資訊 */ public class Context { /** * 上一個被處理的元素 */ private Element preEle = null; /** * Dom解析Xml的Document物件 */ private Document document = null; /** * 構造方法 * @param filePathName 需要讀取的xml的路徑和名字 * @throws Exception */ public Context(String filePathName) throws Exception{ //通過輔助的Xml工具類來獲取被解析的xml對應的Document物件 this.document = XmlUtil.getRoot(filePathName); } /** * 重新初始化上下文 */ public void reInit(){ preEle = null; } /** * 各個Expression公共使用的方法, * 根據父元素和當前元素的名稱來獲取當前的元素 * @param pEle 父元素 * @param eleName 當前元素的名稱 * @return 找到的當前元素 */ public Element getNowEle(Element pEle,String eleName){ NodeList tempNodeList = pEle.getChildNodes(); for(int i=0;i

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26715458/viewspace-1070987/,如需轉載,請註明出處,否則將追究法律責任。

相關文章