摘要:解決方案解釋器模式來解決用來解決上述問題的一個合理的解決方案,就是使用解釋器模式。使用解釋器模式重寫示例通過上面的講述可以看出,要使用解釋器模式,一個重要的前提就是要定義一套語法規則,也稱為文法。
#1 場景問題# ##1.1 讀取配置文件## 考慮這樣一個實際的應用,維護系統自定義的配置文件。
幾乎每個實際的應用系統都有與應用自身相關的配置文件,這個配置文件是由開發人員根據需要自定義的,系統運行時會根據配置的數據進行相應的功能處理。
系統現有的配置數據很簡單,主要是JDBC所需要的數據,還有默認讀取Spring的配置文件,目前系統只需要一個Spring的配置文件。示例如下:
<);"1.0" encoding="UTF-8");
現在的功能需求是:如何能夠靈活的讀取配置文件的內容?
##1.2 不用模式的解決方案## 不就是讀取配置文件嗎?實現很簡單,直接讀取并解析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);
}
}
##1.3 有何問題## 看了上面的實現,多簡單啊,就是最基本的Dom解析嘛,要是采用其它的開源工具包,比如dom4j、jDom之類的來處理,會更簡單,這好像不值得一提呀,真的是這樣嗎?
請思考一個問題:如果配置文件的結構需要變動呢?仔細想想,就會感覺出問題來了。還是先看例子,然后再來總結這個問題。
隨著開發的深入進行,越來越多可配置的數據被抽取出來,需要添加到配置文件中,比如與數據庫的連接配置:就加入了是否需要、是否使用DataSource等配置。除了這些還加入了一些其它需要配置的數據,例如:系統管理員、日志記錄方式、緩存線程的間隔時長、默認讀取哪些Spring配置文件等等,示例如下:
<);"1.0" encoding="UTF-8");log>
記錄日志的方式,1-數據庫,2-文件
記錄日志的文件名稱
log>
緩存線程的間隔時長
缺省讀取的Spring配置的文件名稱
其它需要讀取的Spring配置的文件名稱
有朋友可能會想,改變一下配置文件,值得大驚小怪嗎?對于應用系統開發來講,這不是經常發生的、很普通的一件事情嘛。
的確是這樣,改變一下配置文件不是件大事情,但是帶來的一系列麻煩也不容忽視,比如:修改了配置文件的結構,那么讀取配置文件的程序就需要做出相應的變更;用來封裝配置文件數據的數據對象也需要相應的修改;外部使用配置文件的地方,獲取數據的地方也會相應變動。
當然在這一系列麻煩中,最讓人痛苦的莫過于修改讀取配置文件的程序了,有時候幾乎是重寫。比如在使用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"applicationXml=="+applicationXml);
}
仔細對比上面在xml變化前后讀取值的代碼,你會發現,由于xml結構的變化,導致讀取xml文件內容的代碼,基本上完全重寫了。
問題還不僅僅限于讀取元素的值,同樣體現在讀取屬性上。可能有些朋友說可以換不同的xml解析方式來簡化,不是還有Sax解析,實在不行換用其它開源的解決方案。
確實通過使用不同的解析xml的方式是會讓程序變得簡單點,但是每次xml的結構發生變化過后,或多或少都是需要修改程序中解析xml部分的。
有沒有辦法解決這個問題呢?也就是當xml的結構發生改變過后,能夠很方便的獲取相應元素、或者是屬性的值,而不用再去修改解析xml的程序。
#2 解決方案# ##2.1 解釋器模式來解決## 用來解決上述問題的一個合理的解決方案,就是使用解釋器模式。那么什么是解釋器模式呢?
解釋器模式定義
這里的文法,簡單點說就是我們俗稱的“語法規則”。
應用解釋器模式來解決的思路
要想解決當xml的結構發生改變后,不用修改解析部分的代碼,一個自然的思路就是要把解析部分的代碼寫成公共的,而且還要是通用的,能夠滿足各種xml取值的需要,比如:獲取單個元素的值,獲取多個相同名稱的元素的值,獲取單個元素的屬性的值,獲取多個相同名稱的元素的屬性的值,等等。
要寫成通用的代碼,又有幾個問題要解決,如何組織這些通用的代碼?如何調用這些通用的代碼?以何種方式來告訴這些通用代碼,客戶端的需要?
要解決這些問題,其中的一個解決方案就是解釋器模式。在描述這個模式的解決思路之前,先解釋兩個概念,一個是解析器(不是指xml的解析器),一個是解釋器。
這里的解析器,指的是把描述客戶端調用要求的表達式,經過解析,形成一個抽象語法樹的程序,不是指xml的解析器。
這里的解釋器,指的是解釋抽象語法樹,并執行每個節點對應的功能的程序。
要解決通用解析xml的問題:
第一步:需要先設計一個簡單的表達式語言,在客戶端調用解析程序的時候,傳入用這個表達式語言描述的一個表達式,然后把這個表達式通過解析器的解析,形成一個抽象的語法樹。
第二步:解析完成后,自動調用解釋器來解釋抽象語法樹,并執行每個節點所對應的功能,從而完成通用的xml解析。
這樣一來,每次當xml結構發生了更改,也就是在客戶端調用的時候,傳入不同的表達式即可,整個解析xml過程的代碼都不需要再修改了。
##2.2 模式結構和說明## 解釋器模式的結構如圖21.1所示:
AbstractExpression:定義解釋器的接口,約定解釋器的解釋操作。
TerminalExpression:終結符解釋器,用來實現語法規則中和終結符相關的操作,不再包含其它的解釋器,如果用組合模式來構建抽象語法樹的話,就相當于組合模式中的葉子對象,可以有多種終結符解釋器。
NonterminalExpression:非終結符解釋器,用來實現語法規則中非終結符相關的操作,通常一個解釋器對應一個語法規則,可以包含其它的解釋器,如果用組合模式來構建抽象語法樹的話,就相當于組合模式中的組合對象,可以有多種非終結符解釋器。
Context:上下文,通常包含各個解釋器需要的數據,或是公共的功能。
Client:客戶端,指的是使用解釋器的客戶端,通常在這里去把按照語言的語法做的表達式,轉換成為使用解釋器對象描述的抽象語法樹,然后調用解釋操作。
##2.3 解釋器模式示例代碼##
先看看抽象表達式的定義,非常簡單,定義一個執行解釋的方法,示例代碼如下:
/** * 抽象表達式 */ public abstract class AbstractExpression { /** * 解釋的操作 * @param ctx 上下文對象 */ public abstract void interpret(Context ctx); }
再來看看終結符表達式的定義,示例代碼如下:
/** * 終結符表達式 */ public class TerminalExpression extends AbstractExpression{ public void interpret(Context ctx) { //實現與語法規則中的終結符相關聯的解釋操作 } }
接下來該看看非終結符表達式的定義了,示例代碼如下:
/** * 非終結符表達式 */ public class NonterminalExpression extends AbstractExpression{ public void interpret(Context ctx) { //實現與語法規則中的非終結符相關聯的解釋操作 } }
上下文的定義,示例代碼如下:
/** * 上下文,包含解釋器之外的一些全局信息 */ public class Context { }
最后來看看客戶端的定義,示例代碼如下:
/** * 使用解釋器的客戶 */ public class Client { //主要按照語法規則對特定的句子構建抽象語法樹 //然后調用解釋操作 }
看到這里,可能有些朋友會覺得,上面的示例代碼里面什么都沒有啊。這主要是因為解釋器模式是跟具體的語法規則聯系在一起的,沒有相應的語法規則,自然寫不出對應的處理代碼來。
但是這些示例還是有意義的,可以通過它們看出解釋器模式實現的基本架子,只是沒有內部具體的處理罷了。
##2.4 使用解釋器模式重寫示例## 通過上面的講述可以看出,要使用解釋器模式,一個重要的前提就是要定義一套語法規則,也稱為文法。不管這套文法的規則是簡單還是復雜,必須有這么個東西,因為解釋器模式就是來按照這些規則進行解析并執行相應的功能的。
為表達式設計簡單的文法
為了通用,用root表示根元素,a、b、c、d等來代表元素,一個簡單的xml如下:
<);"1.0" encoding="UTF-8");"rootId">
"testC">12345
"1">d1
"2">d2
"3">d3
"4">d4
約定表達式的文法如下:
**獲取單個元素的值:**從根元素開始,一直到想要獲取值的元素,元素中間用“/”分隔,根元素前不加“/”。比如表達式“root/a/b/c”就表示獲取根元素下、a元素下、b元素下的c元素的值;
**獲取單個元素的屬性的值:**要獲取值的屬性一定是表達式的最后一個元素的屬性,在最后一個元素后面添加“.”然后再加上屬性的名稱。比如表達式“root/a/b/c.name”就表示獲取根元素下、a元素下、b元素下、c元素的name屬性的值;
**獲取相同元素名稱的值,當然是多個:**要獲取值的元素一定是表達式的最后一個元素,在最后一個元素后面添加“
從簡單的開始,先來演示獲取單個元素的值和單個元素的屬性的值。在看具體代碼前,先來看看此時系統的整體結構,如圖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;iif(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 eles = new ArrayList();
/**
* 元素的名稱
*/
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中取什么值的語法樹,而沒有看到如何從語言的表達式轉換成為這種解釋器的表示,這個功能是屬于解析器的功能,沒有劃分在標準的解釋器模式中,所以這里就先不演示,在后面會有示例來講解析器。
#3 模式講解# ##3.1 認識解釋器模式##
解釋器模式的功能 解釋器模式使用解釋器對象來表示和處理相應的語法規則,一般一個解釋器處理一條語法規則。理論上來說,只要能用解釋器對象把符合語法的表達式表示出來,而且能夠構成抽象的語法樹,那都可以使用解釋器模式來處理。
語法規則和解釋器 語法規則和解釋器之間是有對應關系的,一般一個解釋器處理一條語法規則,但是反過來并不成立,一條語法規則是可以有多種解釋和處理的,也就是一條語法規則可以對應多個解釋器對象。
上下文的公用性 上下文在解釋器模式中起到非常重要的作用,由于上下文會被傳遞到所有的解釋器中,因此可以在上下文中存儲和訪問解釋器的狀態,比如前面的解釋器可以存儲一些數據在上下文中,后面的解釋器就可以獲取這些值。
另外還可以通過上下文傳遞一些在解釋器外部,但是解釋器需要的數據,也可以是一些全局的,公共的數據。
上下文還有一個功能,就是可以提供所有解釋器對象的公共功能,類似于對象組合,而不是使用繼承來獲取公共功能,在每個解釋器對象里面都可以調用。
誰來構建抽象語法樹 在前面的示例中,大家已經發現,自己在客戶端手工來構建抽象語法樹,是很麻煩的,但是在解釋器模式中,并沒有涉及這部分功能,只是負責對構建好的抽象語法樹進行解釋處理。前面的測試簡單,所以手工構建抽象語法樹也不是特別困難的事,要是復雜了呢?如果還是手工創建,那跟修改解析xml的代碼也差不了多少。后面會給大家講到,可以提供解析器來實現把表達式轉換成為抽象語法樹。
還有一個問題,就是一條語法規則是可以對應多個解釋器對象的,也就是說同一個元素,是可以轉換成多個解釋器對象的,這也就意味著同樣一個表達式,是可以構成不同的抽象語法樹的,這也造成構建抽象語法樹變得很困難,而且工作量很大。
誰負責解釋操作 只要定義好了抽象語法樹,肯定是解釋器來負責解釋執行。雖然有不同的語法規則,但是解釋器不負責選擇究竟用哪一個解釋器對象來解釋執行語法規則,選擇解釋器的功能在構建抽象語法樹的時候就完成了。
所以解釋器只要忠實的按照抽象語法樹解釋執行就好了。
解釋器模式的調用順序示意圖
解釋器模式的調用順序如圖21.4所示:
##3.2 讀取多個元素或屬性的值## 前面看過了如何獲取單個元素的值和單個元素的屬性的值,下面應該來看看如何獲取多個元素的值,還有多個元素中相同名稱的屬性的值了。
獲取多個值和前面獲取單個值的實現思路大致相同,只是在取值的時候需要循環整個NodelList,依次取值,而不是只取出第一個來。當然,由于語法發生了變動,所以對應的解釋器也需要發生改變。
首先是有了一個表示多個元素作為終結符的語法,比如“root/a/b/dreInit(){
preEles = new ArrayList 處理單個非終結符的對象ElementExpression,跟以前處理單個元素相比,主要是現在需要面向多個父元素,但是由于是單個非終結符的處理,因此在多個父元素下面去查找符合要求的元素,找到一個就停止,示例代碼如下: 用來處理單個元素作為終結符的類,也發生了一點改變,主要是從多個父元素去獲取當前元素,如果當前元素是多個,就取第一個,示例代碼如下: 新添加一個解釋器,用來解釋處理以多個元素的屬性作為終結符的情況,它的實現比較簡單,只要獲取到最后的多個元素對象,然后循環這些元素,一個一個取出相應的屬性值就好了,示例代碼如下: 新添加一個解釋器,用來解釋處理以多個元素作為終結符的情況,示例代碼如下: 新添加一個解釋器,用來解釋處理以多個元素作為非終結符的情況,它的實現類似于以單個元素作為非終結符的情況,只是這次處理的是多個,需要循環處理,同樣需要維護子對象,在我們現在設計的語法中,多個元素后面是可以再加子元素的,最起碼可以加多個屬性的終結符對象,示例代碼如下: 終于可以寫客戶端來測試一下了,看看是否能實現期望的功能。先測試獲取多個元素的值的情況,示例代碼如下: 運行結果如下: 接下來測試一下獲取多個屬性值的情況,示例代碼如下: 運行結果如下: 也很簡單,是不是。只要學會了處理單個的值,處理多個值也就變得容易了,只要把原來獲取單個值的地方改成循環操作即可。 當然,如果要使用同一個上下文,連續進行解析,是同樣需要重新初始化上下文對象的。你還可以嘗試一下,如果是想要獲取多個元素下的,多個元素的同一個屬性的值,能實現嗎?你自己去測試,應該是可以實現的。 ##3.3 解析器## 前面看完了解釋器部分的功能,看到只要構建好了抽象語法樹,解釋器就能夠正確地解釋并執行它,但是該如何得到這個抽象語法樹呢?前面的測試都是人工組合好抽象語法樹的,如果實際開發中還這樣做,基本上工作量跟修改解析xml的代碼差不多。 這就需要解析器出場了,這個程序專門負責把按照語法表達的表達式,解析轉換成為解釋器需要的抽象語法樹。當然解析器是跟表達式的語法,還有解釋器對象緊密關聯的。 下面來示例一下解析器的實現,把符合前面定義的語法的表達式,轉換成為前面實現的解釋器的抽象語法樹。解析器有很多種實現方式,沒有什么定式,只要能完成相應的功能即可,比如表驅動、語法分析生成程序等等。這里的示例采用自己來分解表達式以實現構建抽象語法樹的功能,沒有使用遞歸,是用循環實現的,當然也可以用遞歸來做。 實現思路 要實現解析器也不復雜,大約有下面三個步驟: 第一步:把客戶端傳遞來的表達式進行分解,分解成為一個一個的元素,并用一個對應的解析模型來封裝這個元素的一些信息; 第二步:根據每個元素的信息,轉化成相對應的解析器對象; 第三步:按照先后順序,把這些解析器對象組合起來,就得到抽象語法樹了; 可能有朋友會說,為什么不把第一步和第二步合并,直接分解出一個元素就轉換成相應的解析器對象呢?原因有兩個: 其一是功能分離,不要讓一個方法的功能過于復雜; 其二是為了今后的修改和擴展,現在語法簡單,所以轉換成解析器對象需要考慮的東西少,直接轉換也不難,但要是語法復雜了,直接轉換就很雜亂了; 事實上,封裝解析屬性的數據模型充當了第一步和第二步操作間的接口,使第一步和第二步都變簡單了。 先來看看用來封裝每一個解析出來的元素對應的屬性對象,示例代碼如下: 看看解析器的實現,代碼稍微復雜點,注釋很詳盡,為了整體展示解析器,就不去分開每步單講了,不過要注意一點:下面這種實現沒有考慮并發處理的情況,如果要用在多線程環境下,需要補充相應的處理,特別提示一下。示例代碼如下: 看完這個稍長點的解析器程序,該來體會一下,有了它對我們的開發有什么好處,寫個客戶端來測試看看。現在的客戶端就非常簡單了,主要三步:
首先是設計好想要取值的表達式; 然后是通過解析器解析獲取抽象語法樹; 最后就是請求解釋器解釋并執行這個抽象語法樹,就得到最后的結果了; 客戶端測試的示例代碼如下: 簡單多了吧!通過使用解釋器模式,自行設計一種簡單的語法,就可以用很簡單的表達式來獲取你想要的xml中的值了。有的朋友可能會想到XPath,沒錯,本章示例實現的功能就是類似于XPath的部分功能。 如果今后xml的結構要是發生了變化,或者是想要獲取不同的值,基本上就是修改那個表達式而已,你可以試試看,能否完成前面實現過的功能。比如: 想要獲取c元素的值,表達式為:“root/a/b/c”; 想要獲取c元素的name屬性值,表達式為:“root/a/b/c.name”; 想要獲取d元素的值,表達式為:“root/a/b/d$”,獲取d的屬性上面已經測試了; ##3.4 解釋器模式的優缺點## 易于實現語法
在解釋器模式中,一條語法規則用一個解釋器對象來解釋執行,對于解釋器的實現來講,功能就變得比較簡單,只需要考慮這一條語法規則的實現就好了,其它的都不用管。 易于擴展新的語法
正是由于采用一個解釋器對象負責一條語法規則的方式,使得擴展新的語法非常容易,擴展了新的語法,只需要創建相應的解釋器對象,在創建抽象語法樹的時候使用這個新的解釋器對象就可以了。 不適合復雜的語法
如果語法特別復雜,構建解釋器模式需要的抽象語法樹的工作是非常艱巨的,再加上有可能會需要構建多個抽象語法樹。所以解釋器模式不太適合于復雜的語法,對于復雜的語法,使用語法分析程序或編譯器生成器可能會更好。 ##3.5 思考解釋器模式## 解釋器模式的本質
解釋器模式的本質:分離實現,解釋執行。 解釋器模式通過一個解釋器對象處理一個語法規則的方式,把復雜的功能分離開;然后選擇需要
/**
* 單個元素作為非終結符的解釋器
*/
public class ElementExpression extends ReadXmlExpression{
/**
* 用來記錄組合的ReadXmlExpression元素
*/
private Collection
/**
* 元素作為終結符對應的解釋器
*/
public class ElementTerminalExpression extends ReadXmlExpression{
/**
* 元素的名字
*/
private String eleName = "";
public ElementTerminalExpression(String name){
this.eleName = name;
}
public String[] interpret(Context c) {
//先取出上下文里的當前元素作為父級元素
List
/**
* 以多個元素的屬性做為終結符的解釋處理對象
*/
public class PropertysTerminalExpression extends ReadXmlExpression{
/**
* 屬性名字
*/
private String propName;
public PropertysTerminalExpression(String propName){
this.propName = propName;
}
public String[] interpret(Context c) {
//獲取最后的多個元素
List
/**
* 以多個元素作為終結符的解釋處理對象
*/
public class ElementsTerminalExpression extends ReadXmlExpression{
/**
* 元素的名稱
*/
private String eleName = "";
public ElementsTerminalExpression(String name){
this.eleName = name;
}
public String[] interpret(Context c) {
//先取出上下文里的父級元素
List
/**
* 多個元素做為非終結符的解釋處理對象
*/
public class ElementsExpression extends ReadXmlExpression{
/**
* 用來記錄組合的ReadXmlExpression元素
*/
private Collection
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
/**
* 用來封裝每一個解析出來的元素對應的屬性
*/
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;
}
}
/**
* 根據語法來解析表達式,轉換成為相應的抽象語法樹
*/
public class Parser {
/**
* 私有化構造器,避免外部無謂的創建對象實例
*/
private Parser(){
//
}
//定義幾個常量,內部使用
private final static String BACKLASH = "/";
private final static String DOT = ".";
private final static String DOLLAR = "$";
/**
* 按照分解的先后記錄需要解析的元素的名稱
*/
private static List
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);
}
}
}
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7118.html
摘要:解決方案解釋器模式來解決用來解決上述問題的一個合理的解決方案,就是使用解釋器模式。使用解釋器模式重寫示例通過上面的講述可以看出,要使用解釋器模式,一個重要的前提就是要定義一套語法規則,也稱為文法。#1 場景問題# ##1.1 讀取配置文件## 考慮這樣一個實際的應用,維護系統自定義的配置文件。 幾乎每個實際的應用系統都有與應用自身相關的配置文件,這個配置文件是由開發人員根據需要自定義的,系統...
摘要:解決方案解釋器模式來解決用來解決上述問題的一個合理的解決方案,就是使用解釋器模式。使用解釋器模式重寫示例通過上面的講述可以看出,要使用解釋器模式,一個重要的前提就是要定義一套語法規則,也稱為文法。#1 場景問題# ##1.1 讀取配置文件## 考慮這樣一個實際的應用,維護系統自定義的配置文件。 幾乎每個實際的應用系統都有與應用自身相關的配置文件,這個配置文件是由開發人員根據需要自定義的,系統...
摘要:遠程調用協議目標介紹遠程調用中跟協議相關的設計和實現,介紹的源碼。二該類繼承了,是協議中獨有的服務暴露者。八該類也是對的裝飾,其中增強了調用次數多功能。 遠程調用——dubbo協議 目標:介紹遠程調用中跟dubbo協議相關的設計和實現,介紹dubbo-rpc-dubbo的源碼。 前言 Dubbo 缺省協議采用單一長連接和 NIO 異步通訊,適合于小數據量大并發的服務調用,以及服務消費者...
摘要:十開放模式識別項目開放模式識別項目,致力于開發出一套包含圖像處理計算機視覺自然語言處理模式識別機器學習和相關領域算法的函數庫。 一、開源生物特征識別庫 OpenBROpenBR 是一個用來從照片中識別人臉的工具。還支持推算性別與年齡。使用方法:$ br -algorithm FaceRecognition -compare me.jpg you.jpg二、計算機視覺庫 OpenCVOpenC...
閱讀 713·2023-04-25 19:43
閱讀 3910·2021-11-30 14:52
閱讀 3784·2021-11-30 14:52
閱讀 3852·2021-11-29 11:00
閱讀 3783·2021-11-29 11:00
閱讀 3869·2021-11-29 11:00
閱讀 3557·2021-11-29 11:00
閱讀 6105·2021-11-29 11:00