考慮這樣一個實際的應用,維護系統自定義的配置文件。html
幾乎每一個實際的應用系統都有與應用自身相關的配置文件,這個配置文件是由開發人員根據須要自定義的,系統運行時會根據配置的數據進行相應的功能處理。java
系統現有的配置數據很簡單,主要是JDBC所須要的數據,還有默認讀取Spring的配置文件,目前系統只須要一個Spring的配置文件。示例以下:spring
<?xml version="1.0" encoding="UTF-8"?> <root> <jdbc> <driver-class>驅動類名</driver-class> <url>鏈接數據庫的URL</url> <user>鏈接數據庫的用戶名</user> <password>鏈接數據庫的密碼</password> </jdbc> <application-xml>缺省讀取的Spring配置的文件名稱</application-xml> </root>
如今的功能需求是:如何可以靈活的讀取配置文件的內容?數據庫
不就是讀取配置文件嗎?實現很簡單,直接讀取並解析xml就能夠了。讀取xml的應用包不少,這裏都不用,直接採用最基礎的Dom解析就能夠了。另外,讀取到xml中的值事後,後續如何處理,這裏也不去管,這裏只是實現把配置文件讀取並解析出來。設計模式
按照這個思路,很快就寫出了實現的代碼,示例代碼以下:數組
/** * 讀取配置文件 */ public class ReadAppXml { /** * 讀取配置文件內容 * @param filePathName 配置文件的路徑和文件名 * @throws Exception */ public void read(String filePathName)throws Exception{ Document doc = null; //創建一個解析器工廠 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //得到一個DocumentBuilder對象,這個對象表明了具體的DOM解析器 DocumentBuilder builder=factory.newDocumentBuilder(); //獲得一個表示XML文檔的Document對象 doc=builder.parse(filePathName); //去掉XML中做爲格式化內容的空白而映射在DOM樹中的Text Node對象 doc.normalize(); //獲取jdbc的配置值 NodeList jdbc = doc.getElementsByTagName("jdbc"); //只有一個jdbc,獲取jdbc中的驅動類的名稱 NodeList driverClassNode = ((Element)jdbc.item(0)) .getElementsByTagName("driver-class"); String driverClass = driverClassNode.item(0) .getFirstChild().getNodeValue(); System.out.println("driverClass=="+driverClass); //同理獲取url、user、password等的值 NodeList urlNode = ((Element)jdbc.item(0)) .getElementsByTagName("url"); String url=urlNode.item(0).getFirstChild().getNodeValue(); System.out.println("url=="+url); NodeList userNode = ((Element)jdbc.item(0)) .getElementsByTagName("user"); String user = userNode.item(0).getFirstChild() .getNodeValue(); System.out.println("user=="+user); NodeList passwordNode = ((Element)jdbc.item(0)) .getElementsByTagName("password"); String password = passwordNode.item(0).getFirstChild() .getNodeValue(); System.out.println("password=="+password); //獲取application-xml NodeList applicationXmlNode = doc.getElementsByTagName("application-xml"); String applicationXml = applicationXmlNode.item(0) .getFirstChild().getNodeValue(); System.out.println("applicationXml=="+applicationXml); } }
看了上面的實現,多簡單啊,就是最基本的Dom解析嘛,要是採用其它的開源工具包,好比dom4j、jDom之類的來處理,會更簡單,這好像不值得一提呀,真的是這樣嗎?緩存
請思考一個問題:若是配置文件的結構須要變更呢?仔細想一想,就會感受出問題來了。仍是先看例子,而後再來總結這個問題。服務器
隨着開發的深刻進行,愈來愈多可配置的數據被抽取出來,須要添加到配置文件中,好比與數據庫的鏈接配置:就加入了是否須要、是否使用DataSource等配置。除了這些還加入了一些其它須要配置的數據,例如:系統管理員、日誌記錄方式、緩存線程的間隔時長、默認讀取哪些Spring配置文件等等,示例以下:多線程
<?xml version="1.0" encoding="UTF-8"?> <root> <database-connection> <connection-type>鏈接數據庫的類型,1-用Spring集成的方式 (也就是不用下面兩種方式了),2-DataSource(就是使用JNDI), 3-使用JDBC本身來鏈接數據庫 </connection-type> <jndi>DataSource的方式用,服務器數據源的JNDI名稱</jndi> <jdbc>跟上面同樣,省略了</jdbc> </database-connection> <system-operator>系統管理員ID</system-operator> <log> <operate-type>記錄日誌的方式,1-數據庫,2-文件</operate-type> <file-name>記錄日誌的文件名稱</file-name> </log> <thread-interval>緩存線程的間隔時長</thread-interval> <spring-default> <application-xmls> <application-xml> 缺省讀取的Spring配置的文件名稱 </application-xml> <application-xml> 其它須要讀取的Spring配置的文件名稱 </application-xml> </application-xmls> </spring-default> </root>
有朋友可能會想,改變一下配置文件,值得大驚小怪嗎?對於應用系統開發來說,這不是常常發生的、很普通的一件事情嘛。併發
的確是這樣,改變一下配置文件不是件大事情,可是帶來的一系列麻煩也不容忽視,好比:修改了配置文件的結構,那麼讀取配置文件的程序就須要作出相應的變動;用來封裝配置文件數據的數據對象也須要相應的修改;外部使用配置文件的地方,獲取數據的地方也會相應變更。
固然在這一系列麻煩中,最讓人痛苦的莫過於修改讀取配置文件的程序了,有時候幾乎是重寫。好比在使用Dom讀取第一個配置文件,讀取默認的Spring配置文件的值的時候,可能的片段代碼示例以下:
//獲取application-xml NodeList applicationXmlNode = doc.getElementsByTagName("application-xml"); String applicationXml = applicationXmlNode.item(0) .getFirstChild().getNodeValue(); System.out.println("applicationXml=="+applicationXml);
可是若是配置文件改爲第二個,文件的結構發生了改變,須要讀取的配置文件變成了多個了,讀取的程序也發生了改變,並且application-xml節點也不是直接從doc下獲取了。幾乎是徹底重寫了,此時可能的片段代碼示例以下:
//先要獲取spring-default,而後獲取application-xmls //而後才能獲取application-xml NodeList springDefaultNode = doc.getElementsByTagName("spring-default"); NodeList appXmlsNode = ((Element)springDefaultNode.item(0)) .getElementsByTagName("application-xmls"); NodeList appXmlNode = ((Element)appXmlsNode.item(0)) .getElementsByTagName("application-xml"); //循環獲取每一個application-xml元素的值 for(int i=0;i<appXmlNode.getLength();i++){ String applicationXml = appXmlNode.item(i) .getFirstChild().getNodeValue(); System.out.println("applicationXml=="+applicationXml); }
仔細對比上面在xml變化先後讀取值的代碼,你會發現,因爲xml結構的變化,致使讀取xml文件內容的代碼,基本上徹底重寫了。
問題還不只僅限於讀取元素的值,一樣體如今讀取屬性上。可能有些朋友說能夠換不一樣的xml解析方式來簡化,不是還有Sax解析,實在不行換用其它開源的解決方案。
確實經過使用不一樣的解析xml的方式是會讓程序變得簡單點,可是每次xml的結構發生變化事後,或多或少都是須要修改程序中解析xml部分的。
有沒有辦法解決這個問題呢?也就是當xml的結構發生改變事後,可以很方便的獲取相應元素、或者是屬性的值,而不用再去修改解析xml的程序。
用來解決上述問題的一個合理的解決方案,就是使用解釋器模式。那麼什麼是解釋器模式呢?
(1)解釋器模式定義
這裏的文法,簡單點說就是咱們俗稱的「語法規則」。
(2)應用解釋器模式來解決的思路
要想解決當xml的結構發生改變後,不用修改解析部分的代碼,一個天然的思路就是要把解析部分的代碼寫成公共的,並且還要是通用的,可以知足各類xml取值的須要,好比:獲取單個元素的值,獲取多個相同名稱的元素的值,獲取單個元素的屬性的值,獲取多個相同名稱的元素的屬性的值,等等。
要寫成通用的代碼,又有幾個問題要解決,如何組織這些通用的代碼?如何調用這些通用的代碼?以何種方式來告訴這些通用代碼,客戶端的須要?
要解決這些問題,其中的一個解決方案就是解釋器模式。在描述這個模式的解決思路以前,先解釋兩個概念,一個是解析器(不是指xml的解析器),一個是解釋器。
這裏的解析器,指的是把描述客戶端調用要求的表達式,通過解析,造成一個抽象語法樹的程序,不是指xml的解析器。
這裏的解釋器,指的是解釋抽象語法樹,並執行每一個節點對應的功能的程序。
要解決通用解析xml的問題,第一步:須要先設計一個簡單的表達式語言,在客戶端調用解析程序的時候,傳入用這個表達式語言描述的一個表達式,而後把這個表達式經過解析器的解析,造成一個抽象的語法樹。
第二步:解析完成後,自動調用解釋器來解釋抽象語法樹,並執行每一個節點所對應的功能,從而完成通用的xml解析。
這樣一來,每次當xml結構發生了更改,也就是在客戶端調用的時候,傳入不一樣的表達式便可,整個解析xml過程的代碼都不須要再修改了。
解釋器模式的結構如圖所示:
AbstractExpression:
定義解釋器的接口,約定解釋器的解釋操做。
TerminalExpression:
終結符解釋器,用來實現語法規則中和終結符相關的操做,再也不包含其它的解釋器,若是用組合模式來構建抽象語法樹的話,就至關於組合模式中的葉子對象,能夠有多種終結符解釋器。
NonterminalExpression:
非終結符解釋器,用來實現語法規則中非終結符相關的操做,一般一個解釋器對應一個語法規則,能夠包含其它的解釋器,若是用組合模式來構建抽象語法樹的話,就至關於組合模式中的組合對象,能夠有多種非終結符解釋器。
Context:
上下文,一般包含各個解釋器須要的數據,或是公共的功能。
Client:
客戶端,指的是使用解釋器的客戶端,一般在這裏去把按照語言的語法作的表達式,轉換成爲使用解釋器對象描述的抽象語法樹,而後調用解釋操做。
(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 { //主要按照語法規則對特定的句子構建抽象語法樹 //而後調用解釋操做 }
看到這裏,可能有些朋友會以爲,上面的示例代碼裏面什麼都沒有啊。這主要是由於解釋器模式是跟具體的語法規則聯繫在一塊兒的,沒有相應的語法規則,天然寫不出對應的處理代碼來。
可是這些示例仍是有意義的,能夠經過它們看出解釋器模式實現的基本架子,只是沒有內部具體的處理罷了
經過上面的講述能夠看出,要使用解釋器模式,一個重要的前提就是要定義一套語法規則,也稱爲文法。無論這套文法的規則是簡單仍是複雜,必須有這麼個東西,由於解釋器模式就是來按照這些規則進行解析並執行相應的功能的。
1:爲表達式設計簡單的文法
爲了通用,用root表示根元素,a、b、c、d等來表明元素,一個簡單的xml以下:
<?xml version="1.0" encoding="UTF-8"?> <root id="rootId"> <a> <b> <c name="testC">12345</c> <d id="1">d1</d> <d id="2">d2</d> <d id="3">d3</d> <d id="4">d4</d> </b> </a> </root>
約定表達式的文法以下:
獲取單個元素的值:從根元素開始,一直到想要獲取值的元素,元素中間用「/」分隔,根元素前不加「/」。好比表達式「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,對應成爲抽象語法樹,可能的結構以下圖所示:
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<tempNodeList.getLength();i++){ if(tempNodeList.item(i) instanceof Element){ Element nowEle = (Element)tempNodeList.item(i); if(nowEle.getTagName().equals(eleName)){ return nowEle; } } } return null; } public Element getPreEle() { return preEle; } public void setPreEle(Element preEle) { this.preEle = preEle; } public Document getDocument() { return document; } }
在上下文中使用了一個工具對象XmlUtil來獲取Document對象,就是Dom解析xml,獲取相應的Document對象,示例以下:
public class XmlUtil { public static Document getRoot(String filePathName) throws Exception{ Document doc = null; //創建一個解析器工廠 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //得到一個DocumentBuilder對象,這個對象表明了具體的DOM解析器 DocumentBuilder builder=factory.newDocumentBuilder(); //獲得一個表示XML文檔的Document對象 doc=builder.parse(filePathName); //去掉XML文檔中做爲格式化內容的空白而映射在DOM樹中的TextNode對象 doc.normalize(); return doc; } }
(3)定義元素做爲非終結符對應的解釋器
接下來該看看如何解釋執行中間元素了,首先這個元素至關於組合模式的Composite對象,所以須要對子元素進行維護,另外這個元素的解釋處理,只是須要把本身找到,做爲下一個元素的父元素就行了。示例代碼以下:
/** * 元素做爲非終結符對應的解釋器,解釋並執行中間元素 */ public class ElementExpression extends ReadXmlExpression{ /** * 用來記錄組合的ReadXmlExpression元素 */ private Collection<ReadXmlExpression> eles = new ArrayList<ReadXmlExpression>(); /** * 元素的名稱 */ private String eleName = ""; public ElementExpression(String eleName){ this.eleName = eleName; } public boolean addEle(ReadXmlExpression ele){ this.eles.add(ele); return true; } public boolean removeEle(ReadXmlExpression ele){ this.eles.remove(ele); return true; } public String[] interpret(Context c) { //先取出上下文裏的當前元素做爲父級元素 //查找到當前元素名稱所對應的xml元素,並設置回到上下文中 Element pEle = c.getPreEle(); if(pEle==null){ //說明如今獲取的是根元素 c.setPreEle(c.getDocument().getDocumentElement()); }else{ //根據父級元素和要查找的元素的名稱來獲取當前的元素 Element nowEle = c.getNowEle(pEle, eleName); //把當前獲取的元素放到上下文裏面 c.setPreEle(nowEle); } //循環調用子元素的interpret方法 String [] ss = null; for(ReadXmlExpression ele : eles){ ss = ele.interpret(c); } return ss; } }
(4)定義元素做爲終結符對應的解釋器
對於單個元素的處理,終結符有兩種,一個是元素終結,一個是屬性終結。若是是元素終結,就是要獲取元素的值;若是是屬性終結,就是要獲取屬性的值。
分別來看看如何實現的,先看元素做爲終結的解釋器,示例代碼以下:
/** * 元素做爲終結符對應的解釋器 */ public class ElementTerminalExpression extends ReadXmlExpression{ /** * 元素的名字 */ private String eleName = ""; public ElementTerminalExpression(String name){ this.eleName = name; } public String[] interpret(Context c) { //先取出上下文裏的當前元素做爲父級元素 Element pEle = c.getPreEle(); //查找到當前元素名稱所對應的xml元素 Element ele = null; if(pEle==null){ //說明如今獲取的是根元素 ele = c.getDocument().getDocumentElement(); c.setPreEle(ele); }else{ //根據父級元素和要查找的元素的名稱來獲取當前的元素 ele = c.getNowEle(pEle, eleName); //把當前獲取的元素放到上下文裏面 c.setPreEle(ele); } //而後須要去獲取這個元素的值 String[] ss = new String[1]; ss[0] = ele.getFirstChild().getNodeValue(); return ss; } }
(5)定義屬性做爲終結符對應的解釋器
接下來看看屬性終結符的實現,就會比較簡單,直接獲取最後的元素對象,而後獲取相應的屬性的值,示例代碼以下:
/** * 屬性做爲終結符對應的解釋器 */ public class PropertyTerminalExpression extends ReadXmlExpression{ /** * 屬性的名字 */ private String propName; public PropertyTerminalExpression(String propName){ this.propName = propName; } public String[] interpret(Context c) { //直接獲取最後的元素的屬性的值 String[] ss = new String[1]; ss[0] = c.getPreEle().getAttribute(this.propName); return ss; } }
(6)使用解釋器
定義好了各個解釋器的實現,能夠寫個客戶端來測試一下這些解釋器對象的功能了。使用解釋器的客戶端的工做會比較多,最主要的就是要組裝抽象的語法樹。
先來看看如何使用解釋器獲取單個元素的值,示例代碼以下:
public class Client { public static void main(String[] args) throws Exception { //準備上下文 Context c = new Context("InterpreterTest.xml"); //想要獲取c元素的值,也就是以下表達式的值:"root/a/b/c" //首先要構建解釋器的抽象語法樹 ElementExpression root = new ElementExpression("root"); ElementExpression aEle = new ElementExpression("a"); ElementExpression bEle = new ElementExpression("b"); ElementTerminalExpression cEle = new ElementTerminalExpression("c"); //組合起來 root.addEle(aEle); aEle.addEle(bEle); bEle.addEle(cEle); //調用 String ss[] = root.interpret(c); System.out.println("c的值是="+ss[0]); } }
把前面定義的xml取名叫做「InterpreterTest.xml」,放到當前工程的根下面,運行看看,能正確獲取值嗎,運行的結果以下:
c的值是=12345
再來測試一下獲取單個元素的屬性的值,示例代碼以下:
public class Client { public static void main(String[] args) throws Exception { //準備上下文 Context c = new Context("InterpreterTest.xml"); //想要獲取c元素的name屬性,也就是以下表達式的值:"root/a/b/c.name" //這個時候c不是終結了,須要把c修改爲ElementExpressioin ElementExpression root = new ElementExpression("root"); ElementExpression aEle = new ElementExpression("a"); ElementExpression bEle = new ElementExpression("b"); ElementExpression cEle = new ElementExpression("c"); PropertyTerminalExpression prop = new PropertyTerminalExpression("name"); //組合 root.addEle(aEle); aEle.addEle(bEle); bEle.addEle(cEle); cEle.addEle(prop); //調用 String ss[] = root.interpret(c); System.out.println("c的屬性name的值是="+ss[0]); //若是要使用同一個上下文,連續進行解析,須要從新初始化上下文對象 //好比要連續的從新再獲取一次屬性name的值,固然你能夠從新組合元素, //從新解析,只要是在使用同一個上下文,就須要從新初始化上下文對象 c.reInit(); String ss2[] = root.interpret(c); System.out.println("從新獲取c的屬性name的值是="+ss2[0]); } }
運行的結果以下:
c的屬性name的值是=testC 從新獲取c的屬性name的值是=testC
就像前面講述的那樣,制定一種簡單的語言,讓客戶端用來表達從xml中取值的表達式的語言,而後爲它們定義一種文法的表示,也就是語法規則,而後用解釋器對象來表示那些表達式,接下來經過運行解釋器來解釋並執行這些功能。
可是從前面的示例中,咱們只能看到客戶端直接使用解釋器對象,來表示客戶要從xml中取什麼值的語法樹,而沒有看到如何從語言的表達式轉換成爲這種解釋器的表示,這個功能是屬於解析器的功能,沒有劃分在標準的解釋器模式中,因此這裏就先不演示,在後面會有示例來說解析器。
(1)解釋器模式的功能
解釋器模式使用解釋器對象來表示和處理相應的語法規則,通常一個解釋器處理一條語法規則。理論上來講,只要能用解釋器對象把符合語法的表達式表示出來,並且可以構成抽象的語法樹,那均可以使用解釋器模式來處理。
(2)語法規則和解釋器
語法規則和解釋器之間是有對應關係的,通常一個解釋器處理一條語法規則,可是反過來並不成立,一條語法規則是能夠有多種解釋和處理的,也就是一條語法規則能夠對應多個解釋器對象。
(3)上下文的公用性
上下文在解釋器模式中起到很是重要的做用,因爲上下文會被傳遞到全部的解釋器中,所以能夠在上下文中存儲和訪問解釋器的狀態,好比前面的解釋器能夠存儲一些數據在上下文中,後面的解釋器就能夠獲取這些值。
另外還能夠經過上下文傳遞一些在解釋器外部,可是解釋器須要的數據,也能夠是一些全局的,公共的數據。
上下文還有一個功能,就是能夠提供全部解釋器對象的公共功能,相似於對象組合,而不是使用繼承來獲取公共功能,在每一個解釋器對象裏面均可以調用。
(4)誰來構建抽象語法樹
在前面的示例中,你們已經發現,本身在客戶端手工來構建抽象語法樹,是很麻煩的,可是在解釋器模式中,並無涉及這部分功能,只是負責對構建好的抽象語法樹進行解釋處理。前面的測試簡單,因此手工構建抽象語法樹也不是特別困難的事,要是複雜了呢?若是仍是手工建立,那跟修改解析xml的代碼也差不了多少。後面會給你們講到,能夠提供解析器來實現把表達式轉換成爲抽象語法樹。
還有一個問題,就是一條語法規則是能夠對應多個解釋器對象的,也就是說同一個元素,是能夠轉換成多個解釋器對象的,這也就意味着一樣一個表達式,是能夠構成不一樣的抽象語法樹的,這也形成構建抽象語法樹變得很困難,並且工做量很大。
(5)誰負責解釋操做
只要定義好了抽象語法樹,確定是解釋器來負責解釋執行。雖然有不一樣的語法規則,可是解釋器不負責選擇究竟用哪個解釋器對象來解釋執行語法規則,選擇解釋器的功能在構建抽象語法樹的時候就完成了。
因此解釋器只要忠實的按照抽象語法樹解釋執行就行了。
(6)解釋器模式的調用順序示意圖
解釋器模式的調用順序如圖所示:
前面看過了如何獲取單個元素的值和單個元素的屬性的值,下面應該來看看如何獲取多個元素的值,還有多個元素中相同名稱的屬性的值了。
獲取多個值和前面獲取單個值的實現思路大體相同,只是在取值的時候須要循環整個NodelList,依次取值,而不是隻取出第一個來。固然,因爲語法發生了變更,因此對應的解釋器也須要發生改變。
首先是有了一個表示多個元素做爲終結符的語法,好比「root/a/b/d$」中的「d$」;其次有了一個表示多個元素的屬性做爲終結符的語法,好比「root/a/b/d$.id$」中的「.id$」;最後還有一個表示多個元素,但不是終結符的語法,好比「root/a/b/d$.id$」中的「d$」。
仍是看看代碼示例吧,會比較清楚。
(1)解釋器接口沒有變化,本來就定義的是數組,早作好準備了。
(2)讀取Xml的工具類XmlUtil也沒有任何變化
(3)上下文作了一點改變:
把原來用來記錄上一次操做的元素,變成記錄上一次操做的多個元素的這麼一個集合,而後爲它提供相應的getter/setter方法
另外,原來根據父元素和當前元素的名稱獲取當前元素的方法,變成了根據父元素和當前元素的名稱來獲取多個元素
從新初始化上下文的方法裏面,初始化的就是記錄上一次操做的多個元素的這個集合了
具體的Context類的代碼示例以下:
/** * 上下文,用來包含解釋器須要的一些全局信息 */ public class Context { /** * Dom解析Xml的Document對象 */ private Document document = null; /** * 上一次被處理的多個元素 */ private List<Element> preEles = new ArrayList<Element>(); /** * 構造方法 * @param filePathName 須要讀取的xml的路徑和名字 * @throws Exception */ public Context(String filePathName) throws Exception{ //經過輔助的Xml工具類來獲取被解析的xml對應的Document對象 this.document = XmlUtil.getRoot(filePathName); } /** * 從新初始化上下文 */ public void reInit(){ preEles = new ArrayList<Element>(); } /** * 各個Expression公共使用的方法, * 根據父元素和當前元素的名稱來獲取當前的多個元素的集合 * @param pEle 父元素 * @param eleName 當前元素的名稱 * @return 當前的多個元素的集合 */ public List<Element> getNowEles(Element pEle,String eleName){ List<Element> elements = new ArrayList<Element>(); NodeList tempNodeList = pEle.getChildNodes(); for(int i=0;i<tempNodeList.getLength();i++){ if(tempNodeList.item(i) instanceof Element){ Element nowEle = (Element)tempNodeList.item(i); if(nowEle.getTagName().equals(eleName)){ elements.add(nowEle); } } } return elements; } public Document getDocument() { return document; } public List<Element> getPreEles() { return preEles; } public void setPreEles(List<Element> nowEles) { this.preEles = nowEles; } }
(4)處理單個非終結符的對象ElementExpression,跟之前處理單個元素相比,主要是如今須要面向多個父元素,可是因爲是單個非終結符的處理,所以在多個父元素下面去查找符合要求的元素,找到一個就中止,示例代碼以下:
/** * 單個元素做爲非終結符的解釋器 */ public class ElementExpression extends ReadXmlExpression{ /** * 用來記錄組合的ReadXmlExpression元素 */ private Collection<ReadXmlExpression> eles = new ArrayList<ReadXmlExpression>(); /** * 元素的名稱 */ private String eleName = ""; public ElementExpression(String eleName){ this.eleName = eleName; } public boolean addEle(ReadXmlExpression ele){ this.eles.add(ele); return true; } public boolean removeEle(ReadXmlExpression ele){ this.eles.remove(ele); return true; } public String[] interpret(Context c) { //先取出上下文裏的父級元素 List<Element> pEles = c.getPreEles(); Element ele = null; //把當前獲取的元素放到上下文裏面 List<Element> nowEles = new ArrayList<Element>(); if(pEles.size()==0){ //說明如今獲取的是根元素 ele = c.getDocument().getDocumentElement(); pEles.add(ele); c.setPreEles(pEles); }else{ for(Element tempEle : pEles){ nowEles.addAll(c.getNowEles(tempEle, eleName)); if(nowEles.size()>0){ //找到一個就中止 break; } } List<Element> tempList = new ArrayList<Element>(); tempList.add(nowEles.get(0)); c.setPreEles(tempList); } //循環調用子元素的interpret方法 String [] ss = null; for(ReadXmlExpression tempEle : eles){ ss = tempEle.interpret(c); } return ss; } }
(5)用來處理單個元素做爲終結符的類,也發生了一點改變,主要是從多個父元素去獲取當前元素,若是當前元素是多個,就取第一個,示例代碼以下:
/** * 元素做爲終結符對應的解釋器 */ public class ElementTerminalExpression extends ReadXmlExpression{ /** * 元素的名字 */ private String eleName = ""; public ElementTerminalExpression(String name){ this.eleName = name; } public String[] interpret(Context c) { //先取出上下文裏的當前元素做爲父級元素 List<Element> pEles = c.getPreEles(); //查找到當前元素名稱所對應的xml元素 Element ele = null; if(pEles.size() == 0){ //說明如今獲取的是根元素 ele = c.getDocument().getDocumentElement(); }else{ //獲取當前的元素 ele = c.getNowEles(pEles.get(0), eleName).get(0); } //而後須要去獲取這個元素的值 String[] ss = new String[1]; ss[0] = ele.getFirstChild().getNodeValue(); return ss; } }
(6)新添加一個解釋器,用來解釋處理以多個元素的屬性做爲終結符的狀況,它的實現比較簡單,只要獲取到最後的多個元素對象,而後循環這些元素,一個一個取出相應的屬性值就行了,示例代碼以下:
/** * 以多個元素的屬性作爲終結符的解釋處理對象 */ public class PropertysTerminalExpression extends ReadXmlExpression{ /** * 屬性名字 */ private String propName; public PropertysTerminalExpression(String propName){ this.propName = propName; } public String[] interpret(Context c) { //獲取最後的多個元素 List<Element> eles = c.getPreEles(); String[] ss = new String[eles.size()]; //循環多個元素,獲取每一個的屬性的值 for(int i=0;i<ss.length;i++){ ss[i] = eles.get(i).getAttribute(this.propName); } return ss; } }
(7)新添加一個解釋器,用來解釋處理以多個元素做爲終結符的狀況,示例代碼以下:
/** * 以多個元素做爲終結符的解釋處理對象 */ public class ElementsTerminalExpression extends ReadXmlExpression{ /** * 元素的名稱 */ private String eleName = ""; public ElementsTerminalExpression(String name){ this.eleName = name; } public String[] interpret(Context c) { //先取出上下文裏的父級元素 List<Element> pEles = c.getPreEles(); //獲取當前的多個元素 List<Element> nowEles = new ArrayList<Element>(); for(Element ele : pEles){ nowEles.addAll(c.getNowEles(ele, eleName)); } //而後須要去獲取這些元素的值 String[] ss = new String[nowEles.size()]; for(int i=0;i<ss.length;i++){ ss[i] = nowEles.get(i).getFirstChild().getNodeValue(); } return ss; } }
(8)新添加一個解釋器,用來解釋處理以多個元素做爲非終結符的狀況,它的實現相似於以單個元素做爲非終結符的狀況,只是此次處理的是多個,須要循環處理,一樣須要維護子對象,在咱們如今設計的語法中,多個元素後面是能夠再加子元素的,最起碼能夠加多個屬性的終結符對象,示例代碼以下:
/** * 多個元素作爲非終結符的解釋處理對象 */ public class ElementsExpression extends ReadXmlExpression{ /** * 用來記錄組合的ReadXmlExpression元素 */ private Collection<ReadXmlExpression> eles = new ArrayList<ReadXmlExpression>(); /** * 元素名字 */ private String eleName = ""; public ElementsExpression(String eleName){ this.eleName = eleName; } public String[] interpret(Context c) { //先取出上下文裏的父級元素 List<Element> pEles = c.getPreEles(); //把當前獲取的元素放到上下文裏面,此次是獲取多個元素 List<Element> nowEles = new ArrayList<Element>(); for(Element ele : pEles){ nowEles.addAll(c.getNowEles(ele, eleName)); } c.setPreEles(nowEles); //循環調用子元素的interpret方法 String [] ss = null; for(ReadXmlExpression ele : eles){ ss = ele.interpret(c); } return ss; } public boolean addEle(ReadXmlExpression ele){ this.eles.add(ele); return true; } public boolean removeEle(ReadXmlExpression ele){ this.eles.remove(ele); return true; } }
(9)終於能夠寫客戶端來測試一下了,看看是否能實現指望的功能。先測試獲取多個元素的值的狀況,示例代碼以下:
public class Client { public static void main(String[] args) throws Exception { //準備上下文 Context c = new Context("InterpreterTest.xml"); //想要獲取多個d元素的值,也就是以下表達式的值:"root/a/b/d$" //首先要構建解釋器的抽象語法樹 ElementExpression root = new ElementExpression("root"); ElementExpression aEle = new ElementExpression("a"); ElementExpression bEle = new ElementExpression("b"); ElementsTerminalExpression dEle = new ElementsTerminalExpression("d"); //組合起來 root.addEle(aEle); aEle.addEle(bEle); bEle.addEle(dEle); //調用 String ss[] = root.interpret(c); for(String s : ss){ System.out.println("d的值是="+s); } } }
測試結果以下:
d的值是=d1 d的值是=d2 d的值是=d3 d的值是=d4
接下來測試一下獲取多個屬性值的狀況,示例代碼以下:
public class Client { public static void main(String[] args) throws Exception { //準備上下文 Context c = new Context("InterpreterTest.xml"); //想要獲取d元素的id屬性,也就是以下表達式的值:"a/b/d$.id$" //首先要構建解釋器的抽象語法樹 ElementExpression root = new ElementExpression("root"); ElementExpression aEle = new ElementExpression("a"); ElementExpression bEle = new ElementExpression("b"); ElementsExpression dEle = new ElementsExpression("d"); PropertysTerminalExpression prop = new PropertysTerminalExpression("id"); //組合 root.addEle(aEle); aEle.addEle(bEle); bEle.addEle(dEle); dEle.addEle(prop); //調用 String ss[] = root.interpret(c); for (String s : ss) { System.out.println("d的屬性id值是=" + s); } } }
測試結果以下:
d的屬性id值是=1 d的屬性id值是=2 d的屬性id值是=3 d的屬性id值是=4
也很簡單,是否是。只要學會了處理單個的值,處理多個值也就變得容易了,只要把原來獲取單個值的地方改爲循環操做便可。
固然,若是要使用同一個上下文,連續進行解析,是一樣須要從新初始化上下文對象的。你還能夠嘗試一下,若是是想要獲取多個元素下的,多個元素的同一個屬性的值,能實現嗎?你本身去測試,應該是能夠實現的。
前面看完了解釋器部分的功能,看到只要構建好了抽象語法樹,解釋器就可以正確地解釋並執行它,可是該如何獲得這個抽象語法樹呢?前面的測試都是人工組合好抽象語法樹的,若是實際開發中還這樣作,基本上工做量跟修改解析xml的代碼差很少。
這就須要解析器出場了,這個程序專門負責把按照語法表達的表達式,解析轉換成爲解釋器須要的抽象語法樹。固然解析器是跟表達式的語法,還有解釋器對象緊密關聯的。
下面來示例一下解析器的實現,把符合前面定義的語法的表達式,轉換成爲前面實現的解釋器的抽象語法樹。解析器有不少種實現方式,沒有什麼定式,只要能完成相應的功能便可,好比表驅動、語法分析生成程序等等。這裏的示例採用本身來分解表達式以實現構建抽象語法樹的功能,沒有使用遞歸,是用循環實現的,固然也能夠用遞歸來作。
(1)實現思路
要實現解析器也不復雜,大約有下面三個步驟:
第一步:把客戶端傳遞來的表達式進行分解,分解成爲一個一個的元素,並用一個對應的解析模型來封裝這個元素的一些信息。
第二步:根據每一個元素的信息,轉化成相對應的解析器對象
第三步:按照前後順序,把這些解析器對象組合起來,就獲得抽象語法樹了。
可能有朋友會說,爲何不把第一步和第二步合併,直接分解出一個元素就轉換成相應的解析器對象呢?緣由有兩個:
其一是功能分離,不要讓一個方法的功能過於複雜;
其二是爲了從此的修改和擴展,如今語法簡單,因此轉換成解析器對象須要考慮的東西少,直接轉換也不難,但要是語法複雜了,直接轉換就很雜亂了。
事實上,封裝解析屬性的數據模型充當了第一步和第二步操做間的接口,使第一步和第二步都變簡單了。
(2)先來看看用來封裝每個解析出來的元素對應的屬性對象,示例代碼以下:
/** * 用來封裝每個解析出來的元素對應的屬性 */ public class ParserModel { /** * 是否單個值 */ private boolean singleVlaue; /** * 是否屬性,不是屬性就是元素 */ private boolean propertyValue; /** * 是否終結符 */ private boolean end; public boolean isEnd() { return end; } public void setEnd(boolean end) { this.end = end; } public boolean isSingleVlaue() { return singleVlaue; } public void setSingleVlaue(boolean oneVlaue) { this.singleVlaue = oneVlaue; } public boolean isPropertyValue() { return propertyValue; } public void setPropertyValue(boolean propertyValue) { this.propertyValue = propertyValue; } }
(3)看看解析器的實現,代碼稍微複雜點,註釋很詳盡,爲了總體展現解析器,就不去分開每步單講了,不過要注意一點:下面這種實現沒有考慮併發處理的狀況,若是要用在多線程環境下,須要補充相應的處理,特別提示一下。示例代碼以下:
/** * 根據語法來解析表達式,轉換成爲相應的抽象語法樹 */ public class Parser { /** * 私有化構造器,避免外部無謂的建立對象實例 */ private Parser(){ // } //定義幾個常量,內部使用 private final static String BACKLASH = "/"; private final static String DOT = "."; private final static String DOLLAR = "$"; /** * 按照分解的前後記錄須要解析的元素的名稱 */ private static List<String> listEle = null; /** * 傳入一個字符串表達式,經過解析,組合成爲一個抽象的語法樹 * @param expr 描述要取值的字符串表達式 * @return 對應的抽象語法樹 */ public static ReadXmlExpression parse(String expr){ //先初始化記錄需解析的元素的名稱的集 會 listEle = new ArrayList<String>(); //第一步:分解表達式,獲得須要解析的元素名稱和該元素對應的解析模型 Map<String,ParserModel> mapPath = parseMapPath(expr); //第二步:根據節點的屬性轉換成爲相應的解釋器對象 List<ReadXmlExpression> list = mapPath2Interpreter( mapPath); //第三步:組合抽象語法樹,必定要按照前後順序來組合, //不然對象的包含關係就亂了 ReadXmlExpression returnRe = buildTree(list); return returnRe; } /*----------------------開始實現第一步-----------------------*/ /** * 按照從左到右順序來分解表達式,獲得須要解析的元素名稱, * 還有該元素對應的解析模型 * @param expr 須要分解的表達式 * @return 獲得須要解析的元素名稱,還有該元素對應的解析模型 */ private static Map<String,ParserModel> parseMapPath( String expr){ //先按照/分割字符串 StringTokenizer tokenizer = new StringTokenizer( expr, BACKLASH); //初始化一個map用來存放分解出來的值 Map<String,ParserModel> mapPath = new HashMap<String,ParserModel>(); while (tokenizer.hasMoreTokens()) { String onePath = tokenizer.nextToken(); if (tokenizer.hasMoreTokens()) { //還有下一個值,說明這不是最後一個元素 //按照如今的語法,屬性必然在最後,所以也不是屬性 setParsePath(false,onePath,false,mapPath); } else { //說明到最後了 int dotIndex = onePath.indexOf(DOT); if (dotIndex > 0) { //說明是要獲取屬性的值,那就按照"."來分割, //前面的就是元素名字,後面的是屬性的名字 String eleName = onePath.substring(0, dotIndex); String propName = onePath.substring(dotIndex + 1); //設置屬性前面的那個元素,天然不是最後一個,也不是屬性 setParsePath(false,eleName,false,mapPath); //設置屬性,按照如今的語法定義,屬性只能是最後一個 setParsePath(true,propName,true,mapPath); } else { //說明是取元素的值,並且是最後一個元素的值 setParsePath(true,onePath,false,mapPath); } break; } } return mapPath; } /** * 按照分解出來的位置和名稱來設置須要解析的元素名稱, * 還有該元素對應的解析模型 * @param end 是不是最後一個 * @param ele 元素名稱 * @param propertyValue 是不是取屬性 * @param mapPath 設置須要解析的元素名稱,還有該元素對應的解析模型的Map */ private static void setParsePath(boolean end,String ele ,boolean propertyValue,Map<String,ParserModel> mapPath){ ParserModel pm = new ParserModel(); pm.setEnd(end); //若是帶有$符號就說明不是一個值 pm.setSingleVlaue(!(ele.indexOf(DOLLAR)>0)); pm.setPropertyValue(propertyValue); //去掉$ ele = ele.replace(DOLLAR, ""); mapPath.put(ele,pm); listEle.add(ele); } /*----------------------第一步實現結束-----------------------*/ /*----------------------開始實現第二步-----------------------*/ /** * 把分解出來的元素名稱,根據對應的解析模型轉換成爲相應的解釋器對象 * @param mapPath 分解出來的需解析的元素名稱,還有該元素對應的解析模型 * @return 把每一個元素轉換成爲相應的解釋器對象後的集合 */ private static List<ReadXmlExpression> mapPath2Interpreter( Map<String,ParserModel> mapPath){ List<ReadXmlExpression> list = new ArrayList<ReadXmlExpression>(); //必定要按照分解的前後順序來轉換成解釋器對象 for(String key : listEle){ ParserModel pm = mapPath.get(key); ReadXmlExpression obj = null; if(!pm.isEnd()){ if(pm.isSingleVlaue()){ //不是最後一個,是一個值,轉化爲 obj = new ElementExpression(key); }else{ //不是最後一個,是多個值,轉化爲 obj = new ElementsExpression(key); } }else{ if(pm.isPropertyValue()){ if(pm.isSingleVlaue()){ //是最後一個,是一個值,取屬性的值,轉化爲 obj = new PropertyTerminalExpression(key); }else{ //是最後一個,是多個值,取屬性的值,轉化爲 obj = new PropertysTerminalExpression(key); } }else{ if(pm.isSingleVlaue()){ //是最後一個,是一個值,取元素的值,轉化爲 obj = new ElementTerminalExpression(key); }else{ //是最後一個,是多個值,取元素的值,轉化爲 obj = new ElementsTerminalExpression(key); } } } //把轉換後的對象添加到集合中 list.add(obj); } return list; } /*----------------------第二步實現結束-----------------------*/ /*----------------------開始實現第三步-----------------------*/ private static ReadXmlExpression buildTree( List<ReadXmlExpression> list){ //第一個對象,也是返回去的對象,就是抽象語法樹的根 ReadXmlExpression returnRe = null; //定義上一個對象 ReadXmlExpression preRe = null; for(ReadXmlExpression re : list){ if(preRe==null){ //說明是第一個元素 preRe = re; returnRe = re; }else{ //把元素添加到上一個對象下面,同時把本對象設置成爲oldRe, //做爲下一個對象的父結點 if(preRe instanceof ElementExpression){ ElementExpression ele = (ElementExpression)preRe; ele.addEle(re); preRe = re; }else if(preRe instanceof ElementsExpression){ ElementsExpression eles = (ElementsExpression)preRe; eles.addEle(re); preRe = re; } } } return returnRe; } /*----------------------第三步實現結束-----------------------*/ }
(4)看完這個稍長點的解析器程序,該來體會一下,有了它對咱們的開發有什麼好處,寫個客戶端來測試看看。如今的客戶端就很是簡單了,主要三步:
首先是設計好想要取值的表達式
而後是經過解析器解析獲取抽象語法樹
最後就是請求解釋器解釋並執行這個抽象語法樹,就獲得最後的結果了
客戶端測試的示例代碼以下:
public class Client { public static void main(String[] args) throws Exception { //準備上下文 Context c = new Context("InterpreterTest.xml"); //經過解析器獲取抽象語法樹 ReadXmlExpression re = Parser.parse("root/a/b/d$.id$"); //請求解析,獲取返回值 String ss[] = re.interpret(c); for (String s : ss) { System.out.println("d的屬性id值是=" + s); } //若是要使用同一個上下文,連續進行解析,須要從新初始化上下文對象 c.reInit(); ReadXmlExpression re2 = Parser.parse("root/a/b/d$"); //請求解析,獲取返回值 String ss2[] = re2.interpret(c); for (String s : ss2) { System.out.println("d的值是=" + s); } } }
簡單多了吧!經過使用解釋器模式,自行設計一種簡單的語法,就能夠用很簡單的表達式來獲取你想要的xml中的值了。有的朋友可能會想到XPath,沒錯,本章示例實現的功能就是相似於XPath的部分功能。
若是從此xml的結構要是發生了變化,或者是想要獲取不一樣的值,基本上就是修改那個表達式而已,你能夠試試看,可否完成前面實現過的功能。好比:
想要獲取c元素的值,表達式爲:「root/a/b/c」
想要獲取c元素的name屬性值,表達式爲:「root/a/b/c.name」
想要獲取d元素的值,表達式爲:「root/a/b/d$」,獲取d的屬性上面已經測試了
l 易於實現語法
在解釋器模式中,一條語法規則用一個解釋器對象來解釋執行,對於解釋器的實現來說,功能就變得比較簡單,只須要考慮這一條語法規則的實現就行了,其它的都不用管。
l 易於擴展新的語法
正是因爲採用一個解釋器對象負責一條語法規則的方式,使得擴展新的語法很是容易,擴展了新的語法,只須要建立相應的解釋器對象,在建立抽象語法樹的時候使用這個新的解釋器對象就能夠了。
l 不適合複雜的語法
若是語法特別複雜,構建解釋器模式須要的抽象語法樹的工做是很是艱鉅的,再加上有可能會須要構建多個抽象語法樹。因此解釋器模式不太適合於複雜的語法,對於複雜的語法,使用語法分析程序或編譯器生成器可能會更好。
1:解釋器模式的本質
解釋器模式的本質:分離實現,解釋執行。
解釋器模式經過一個解釋器對象處理一個語法規則的方式,把複雜的功能分離開;而後選擇須要被執行的功能,並把這些功能組合成爲須要被解釋執行的抽象語法樹;而後再按照抽象語法樹來解釋執行,實現相應的功能。
認識這個本質對於識別和變形使用解釋器模式是頗有做用的。從表面上看,解釋器模式是關注的咱們平時不太用到的自定義語法的處理,可是從實質上看,解釋器模式的思路仍然是分離、封裝、簡化,跟不少模式是同樣的。
好比可使用解釋器模式模擬狀態模式的功能。若是把解釋器模式要處理的語法簡化到只有一個狀態標記,把解釋器當作是對狀態的處理對象,對同一個表示狀態的語法,能夠有不少不一樣的解釋器,也就是有不少不一樣的處理狀態的對象,而後在建立抽象語法樹的時候,簡化成根據狀態的標記來建立相應的解釋器,不用再構建樹了。你看看這麼簡化下來,是否是能夠用解釋器模擬出狀態模式的功能呢?
同理,解釋器模式能夠模擬實現策略模式的功能,裝飾器模式的功能等等,尤爲是模擬裝飾器模式的功能,構建抽象語法樹的過程,天然就對應成爲組合裝飾器的過程。
2:什麼時候選用解釋器模式
建議在以下狀況中,選用解釋器模式:
當有一個語言須要解釋執行,而且能夠將該語言中的句子表示爲一個抽象語法樹的時候,能夠考慮使用解釋器模式。
在使用解釋器模式的時候,還有兩個特色須要考慮,一個是語法相對應該比較簡單,太複雜的語法不合適使用解釋器模式;另外一個是效率要求不是很高,對效率要求很高的狀況下,不適合使用解釋器模式。
l 解釋器模式和組合模式
這兩個模式能夠組合使用。
一般解釋器模式都會使用組合模式來實現,這樣可以方便的構建抽象語法樹。通常非終結符解釋器就至關於組合模式中的組合對象,終結符解釋器就至關於葉子對象。
l 解釋器模式和迭代器模式
這兩個模式能夠組合使用。
因爲解釋器模式一般使用組合模式來實現,所以在遍歷整個對象結構的時候,天然可使用迭代器模式。
l 解釋器模式和享元模式
這兩個模式能夠組合使用。
在使用解釋器模式的時候,可能會形成多個細粒度對象,好比會有各類各樣的終結符解釋器,而這些終結符解釋器對不一樣的表達式來講是同樣的,是能夠共用的,所以能夠引入享元模式來共享這些對象。
l 解釋器模式和訪問者模式
這兩個模式能夠組合使用。
在解釋器模式中,語法規則和解釋器對象是有對應關係的。語法規則的變更意味着功能的變化,天然會致使使用不一樣的解釋器對象;並且一個語法規則能夠被不一樣的解釋器解釋執行。
所以在構建抽象語法樹的時候,若是每一個節點所對應的解釋器對象是固定的,這就意味着這個節點對應的功能是固定的,那麼就不得不根據須要來構建不一樣的抽象語法樹。
爲了讓構建的抽象語法樹較爲通用,那就要求解釋器的功能不要那麼固定,要能很方便的改變解釋器的功能,這個時候問題就變成了,如何可以很方便的更改樹形結構中節點對象的功能了,訪問者模式能夠很好的實現這個功能。
轉載至:http://sishuok.com/forum/blogPost/list/5667.html cc老師的設計模式是我目前看過最詳細最有實踐的教程。