成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

設(shè)計(jì)模式(二十四)解釋器模式

inapt / 3754人閱讀

摘要:解決方案解釋器模式來(lái)解決用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案,就是使用解釋器模式。使用解釋器模式重寫(xiě)示例通過(guò)上面的講述可以看出,要使用解釋器模式,一個(gè)重要的前提就是要定義一套語(yǔ)法規(guī)則,也稱為文法。

#1 場(chǎng)景問(wèn)題# ##1.1 讀取配置文件## 考慮這樣一個(gè)實(shí)際的應(yīng)用,維護(hù)系統(tǒng)自定義的配置文件。

幾乎每個(gè)實(shí)際的應(yīng)用系統(tǒng)都有與應(yīng)用自身相關(guān)的配置文件,這個(gè)配置文件是由開(kāi)發(fā)人員根據(jù)需要自定義的,系統(tǒng)運(yùn)行時(shí)會(huì)根據(jù)配置的數(shù)據(jù)進(jìn)行相應(yīng)的功能處理。

系統(tǒng)現(xiàn)有的配置數(shù)據(jù)很簡(jiǎn)單,主要是JDBC所需要的數(shù)據(jù),還有默認(rèn)讀取Spring的配置文件,目前系統(tǒng)只需要一個(gè)Spring的配置文件。示例如下:

<");"1.0" encoding="UTF-8"");

現(xiàn)在的功能需求是:如何能夠靈活的讀取配置文件的內(nèi)容?

##1.2 不用模式的解決方案## 不就是讀取配置文件嗎?實(shí)現(xiàn)很簡(jiǎn)單,直接讀取并解析xml就可以了。讀取xml的應(yīng)用包很多,這里都不用,直接采用最基礎(chǔ)的Dom解析就可以了。另外,讀取到xml中的值過(guò)后,后續(xù)如何處理,這里也不去管,這里只是實(shí)現(xiàn)把配置文件讀取并解析出來(lái)。

按照這個(gè)思路,很快就寫(xiě)出了實(shí)現(xiàn)的代碼,示例代碼如下:

/**
 * 讀取配置文件
 */
public class ReadAppXml {
    /**
     * 讀取配置文件內(nèi)容
     * @param filePathName 配置文件的路徑和文件名
     * @throws Exception
     */
    public void read(String filePathName)throws Exception{
        Document doc = null;
        //建立一個(gè)解析器工廠
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //獲得一個(gè)DocumentBuilder對(duì)象,這個(gè)對(duì)象代表了具體的DOM解析器
        DocumentBuilder builder=factory.newDocumentBuilder();
        //得到一個(gè)表示XML文檔的Document對(duì)象
        doc=builder.parse(filePathName);
        //去掉XML中作為格式化內(nèi)容的空白而映射在DOM樹(shù)中的Text Node對(duì)象
        doc.normalize();
      
        //獲取jdbc的配置值
        NodeList jdbc = doc.getElementsByTagName("jdbc");
        //只有一個(gè)jdbc,獲取jdbc中的驅(qū)動(dòng)類的名稱
        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 有何問(wèn)題## 看了上面的實(shí)現(xiàn),多簡(jiǎn)單啊,就是最基本的Dom解析嘛,要是采用其它的開(kāi)源工具包,比如dom4j、jDom之類的來(lái)處理,會(huì)更簡(jiǎn)單,這好像不值得一提呀,真的是這樣嗎?

請(qǐng)思考一個(gè)問(wèn)題:如果配置文件的結(jié)構(gòu)需要變動(dòng)呢?仔細(xì)想想,就會(huì)感覺(jué)出問(wèn)題來(lái)了。還是先看例子,然后再來(lái)總結(jié)這個(gè)問(wèn)題。

隨著開(kāi)發(fā)的深入進(jìn)行,越來(lái)越多可配置的數(shù)據(jù)被抽取出來(lái),需要添加到配置文件中,比如與數(shù)據(jù)庫(kù)的連接配置:就加入了是否需要、是否使用DataSource等配置。除了這些還加入了一些其它需要配置的數(shù)據(jù),例如:系統(tǒng)管理員、日志記錄方式、緩存線程的間隔時(shí)長(zhǎng)、默認(rèn)讀取哪些Spring配置文件等等,示例如下:

<");"1.0" encoding="UTF-8"");log>
        記錄日志的方式,1-數(shù)據(jù)庫(kù),2-文件
        記錄日志的文件名稱
    log>
    緩存線程的間隔時(shí)長(zhǎng)
    
        
            
                缺省讀取的Spring配置的文件名稱
            
            
                其它需要讀取的Spring配置的文件名稱
            
        
    

有朋友可能會(huì)想,改變一下配置文件,值得大驚小怪嗎?對(duì)于應(yīng)用系統(tǒng)開(kāi)發(fā)來(lái)講,這不是經(jīng)常發(fā)生的、很普通的一件事情嘛。

的確是這樣,改變一下配置文件不是件大事情,但是帶來(lái)的一系列麻煩也不容忽視,比如:修改了配置文件的結(jié)構(gòu),那么讀取配置文件的程序就需要做出相應(yīng)的變更;用來(lái)封裝配置文件數(shù)據(jù)的數(shù)據(jù)對(duì)象也需要相應(yīng)的修改;外部使用配置文件的地方,獲取數(shù)據(jù)的地方也會(huì)相應(yīng)變動(dòng)。

當(dāng)然在這一系列麻煩中,最讓人痛苦的莫過(guò)于修改讀取配置文件的程序了,有時(shí)候幾乎是重寫(xiě)。比如在使用Dom讀取第一個(gè)配置文件,讀取默認(rèn)的Spring配置文件的值的時(shí)候,可能的片斷代碼示例如下:

// 獲取application-xml
NodeList applicationXmlNode = doc.getElementsByTagName("application-xml");
String applicationXml = applicationXmlNode.item(0).getFirstChild().getNodeValue();
System.out.println("applicationXml=="+applicationXml);

但是如果配置文件改成第二個(gè),文件的結(jié)構(gòu)發(fā)生了改變,需要讀取的配置文件變成了多個(gè)了,讀取的程序也發(fā)生了改變,而且application-xml節(jié)點(diǎn)也不是直接從doc下獲取了。幾乎是完全重寫(xiě)了,此時(shí)可能的片斷代碼示例如下:

//先要獲取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");
//循環(huán)獲取每個(gè)application-xml元素的值
for(int i=0;i"applicationXml=="+applicationXml);
}

仔細(xì)對(duì)比上面在xml變化前后讀取值的代碼,你會(huì)發(fā)現(xiàn),由于xml結(jié)構(gòu)的變化,導(dǎo)致讀取xml文件內(nèi)容的代碼,基本上完全重寫(xiě)了。

問(wèn)題還不僅僅限于讀取元素的值,同樣體現(xiàn)在讀取屬性上。可能有些朋友說(shuō)可以換不同的xml解析方式來(lái)簡(jiǎn)化,不是還有Sax解析,實(shí)在不行換用其它開(kāi)源的解決方案。

確實(shí)通過(guò)使用不同的解析xml的方式是會(huì)讓程序變得簡(jiǎn)單點(diǎn),但是每次xml的結(jié)構(gòu)發(fā)生變化過(guò)后,或多或少都是需要修改程序中解析xml部分的。

有沒(méi)有辦法解決這個(gè)問(wèn)題呢?也就是當(dāng)xml的結(jié)構(gòu)發(fā)生改變過(guò)后,能夠很方便的獲取相應(yīng)元素、或者是屬性的值,而不用再去修改解析xml的程序。

#2 解決方案# ##2.1 解釋器模式來(lái)解決## 用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案,就是使用解釋器模式。那么什么是解釋器模式呢?

解釋器模式定義

這里的文法,簡(jiǎn)單點(diǎn)說(shuō)就是我們俗稱的“語(yǔ)法規(guī)則”。

應(yīng)用解釋器模式來(lái)解決的思路

要想解決當(dāng)xml的結(jié)構(gòu)發(fā)生改變后,不用修改解析部分的代碼,一個(gè)自然的思路就是要把解析部分的代碼寫(xiě)成公共的,而且還要是通用的,能夠滿足各種xml取值的需要,比如:獲取單個(gè)元素的值,獲取多個(gè)相同名稱的元素的值,獲取單個(gè)元素的屬性的值,獲取多個(gè)相同名稱的元素的屬性的值,等等。

要寫(xiě)成通用的代碼,又有幾個(gè)問(wèn)題要解決,如何組織這些通用的代碼?如何調(diào)用這些通用的代碼?以何種方式來(lái)告訴這些通用代碼,客戶端的需要?

要解決這些問(wèn)題,其中的一個(gè)解決方案就是解釋器模式。在描述這個(gè)模式的解決思路之前,先解釋兩個(gè)概念,一個(gè)是解析器(不是指xml的解析器),一個(gè)是解釋器。

這里的解析器,指的是把描述客戶端調(diào)用要求的表達(dá)式,經(jīng)過(guò)解析,形成一個(gè)抽象語(yǔ)法樹(shù)的程序,不是指xml的解析器。

這里的解釋器,指的是解釋抽象語(yǔ)法樹(shù),并執(zhí)行每個(gè)節(jié)點(diǎn)對(duì)應(yīng)的功能的程序。

要解決通用解析xml的問(wèn)題:

第一步:需要先設(shè)計(jì)一個(gè)簡(jiǎn)單的表達(dá)式語(yǔ)言,在客戶端調(diào)用解析程序的時(shí)候,傳入用這個(gè)表達(dá)式語(yǔ)言描述的一個(gè)表達(dá)式,然后把這個(gè)表達(dá)式通過(guò)解析器的解析,形成一個(gè)抽象的語(yǔ)法樹(shù)。

第二步:解析完成后,自動(dòng)調(diào)用解釋器來(lái)解釋抽象語(yǔ)法樹(shù),并執(zhí)行每個(gè)節(jié)點(diǎn)所對(duì)應(yīng)的功能,從而完成通用的xml解析。

這樣一來(lái),每次當(dāng)xml結(jié)構(gòu)發(fā)生了更改,也就是在客戶端調(diào)用的時(shí)候,傳入不同的表達(dá)式即可,整個(gè)解析xml過(guò)程的代碼都不需要再修改了。

##2.2 模式結(jié)構(gòu)和說(shuō)明## 解釋器模式的結(jié)構(gòu)如圖21.1所示:

AbstractExpression:定義解釋器的接口,約定解釋器的解釋操作。

TerminalExpression:終結(jié)符解釋器,用來(lái)實(shí)現(xiàn)語(yǔ)法規(guī)則中和終結(jié)符相關(guān)的操作,不再包含其它的解釋器,如果用組合模式來(lái)構(gòu)建抽象語(yǔ)法樹(shù)的話,就相當(dāng)于組合模式中的葉子對(duì)象,可以有多種終結(jié)符解釋器。

NonterminalExpression:非終結(jié)符解釋器,用來(lái)實(shí)現(xiàn)語(yǔ)法規(guī)則中非終結(jié)符相關(guān)的操作,通常一個(gè)解釋器對(duì)應(yīng)一個(gè)語(yǔ)法規(guī)則,可以包含其它的解釋器,如果用組合模式來(lái)構(gòu)建抽象語(yǔ)法樹(shù)的話,就相當(dāng)于組合模式中的組合對(duì)象,可以有多種非終結(jié)符解釋器。

Context:上下文,通常包含各個(gè)解釋器需要的數(shù)據(jù),或是公共的功能。

Client:客戶端,指的是使用解釋器的客戶端,通常在這里去把按照語(yǔ)言的語(yǔ)法做的表達(dá)式,轉(zhuǎn)換成為使用解釋器對(duì)象描述的抽象語(yǔ)法樹(shù),然后調(diào)用解釋操作。

##2.3 解釋器模式示例代碼##

先看看抽象表達(dá)式的定義,非常簡(jiǎn)單,定義一個(gè)執(zhí)行解釋的方法,示例代碼如下:

/**
 * 抽象表達(dá)式
 */
public abstract class AbstractExpression {
    /**
     * 解釋的操作
     * @param ctx 上下文對(duì)象
     */
    public abstract void interpret(Context ctx);
}

再來(lái)看看終結(jié)符表達(dá)式的定義,示例代碼如下:

/**
 * 終結(jié)符表達(dá)式
 */
public class TerminalExpression extends AbstractExpression{
    public void interpret(Context ctx) {
       //實(shí)現(xiàn)與語(yǔ)法規(guī)則中的終結(jié)符相關(guān)聯(lián)的解釋操作
    }
}

接下來(lái)該看看非終結(jié)符表達(dá)式的定義了,示例代碼如下:

/**
 * 非終結(jié)符表達(dá)式
 */
public class NonterminalExpression extends AbstractExpression{
    public void interpret(Context ctx) {
       //實(shí)現(xiàn)與語(yǔ)法規(guī)則中的非終結(jié)符相關(guān)聯(lián)的解釋操作
    }
}

上下文的定義,示例代碼如下:

/**
 * 上下文,包含解釋器之外的一些全局信息
 */
public class Context {
}

最后來(lái)看看客戶端的定義,示例代碼如下:

/**
 * 使用解釋器的客戶
 */
public class Client {
    //主要按照語(yǔ)法規(guī)則對(duì)特定的句子構(gòu)建抽象語(yǔ)法樹(shù)
    //然后調(diào)用解釋操作
}

看到這里,可能有些朋友會(huì)覺(jué)得,上面的示例代碼里面什么都沒(méi)有啊。這主要是因?yàn)榻忉屍髂J绞歉唧w的語(yǔ)法規(guī)則聯(lián)系在一起的,沒(méi)有相應(yīng)的語(yǔ)法規(guī)則,自然寫(xiě)不出對(duì)應(yīng)的處理代碼來(lái)。

但是這些示例還是有意義的,可以通過(guò)它們看出解釋器模式實(shí)現(xiàn)的基本架子,只是沒(méi)有內(nèi)部具體的處理罷了。

##2.4 使用解釋器模式重寫(xiě)示例## 通過(guò)上面的講述可以看出,要使用解釋器模式,一個(gè)重要的前提就是要定義一套語(yǔ)法規(guī)則,也稱為文法。不管這套文法的規(guī)則是簡(jiǎn)單還是復(fù)雜,必須有這么個(gè)東西,因?yàn)榻忉屍髂J骄褪莵?lái)按照這些規(guī)則進(jìn)行解析并執(zhí)行相應(yīng)的功能的。

為表達(dá)式設(shè)計(jì)簡(jiǎn)單的文法

為了通用,用root表示根元素,a、b、c、d等來(lái)代表元素,一個(gè)簡(jiǎn)單的xml如下:

<");"1.0" encoding="UTF-8"");"rootId">
    
        
            "testC">12345
            "1">d1
            "2">d2
            "3">d3
            "4">d4
        
    

約定表達(dá)式的文法如下:

**獲取單個(gè)元素的值:**從根元素開(kāi)始,一直到想要獲取值的元素,元素中間用“/”分隔,根元素前不加“/”。比如表達(dá)式“root/a/b/c”就表示獲取根元素下、a元素下、b元素下的c元素的值;

**獲取單個(gè)元素的屬性的值:**要獲取值的屬性一定是表達(dá)式的最后一個(gè)元素的屬性,在最后一個(gè)元素后面添加“.”然后再加上屬性的名稱。比如表達(dá)式“root/a/b/c.name”就表示獲取根元素下、a元素下、b元素下、c元素的name屬性的值;

**獲取相同元素名稱的值,當(dāng)然是多個(gè):**要獲取值的元素一定是表達(dá)式的最后一個(gè)元素,在最后一個(gè)元素后面添加“

具體示例

從簡(jiǎn)單的開(kāi)始,先來(lái)演示獲取單個(gè)元素的值和單個(gè)元素的屬性的值。在看具體代碼前,先來(lái)看看此時(shí)系統(tǒng)的整體結(jié)構(gòu),如圖21.3所示:

(1)定義抽象的解釋器

要實(shí)現(xiàn)解釋器的功能,首先定義一個(gè)抽象的解釋器,來(lái)約束所有被解釋的語(yǔ)法對(duì)象,也就是節(jié)點(diǎn)元素和終結(jié)符元素都要實(shí)現(xiàn)的功能。示例代碼如下:

/**
 * 用于處理自定義Xml取值表達(dá)式的接口
 */
public abstract class ReadXmlExpression {
    /**
     * 解釋表達(dá)式
     * @param c 上下文
     * @return 解析過(guò)后的值,為了通用,可能是單個(gè)值,也可能是多個(gè)值,
     *         因此就返回一個(gè)數(shù)組
     */
    public abstract String[] interpret(Context c);
}

(2)定義上下文

上下文是用來(lái)封裝解釋器需要的一些全局?jǐn)?shù)據(jù),也可以在里面封裝一些解釋器的公共功能,可以相當(dāng)于各個(gè)解釋器的公共對(duì)象,示例代碼如下:

/**
 *  上下文,用來(lái)包含解釋器需要的一些全局信息
 */
public class Context {
    /**
     * 上一個(gè)被處理的元素
     */
    private Element preEle = null;
    /**
     * Dom解析Xml的Document對(duì)象
     */
    private Document document = null;
    /**
     * 構(gòu)造方法
     * @param filePathName 需要讀取的xml的路徑和名字
     * @throws Exception
     */
    public Context(String filePathName) throws Exception{
       //通過(guò)輔助的Xml工具類來(lái)獲取被解析的xml對(duì)應(yīng)的Document對(duì)象
       this.document = XmlUtil.getRoot(filePathName);
    }
    /**
     * 重新初始化上下文
     */
    public void reInit(){
       preEle = null;
    }
    /**
     * 各個(gè)Expression公共使用的方法,
     * 根據(jù)父元素和當(dāng)前元素的名稱來(lái)獲取當(dāng)前的元素
     * @param pEle 父元素
     * @param eleName 當(dāng)前元素的名稱
     * @return 找到的當(dāng)前元素
     */
    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;
    }
}

在上下文中使用了一個(gè)工具對(duì)象XmlUtil來(lái)獲取Document對(duì)象,就是Dom解析xml,獲取相應(yīng)的Document對(duì)象,示例如下:

public class XmlUtil {
    public static Document getRoot(String filePathName) throws Exception{
        Document doc = null;
        //建立一個(gè)解析器工廠
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //獲得一個(gè)DocumentBuilder對(duì)象,這個(gè)對(duì)象代表了具體的DOM解析器
        DocumentBuilder builder=factory.newDocumentBuilder();
        //得到一個(gè)表示XML文檔的Document對(duì)象
        doc=builder.parse(filePathName);
        //去掉XML文檔中作為格式化內(nèi)容的空白而映射在DOM樹(shù)中的TextNode對(duì)象
        doc.normalize();

        return doc;
    }
}

(3)定義元素作為非終結(jié)符對(duì)應(yīng)的解釋器

接下來(lái)該看看如何解釋執(zhí)行中間元素了,首先這個(gè)元素相當(dāng)于組合模式的Composite對(duì)象,因此需要對(duì)子元素進(jìn)行維護(hù),另外這個(gè)元素的解釋處理,只是需要把自己找到,作為下一個(gè)元素的父元素就好了。示例代碼如下:

/**
 * 元素作為非終結(jié)符對(duì)應(yīng)的解釋器,解釋并執(zhí)行中間元素
 */
public class ElementExpression extends ReadXmlExpression{
    /**
     * 用來(lái)記錄組合的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) {
       //先取出上下文里的當(dāng)前元素作為父級(jí)元素
       //查找到當(dāng)前元素名稱所對(duì)應(yīng)的xml元素,并設(shè)置回到上下文中
       Element pEle = c.getPreEle();
       if(pEle==null){
           //說(shuō)明現(xiàn)在獲取的是根元素
           c.setPreEle(c.getDocument().getDocumentElement());
       }else{
           //根據(jù)父級(jí)元素和要查找的元素的名稱來(lái)獲取當(dāng)前的元素
           Element nowEle = c.getNowEle(pEle, eleName);
           //把當(dāng)前獲取的元素放到上下文里面
           c.setPreEle(nowEle);
       }
     
       //循環(huán)調(diào)用子元素的interpret方法
       String [] ss = null;
       for(ReadXmlExpression ele : eles){
           ss = ele.interpret(c);
       }
       return ss;
    }
}

(4)定義元素作為終結(jié)符對(duì)應(yīng)的解釋器

對(duì)于單個(gè)元素的處理,終結(jié)符有兩種,一個(gè)是元素終結(jié),一個(gè)是屬性終結(jié)。如果是元素終結(jié),就是要獲取元素的值;如果是屬性終結(jié),就是要獲取屬性的值。

分別來(lái)看看如何實(shí)現(xiàn)的,先看元素作為終結(jié)的解釋器,示例代碼如下:

/**
 * 元素作為終結(jié)符對(duì)應(yīng)的解釋器
 */
public class ElementTerminalExpression  extends ReadXmlExpression{
    /**
     * 元素的名字
     */
    private String eleName = "";

    public ElementTerminalExpression(String name){
        this.eleName = name;
    }  

    public String[] interpret(Context c) {
        //先取出上下文里的當(dāng)前元素作為父級(jí)元素
        Element pEle = c.getPreEle();
        //查找到當(dāng)前元素名稱所對(duì)應(yīng)的xml元素
        Element ele = null;
        if(pEle==null){
            //說(shuō)明現(xiàn)在獲取的是根元素
            ele = c.getDocument().getDocumentElement();
            c.setPreEle(ele);
        }else{
            //根據(jù)父級(jí)元素和要查找的元素的名稱來(lái)獲取當(dāng)前的元素
            ele = c.getNowEle(pEle, eleName);
            //把當(dāng)前獲取的元素放到上下文里面
            c.setPreEle(ele);
        }

        //然后需要去獲取這個(gè)元素的值
        String[] ss = new String[1];
        ss[0] = ele.getFirstChild().getNodeValue();
        return ss;
    }
}

(5)定義屬性作為終結(jié)符對(duì)應(yīng)的解釋器

接下來(lái)看看屬性終結(jié)符的實(shí)現(xiàn),就會(huì)比較簡(jiǎn)單,直接獲取最后的元素對(duì)象,然后獲取相應(yīng)的屬性的值,示例代碼如下:

/**
 * 屬性作為終結(jié)符對(duì)應(yīng)的解釋器
 */
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)使用解釋器

定義好了各個(gè)解釋器的實(shí)現(xiàn),可以寫(xiě)個(gè)客戶端來(lái)測(cè)試一下這些解釋器對(duì)象的功能了。使用解釋器的客戶端的工作會(huì)比較多,最主要的就是要組裝抽象的語(yǔ)法樹(shù)。

先來(lái)看看如何使用解釋器獲取單個(gè)元素的值,示例代碼如下:

public class Client {
    public static void main(String[] args) throws Exception {
        //準(zhǔn)備上下文
        Context c = new Context("InterpreterTest.xml");     

        //想要獲取c元素的值,也就是如下表達(dá)式的值:"root/a/b/c"
        //首先要構(gòu)建解釋器的抽象語(yǔ)法樹(shù)
        ElementExpression root = new ElementExpression("root");
        ElementExpression aEle = new ElementExpression("a");
        ElementExpression bEle = new ElementExpression("b");
        ElementTerminalExpression cEle = new ElementTerminalExpression("c");
        //組合起來(lái)
        root.addEle(aEle);
        aEle.addEle(bEle);
        bEle.addEle(cEle);
        //調(diào)用
        String ss[] = root.interpret(c);
        System.out.println("c的值是="+ss[0]);
    }
}

把前面定義的xml取名叫作“InterpreterTest.xml”,放到當(dāng)前工程的根下面,運(yùn)行看看,能正確獲取值嗎,運(yùn)行的結(jié)果如下:

c的值是=12345

再來(lái)測(cè)試一下獲取單個(gè)元素的屬性的值,示例代碼如下:

public class Client {
    public static void main(String[] args) throws Exception {
        //準(zhǔn)備上下文
        Context c = new Context("InterpreterTest.xml");

        //想要獲取c元素的name屬性,也就是如下表達(dá)式的值:"root/a/b/c.name"
        //這個(gè)時(shí)候c不是終結(jié)了,需要把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);
        //調(diào)用
        String ss[] = root.interpret(c);
        System.out.println("c的屬性name的值是="+ss[0]);
     
        //如果要使用同一個(gè)上下文,連續(xù)進(jìn)行解析,需要重新初始化上下文對(duì)象
        //比如要連續(xù)的重新再獲取一次屬性name的值,當(dāng)然你可以重新組合元素,
        //重新解析,只要是在使用同一個(gè)上下文,就需要重新初始化上下文對(duì)象
        c.reInit();
        String ss2[] = root.interpret(c);
        System.out.println("重新獲取c的屬性name的值是="+ss2[0]);
    }
}

運(yùn)行的結(jié)果如下:

c的屬性name的值是=testC
重新獲取c的屬性name的值是=testC

就像前面講述的那樣,制定一種簡(jiǎn)單的語(yǔ)言,讓客戶端用來(lái)表達(dá)從xml中取值的表達(dá)式的語(yǔ)言,然后為它們定義一種文法的表示,也就是語(yǔ)法規(guī)則,然后用解釋器對(duì)象來(lái)表示那些表達(dá)式,接下來(lái)通過(guò)運(yùn)行解釋器來(lái)解釋并執(zhí)行這些功能。

但是從前面的示例中,我們只能看到客戶端直接使用解釋器對(duì)象,來(lái)表示客戶要從xml中取什么值的語(yǔ)法樹(shù),而沒(méi)有看到如何從語(yǔ)言的表達(dá)式轉(zhuǎn)換成為這種解釋器的表示,這個(gè)功能是屬于解析器的功能,沒(méi)有劃分在標(biāo)準(zhǔn)的解釋器模式中,所以這里就先不演示,在后面會(huì)有示例來(lái)講解析器。

#3 模式講解# ##3.1 認(rèn)識(shí)解釋器模式##

解釋器模式的功能 解釋器模式使用解釋器對(duì)象來(lái)表示和處理相應(yīng)的語(yǔ)法規(guī)則,一般一個(gè)解釋器處理一條語(yǔ)法規(guī)則。理論上來(lái)說(shuō),只要能用解釋器對(duì)象把符合語(yǔ)法的表達(dá)式表示出來(lái),而且能夠構(gòu)成抽象的語(yǔ)法樹(shù),那都可以使用解釋器模式來(lái)處理。

語(yǔ)法規(guī)則和解釋器 語(yǔ)法規(guī)則和解釋器之間是有對(duì)應(yīng)關(guān)系的,一般一個(gè)解釋器處理一條語(yǔ)法規(guī)則,但是反過(guò)來(lái)并不成立,一條語(yǔ)法規(guī)則是可以有多種解釋和處理的,也就是一條語(yǔ)法規(guī)則可以對(duì)應(yīng)多個(gè)解釋器對(duì)象。

上下文的公用性 上下文在解釋器模式中起到非常重要的作用,由于上下文會(huì)被傳遞到所有的解釋器中,因此可以在上下文中存儲(chǔ)和訪問(wèn)解釋器的狀態(tài),比如前面的解釋器可以存儲(chǔ)一些數(shù)據(jù)在上下文中,后面的解釋器就可以獲取這些值。

另外還可以通過(guò)上下文傳遞一些在解釋器外部,但是解釋器需要的數(shù)據(jù),也可以是一些全局的,公共的數(shù)據(jù)。

上下文還有一個(gè)功能,就是可以提供所有解釋器對(duì)象的公共功能,類似于對(duì)象組合,而不是使用繼承來(lái)獲取公共功能,在每個(gè)解釋器對(duì)象里面都可以調(diào)用。

誰(shuí)來(lái)構(gòu)建抽象語(yǔ)法樹(shù) 在前面的示例中,大家已經(jīng)發(fā)現(xiàn),自己在客戶端手工來(lái)構(gòu)建抽象語(yǔ)法樹(shù),是很麻煩的,但是在解釋器模式中,并沒(méi)有涉及這部分功能,只是負(fù)責(zé)對(duì)構(gòu)建好的抽象語(yǔ)法樹(shù)進(jìn)行解釋處理。前面的測(cè)試簡(jiǎn)單,所以手工構(gòu)建抽象語(yǔ)法樹(shù)也不是特別困難的事,要是復(fù)雜了呢?如果還是手工創(chuàng)建,那跟修改解析xml的代碼也差不了多少。后面會(huì)給大家講到,可以提供解析器來(lái)實(shí)現(xiàn)把表達(dá)式轉(zhuǎn)換成為抽象語(yǔ)法樹(shù)。

還有一個(gè)問(wèn)題,就是一條語(yǔ)法規(guī)則是可以對(duì)應(yīng)多個(gè)解釋器對(duì)象的,也就是說(shuō)同一個(gè)元素,是可以轉(zhuǎn)換成多個(gè)解釋器對(duì)象的,這也就意味著同樣一個(gè)表達(dá)式,是可以構(gòu)成不同的抽象語(yǔ)法樹(shù)的,這也造成構(gòu)建抽象語(yǔ)法樹(shù)變得很困難,而且工作量很大。

誰(shuí)負(fù)責(zé)解釋操作 只要定義好了抽象語(yǔ)法樹(shù),肯定是解釋器來(lái)負(fù)責(zé)解釋執(zhí)行。雖然有不同的語(yǔ)法規(guī)則,但是解釋器不負(fù)責(zé)選擇究竟用哪一個(gè)解釋器對(duì)象來(lái)解釋執(zhí)行語(yǔ)法規(guī)則,選擇解釋器的功能在構(gòu)建抽象語(yǔ)法樹(shù)的時(shí)候就完成了。

所以解釋器只要忠實(shí)的按照抽象語(yǔ)法樹(shù)解釋執(zhí)行就好了。

解釋器模式的調(diào)用順序示意圖

解釋器模式的調(diào)用順序如圖21.4所示:

##3.2 讀取多個(gè)元素或?qū)傩缘闹?# 前面看過(guò)了如何獲取單個(gè)元素的值和單個(gè)元素的屬性的值,下面應(yīng)該來(lái)看看如何獲取多個(gè)元素的值,還有多個(gè)元素中相同名稱的屬性的值了。

獲取多個(gè)值和前面獲取單個(gè)值的實(shí)現(xiàn)思路大致相同,只是在取值的時(shí)候需要循環(huán)整個(gè)NodelList,依次取值,而不是只取出第一個(gè)來(lái)。當(dāng)然,由于語(yǔ)法發(fā)生了變動(dòng),所以對(duì)應(yīng)的解釋器也需要發(fā)生改變。

首先是有了一個(gè)表示多個(gè)元素作為終結(jié)符的語(yǔ)法,比如“root/a/b/d”中的“d”;其次有了一個(gè)表示多個(gè)元素的屬性作為終結(jié)符的語(yǔ)法,比如“root/a/b/d.id”中的“.id”;最后還有一個(gè)表示多個(gè)元素,但不是終結(jié)符的語(yǔ)法,比如“root/a/b/d.id”中的“d”。

還是看看代碼示例吧,會(huì)比較清楚。

解釋器接口沒(méi)有變化,原本就定義的是數(shù)組,早做好準(zhǔn)備了。

讀取Xml的工具類XmlUtil也沒(méi)有任何變化。

上下文做了一點(diǎn)改變。

把原來(lái)用來(lái)記錄上一次操作的元素,變成記錄上一次操作的多個(gè)元素的這么一個(gè)集合,然后為它提供相應(yīng)的getter/setter方法;

另外,原來(lái)根據(jù)父元素和當(dāng)前元素的名稱獲取當(dāng)前元素的方法,變成了根據(jù)父元素和當(dāng)前元素的名稱來(lái)獲取多個(gè)元素;

重新初始化上下文的方法里面,初始化的就是記錄上一次操作的多個(gè)元素的這個(gè)集合了;

具體的Context類的代碼示例如下:

/**
 *  上下文,用來(lái)包含解釋器需要的一些全局信息
 */
public class Context {
    /**
     * Dom解析Xml的Document對(duì)象
     */
    private Document document = null;
    /**
     * 上一次被處理的多個(gè)元素
     */
    private List preEles = new ArrayList();
    /**
     * 構(gòu)造方法
     * @param filePathName 需要讀取的xml的路徑和名字
     * @throws Exception
     */
    public Context(String filePathName) throws Exception{
       //通過(guò)輔助的Xml工具類來(lái)獲取被解析的xml對(duì)應(yīng)的Document對(duì)象
       this.document = XmlUtil.getRoot(filePathName);
    }
    /**
     * 重新初始化上下文
     */
    public void reInit(){
       preEles = new ArrayList();
    }
    /**
     * 各個(gè)Expression公共使用的方法,
     * 根據(jù)父元素和當(dāng)前元素的名稱來(lái)獲取當(dāng)前的多個(gè)元素的集合
     * @param pEle 父元素
     * @param eleName 當(dāng)前元素的名稱
     * @return 當(dāng)前的多個(gè)元素的集合
     */
    public List getNowEles(Element pEle,String eleName){
       List elements = new ArrayList();
       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)){
                  elements.add(nowEle);
              }
           }
       }
       return elements;
    }
  
    public Document getDocument() {
       return document;
    }
    public List getPreEles() {
       return preEles;
    }
    public void setPreEles(List nowEles) {
       this.preEles = nowEles;
    }
}

處理單個(gè)非終結(jié)符的對(duì)象ElementExpression,跟以前處理單個(gè)元素相比,主要是現(xiàn)在需要面向多個(gè)父元素,但是由于是單個(gè)非終結(jié)符的處理,因此在多個(gè)父元素下面去查找符合要求的元素,找到一個(gè)就停止,示例代碼如下:

/**
 * 單個(gè)元素作為非終結(jié)符的解釋器
 */
public class ElementExpression extends ReadXmlExpression{
    /**
     * 用來(lái)記錄組合的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) {
       //先取出上下文里的父級(jí)元素
       List pEles = c.getPreEles();
       Element ele = null;

       //把當(dāng)前獲取的元素放到上下文里面
       List nowEles = new ArrayList();      
       if(pEles.size()==0){
           //說(shuō)明現(xiàn)在獲取的是根元素
           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){
                  //找到一個(gè)就停止
                  break;
              }
           }
           List tempList = new ArrayList();
           tempList.add(nowEles.get(0));
           c.setPreEles(tempList);
       }
     
       //循環(huán)調(diào)用子元素的interpret方法
       String [] ss = null;
       for(ReadXmlExpression tempEle : eles){
           ss = tempEle.interpret(c);
       }
       return ss;
    }
}

用來(lái)處理單個(gè)元素作為終結(jié)符的類,也發(fā)生了一點(diǎn)改變,主要是從多個(gè)父元素去獲取當(dāng)前元素,如果當(dāng)前元素是多個(gè),就取第一個(gè),示例代碼如下:

/**
 * 元素作為終結(jié)符對(duì)應(yīng)的解釋器
 */
public class ElementTerminalExpression  extends ReadXmlExpression{
    /**
     * 元素的名字
     */
    private String eleName = "";
    public ElementTerminalExpression(String name){
       this.eleName = name;
    }
  
    public String[] interpret(Context c) {
       //先取出上下文里的當(dāng)前元素作為父級(jí)元素
       List pEles = c.getPreEles();
       //查找到當(dāng)前元素名稱所對(duì)應(yīng)的xml元素
       Element ele = null;
       if(pEles.size() == 0){
           //說(shuō)明現(xiàn)在獲取的是根元素
           ele = c.getDocument().getDocumentElement();
       }else{
           //獲取當(dāng)前的元素
           ele = c.getNowEles(pEles.get(0), eleName).get(0);
       }

       //然后需要去獲取這個(gè)元素的值
       String[] ss = new String[1];
       ss[0] = ele.getFirstChild().getNodeValue();
       return ss;
    }
}

新添加一個(gè)解釋器,用來(lái)解釋處理以多個(gè)元素的屬性作為終結(jié)符的情況,它的實(shí)現(xiàn)比較簡(jiǎn)單,只要獲取到最后的多個(gè)元素對(duì)象,然后循環(huán)這些元素,一個(gè)一個(gè)取出相應(yīng)的屬性值就好了,示例代碼如下:

/**
 * 以多個(gè)元素的屬性做為終結(jié)符的解釋處理對(duì)象
 */
public class PropertysTerminalExpression extends ReadXmlExpression{
    /**
     * 屬性名字
     */
    private String propName;
    public PropertysTerminalExpression(String propName){
       this.propName = propName;
    }
  
    public String[] interpret(Context c) {
       //獲取最后的多個(gè)元素
       List eles = c.getPreEles();
     
       String[] ss = new String[eles.size()];
       //循環(huán)多個(gè)元素,獲取每個(gè)的屬性的值
       for(int i=0;ireturn ss;
    }
}

新添加一個(gè)解釋器,用來(lái)解釋處理以多個(gè)元素作為終結(jié)符的情況,示例代碼如下:

/**
 * 以多個(gè)元素作為終結(jié)符的解釋處理對(duì)象
 */
public class ElementsTerminalExpression extends ReadXmlExpression{
    /**
     * 元素的名稱
     */
    private String eleName = "";
    public ElementsTerminalExpression(String name){
       this.eleName = name;
    }
  
    public String[] interpret(Context c) {
       //先取出上下文里的父級(jí)元素
       List pEles = c.getPreEles();
       //獲取當(dāng)前的多個(gè)元素
       List nowEles = new ArrayList();
     
       for(Element ele : pEles){
           nowEles.addAll(c.getNowEles(ele, eleName));
       }

       //然后需要去獲取這些元素的值
       String[] ss = new String[nowEles.size()];
       for(int i=0;ireturn ss;
    }
}

新添加一個(gè)解釋器,用來(lái)解釋處理以多個(gè)元素作為非終結(jié)符的情況,它的實(shí)現(xiàn)類似于以單個(gè)元素作為非終結(jié)符的情況,只是這次處理的是多個(gè),需要循環(huán)處理,同樣需要維護(hù)子對(duì)象,在我們現(xiàn)在設(shè)計(jì)的語(yǔ)法中,多個(gè)元素后面是可以再加子元素的,最起碼可以加多個(gè)屬性的終結(jié)符對(duì)象,示例代碼如下:

/**
 * 多個(gè)元素做為非終結(jié)符的解釋處理對(duì)象
 */
public class ElementsExpression extends ReadXmlExpression{
    /**
     * 用來(lái)記錄組合的ReadXmlExpression元素
     */
    private Collection eles = new ArrayList();
    /**
     * 元素名字
     */
    private String eleName = "";
    public ElementsExpression(String eleName){
       this.eleName = eleName;
    }
  
    public String[] interpret(Context c) {
       //先取出上下文里的父級(jí)元素
       List pEles = c.getPreEles();
       //把當(dāng)前獲取的元素放到上下文里面,這次是獲取多個(gè)元素
       List nowEles = new ArrayList();
     
       for(Element ele : pEles){
           nowEles.addAll(c.getNowEles(ele, eleName));
       }
       c.setPreEles(nowEles);
     
       //循環(huán)調(diào)用子元素的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;
    }
}

終于可以寫(xiě)客戶端來(lái)測(cè)試一下了,看看是否能實(shí)現(xiàn)期望的功能。先測(cè)試獲取多個(gè)元素的值的情況,示例代碼如下:

public class Client {
    public static void main(String[] args) throws Exception {
       //準(zhǔn)備上下文
       Context c = new Context("InterpreterTest.xml");
       //想要獲取多個(gè)d元素的值,也就是如下表達(dá)式的值:"root/a/b/d$"
       //首先要構(gòu)建解釋器的抽象語(yǔ)法樹(shù)
       ElementExpression root = new ElementExpression("root");
       ElementExpression aEle = new ElementExpression("a");
       ElementExpression bEle = new ElementExpression("b");
       ElementsTerminalExpression dEle = new ElementsTerminalExpression("d");
       //組合起來(lái)
       root.addEle(aEle);
       aEle.addEle(bEle);
       bEle.addEle(dEle);      
       //調(diào)用
       String ss[] = root.interpret(c);
       for(String s : ss){
           System.out.println("d的值是="+s);  
       }
    }
}

運(yùn)行結(jié)果如下:

d的值是=d1
d的值是=d2
d的值是=d3
d的值是=d4

接下來(lái)測(cè)試一下獲取多個(gè)屬性值的情況,示例代碼如下:

public class Client {
    public static void main(String[] args) throws Exception {
        //準(zhǔn)備上下文
        Context c = new Context("InterpreterTest.xml");

        //想要獲取d元素的id屬性,也就是如下表達(dá)式的值:"a/b/d$.id$"
        //首先要構(gòu)建解釋器的抽象語(yǔ)法樹(shù)
        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);

        //調(diào)用
        String ss[] = root.interpret(c);
        for (String s : ss) {
            System.out.println("d的屬性id值是=" + s);
        }
    }
}

運(yùn)行結(jié)果如下:

d的屬性id值是=1
d的屬性id值是=2
d的屬性id值是=3
d的屬性id值是=4

也很簡(jiǎn)單,是不是。只要學(xué)會(huì)了處理單個(gè)的值,處理多個(gè)值也就變得容易了,只要把原來(lái)獲取單個(gè)值的地方改成循環(huán)操作即可。

當(dāng)然,如果要使用同一個(gè)上下文,連續(xù)進(jìn)行解析,是同樣需要重新初始化上下文對(duì)象的。你還可以嘗試一下,如果是想要獲取多個(gè)元素下的,多個(gè)元素的同一個(gè)屬性的值,能實(shí)現(xiàn)嗎?你自己去測(cè)試,應(yīng)該是可以實(shí)現(xiàn)的。

##3.3 解析器## 前面看完了解釋器部分的功能,看到只要構(gòu)建好了抽象語(yǔ)法樹(shù),解釋器就能夠正確地解釋并執(zhí)行它,但是該如何得到這個(gè)抽象語(yǔ)法樹(shù)呢?前面的測(cè)試都是人工組合好抽象語(yǔ)法樹(shù)的,如果實(shí)際開(kāi)發(fā)中還這樣做,基本上工作量跟修改解析xml的代碼差不多。

這就需要解析器出場(chǎng)了,這個(gè)程序?qū)iT(mén)負(fù)責(zé)把按照語(yǔ)法表達(dá)的表達(dá)式,解析轉(zhuǎn)換成為解釋器需要的抽象語(yǔ)法樹(shù)。當(dāng)然解析器是跟表達(dá)式的語(yǔ)法,還有解釋器對(duì)象緊密關(guān)聯(lián)的。

下面來(lái)示例一下解析器的實(shí)現(xiàn),把符合前面定義的語(yǔ)法的表達(dá)式,轉(zhuǎn)換成為前面實(shí)現(xiàn)的解釋器的抽象語(yǔ)法樹(shù)。解析器有很多種實(shí)現(xiàn)方式,沒(méi)有什么定式,只要能完成相應(yīng)的功能即可,比如表驅(qū)動(dòng)、語(yǔ)法分析生成程序等等。這里的示例采用自己來(lái)分解表達(dá)式以實(shí)現(xiàn)構(gòu)建抽象語(yǔ)法樹(shù)的功能,沒(méi)有使用遞歸,是用循環(huán)實(shí)現(xiàn)的,當(dāng)然也可以用遞歸來(lái)做。

實(shí)現(xiàn)思路

要實(shí)現(xiàn)解析器也不復(fù)雜,大約有下面三個(gè)步驟:

第一步:把客戶端傳遞來(lái)的表達(dá)式進(jìn)行分解,分解成為一個(gè)一個(gè)的元素,并用一個(gè)對(duì)應(yīng)的解析模型來(lái)封裝這個(gè)元素的一些信息;

第二步:根據(jù)每個(gè)元素的信息,轉(zhuǎn)化成相對(duì)應(yīng)的解析器對(duì)象;

第三步:按照先后順序,把這些解析器對(duì)象組合起來(lái),就得到抽象語(yǔ)法樹(shù)了;

可能有朋友會(huì)說(shuō),為什么不把第一步和第二步合并,直接分解出一個(gè)元素就轉(zhuǎn)換成相應(yīng)的解析器對(duì)象呢?原因有兩個(gè):

其一是功能分離,不要讓一個(gè)方法的功能過(guò)于復(fù)雜;

其二是為了今后的修改和擴(kuò)展,現(xiàn)在語(yǔ)法簡(jiǎn)單,所以轉(zhuǎn)換成解析器對(duì)象需要考慮的東西少,直接轉(zhuǎn)換也不難,但要是語(yǔ)法復(fù)雜了,直接轉(zhuǎn)換就很雜亂了;

事實(shí)上,封裝解析屬性的數(shù)據(jù)模型充當(dāng)了第一步和第二步操作間的接口,使第一步和第二步都變簡(jiǎn)單了。

先來(lái)看看用來(lái)封裝每一個(gè)解析出來(lái)的元素對(duì)應(yīng)的屬性對(duì)象,示例代碼如下:

/**
 * 用來(lái)封裝每一個(gè)解析出來(lái)的元素對(duì)應(yīng)的屬性
 */
public class ParserModel {
    /**
     * 是否單個(gè)值
     */
    private boolean singleVlaue;
    /**
     * 是否屬性,不是屬性就是元素
     */
    private boolean propertyValue;
    /**
     * 是否終結(jié)符
     */
    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;
    }
}

看看解析器的實(shí)現(xiàn),代碼稍微復(fù)雜點(diǎn),注釋很詳盡,為了整體展示解析器,就不去分開(kāi)每步單講了,不過(guò)要注意一點(diǎn):下面這種實(shí)現(xiàn)沒(méi)有考慮并發(fā)處理的情況,如果要用在多線程環(huán)境下,需要補(bǔ)充相應(yīng)的處理,特別提示一下。示例代碼如下:

/**
 * 根據(jù)語(yǔ)法來(lái)解析表達(dá)式,轉(zhuǎn)換成為相應(yīng)的抽象語(yǔ)法樹(shù)
 */
public class Parser {
    /**
     * 私有化構(gòu)造器,避免外部無(wú)謂的創(chuàng)建對(duì)象實(shí)例
     */
    private Parser(){
       //
    }
    //定義幾個(gè)常量,內(nèi)部使用
    private final static String BACKLASH = "/";
    private final static String DOT = ".";
    private final static String DOLLAR = "$";
    /**
     * 按照分解的先后記錄需要解析的元素的名稱
     */
    private static List listEle = null;

    /**
     * 傳入一個(gè)字符串表達(dá)式,通過(guò)解析,組合成為一個(gè)抽象的語(yǔ)法樹(shù)
     * @param expr 描述要取值的字符串表達(dá)式
     * @return 對(duì)應(yīng)的抽象語(yǔ)法樹(shù)
     */
    public static ReadXmlExpression parse(String expr){
        //先初始化記錄需解析的元素的名稱的集 會(huì)
        listEle = new ArrayList();

        //第一步:分解表達(dá)式,得到需要解析的元素名稱和該元素對(duì)應(yīng)的解析模型
        Map mapPath = parseMapPath(expr);
     
        //第二步:根據(jù)節(jié)點(diǎn)的屬性轉(zhuǎn)換成為相應(yīng)的解釋器對(duì)象
        List list = mapPath2Interpreter(mapPath);
        //第三步:組合抽象語(yǔ)法樹(shù),一定要按照先后順序來(lái)組合,
        //否則對(duì)象的包含關(guān)系就亂了
        ReadXmlExpression returnRe = buildTree(list);
  
        return returnRe;        
    }
    /*----------------------開(kāi)始實(shí)現(xiàn)第一步-----------------------*/
    /**
     * 按照從左到右順序來(lái)分解表達(dá)式,得到需要解析的元素名稱,
     * 還有該元素對(duì)應(yīng)的解析模型
     * @param expr 需要分解的表達(dá)式
     * @return 得到需要解析的元素名稱,還有該元素對(duì)應(yīng)的解析模型
     */
    private static Map parseMapPath(String expr){
        //先按照/分割字符串
        StringTokenizer tokenizer = new StringTokenizer(expr, BACKLASH);
        //初始化一個(gè)map用來(lái)存放分解出來(lái)的值
        Map mapPath = new HashMap();
        while (tokenizer.hasMoreTokens()) {
            String onePath = tokenizer.nextToken();
            if (tokenizer.hasMoreTokens()) {
               //還有下一個(gè)值,說(shuō)明這不是最后一個(gè)元素
               //按照現(xiàn)在的語(yǔ)法,屬性必然在最后,因此也不是屬性
               setParsePath(false,onePath,false,mapPath);
            } else {
               //說(shuō)明到最后了
               int dotIndex = onePath.indexOf(DOT);
               if (dotIndex > 0) {
                   //說(shuō)明是要獲取屬性的值,那就按照"."來(lái)分割,
                   //前面的就是元素名字,后面的是屬性的名字
                   String eleName = onePath.substring(0, dotIndex);
                   String propName = onePath.substring(dotIndex + 1);
                   //設(shè)置屬性前面的那個(gè)元素,自然不是最后一個(gè),也不是屬性
                   setParsePath(false,eleName,false,mapPath);
                   //設(shè)置屬性,按照現(xiàn)在的語(yǔ)法定義,屬性只能是最后一個(gè)
                   setParsePath(true,propName,true,mapPath);
               } else {
                   //說(shuō)明是取元素的值,而且是最后一個(gè)元素的值
                   setParsePath(true,onePath,false,mapPath);
               }
               break;
            }
        }
        return mapPath;
    }
    /**
     * 按照分解出來(lái)的位置和名稱來(lái)設(shè)置需要解析的元素名稱,
     * 還有該元素對(duì)應(yīng)的解析模型
     * @param end 是否是最后一個(gè)
     * @param ele 元素名稱
     * @param propertyValue 是否是取屬性
     * @param mapPath 設(shè)置需要解析的元素名稱,還有該元素對(duì)應(yīng)的解析模型的Map
     */
    private static void setParsePath(boolean end,String ele,boolean propertyValue,Map mapPath){
        ParserModel pm = new ParserModel();
        pm.setEnd(end);
        //如果帶有$符號(hào)就說(shuō)明不是一個(gè)值
        pm.setSingleVlaue(!(ele.indexOf(DOLLAR)>0));
        pm.setPropertyValue(propertyValue);             
        //去掉$
        ele = ele.replace(DOLLAR, "");
        mapPath.put(ele,pm);
        listEle.add(ele);
    }
    /*----------------------第一步實(shí)現(xiàn)結(jié)束-----------------------*/

    /*----------------------開(kāi)始實(shí)現(xiàn)第二步-----------------------*/  
    /**
     * 把分解出來(lái)的元素名稱,根據(jù)對(duì)應(yīng)的解析模型轉(zhuǎn)換成為相應(yīng)的解釋器對(duì)象
     * @param mapPath 分解出來(lái)的需解析的元素名稱,還有該元素對(duì)應(yīng)的解析模型
     * @return 把每個(gè)元素轉(zhuǎn)換成為相應(yīng)的解釋器對(duì)象后的集合
     */
    private static List mapPath2Interpreter(Map mapPath){
        List list = new ArrayList();
        //一定要按照分解的先后順序來(lái)轉(zhuǎn)換成解釋器對(duì)象
        for(String key : listEle){
            ParserModel pm = mapPath.get(key);
            ReadXmlExpression obj = null;
            if(!pm.isEnd()){
               if(pm.isSingleVlaue()){
                   //不是最后一個(gè),是一個(gè)值,轉(zhuǎn)化為
                   obj = new ElementExpression(key);            
               }else{
                   //不是最后一個(gè),是多個(gè)值,轉(zhuǎn)化為
                   obj = new ElementsExpression(key);
               }
            }else{
               if(pm.isPropertyValue()){
                   if(pm.isSingleVlaue()){
                      //是最后一個(gè),是一個(gè)值,取屬性的值,轉(zhuǎn)化為
                      obj = new PropertyTerminalExpression(key);
                   }else{
                      //是最后一個(gè),是多個(gè)值,取屬性的值,轉(zhuǎn)化為
                      obj = new PropertysTerminalExpression(key);
                   }
               }else{
                   if(pm.isSingleVlaue()){
                      //是最后一個(gè),是一個(gè)值,取元素的值,轉(zhuǎn)化為
                      obj = new ElementTerminalExpression(key);
                   }else{
                      //是最后一個(gè),是多個(gè)值,取元素的值,轉(zhuǎn)化為
                      obj = new ElementsTerminalExpression(key);
                   }
               }
            }
            //把轉(zhuǎn)換后的對(duì)象添加到集合中
            list.add(obj);
        }
        return list;
    }
    /*----------------------第二步實(shí)現(xiàn)結(jié)束-----------------------*/  
  
    /*----------------------開(kāi)始實(shí)現(xiàn)第三步-----------------------*/  
    private static ReadXmlExpression buildTree(List list){
        //第一個(gè)對(duì)象,也是返回去的對(duì)象,就是抽象語(yǔ)法樹(shù)的根
        ReadXmlExpression returnRe = null;
        //定義上一個(gè)對(duì)象
        ReadXmlExpression preRe = null;
        for(ReadXmlExpression re : list){        
            if(preRe==null){
               //說(shuō)明是第一個(gè)元素
               preRe = re;
               returnRe = re;
            }else{
               //把元素添加到上一個(gè)對(duì)象下面,同時(shí)把本對(duì)象設(shè)置成為oldRe,
               //作為下一個(gè)對(duì)象的父結(jié)點(diǎn)
               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;
    }
    /*----------------------第三步實(shí)現(xiàn)結(jié)束-----------------------*/
}

看完這個(gè)稍長(zhǎng)點(diǎn)的解析器程序,該來(lái)體會(huì)一下,有了它對(duì)我們的開(kāi)發(fā)有什么好處,寫(xiě)個(gè)客戶端來(lái)測(cè)試看看?,F(xiàn)在的客戶端就非常簡(jiǎn)單了,主要三步: 首先是設(shè)計(jì)好想要取值的表達(dá)式;

然后是通過(guò)解析器解析獲取抽象語(yǔ)法樹(shù);

最后就是請(qǐng)求解釋器解釋并執(zhí)行這個(gè)抽象語(yǔ)法樹(shù),就得到最后的結(jié)果了;

客戶端測(cè)試的示例代碼如下:

public class Client {
    public static void main(String[] args) throws Exception {
        //準(zhǔn)備上下文
        Context c = new Context("InterpreterTest.xml");
        //通過(guò)解析器獲取抽象語(yǔ)法樹(shù)
        ReadXmlExpression re = Parser.parse("root/a/b/d$.id$");
        //請(qǐng)求解析,獲取返回值
        String ss[] = re.interpret(c);

        for (String s : ss) {
            System.out.println("d的屬性id值是=" + s);
        }
    
        //如果要使用同一個(gè)上下文,連續(xù)進(jìn)行解析,需要重新初始化上下文對(duì)象
        c.reInit();
        ReadXmlExpression re2 = Parser.parse("root/a/b/d$");
        //請(qǐng)求解析,獲取返回值
        String ss2[] = re2.interpret(c);
        for (String s : ss2) {
            System.out.println("d的值是=" + s);
        }
     }
}

簡(jiǎn)單多了吧!通過(guò)使用解釋器模式,自行設(shè)計(jì)一種簡(jiǎn)單的語(yǔ)法,就可以用很簡(jiǎn)單的表達(dá)式來(lái)獲取你想要的xml中的值了。有的朋友可能會(huì)想到XPath,沒(méi)錯(cuò),本章示例實(shí)現(xiàn)的功能就是類似于XPath的部分功能。

如果今后xml的結(jié)構(gòu)要是發(fā)生了變化,或者是想要獲取不同的值,基本上就是修改那個(gè)表達(dá)式而已,你可以試試看,能否完成前面實(shí)現(xiàn)過(guò)的功能。比如:

想要獲取c元素的值,表達(dá)式為:“root/a/b/c”;

想要獲取c元素的name屬性值,表達(dá)式為:“root/a/b/c.name”;

想要獲取d元素的值,表達(dá)式為:“root/a/b/d$”,獲取d的屬性上面已經(jīng)測(cè)試了;

##3.4 解釋器模式的優(yōu)缺點(diǎn)##

易于實(shí)現(xiàn)語(yǔ)法 在解釋器模式中,一條語(yǔ)法規(guī)則用一個(gè)解釋器對(duì)象來(lái)解釋執(zhí)行,對(duì)于解釋器的實(shí)現(xiàn)來(lái)講,功能就變得比較簡(jiǎn)單,只需要考慮這一條語(yǔ)法規(guī)則的實(shí)現(xiàn)就好了,其它的都不用管。

易于擴(kuò)展新的語(yǔ)法 正是由于采用一個(gè)解釋器對(duì)象負(fù)責(zé)一條語(yǔ)法規(guī)則的方式,使得擴(kuò)展新的語(yǔ)法非常容易,擴(kuò)展了新的語(yǔ)法,只需要?jiǎng)?chuàng)建相應(yīng)的解釋器對(duì)象,在創(chuàng)建抽象語(yǔ)法樹(shù)的時(shí)候使用這個(gè)新的解釋器對(duì)象就可以了。

不適合復(fù)雜的語(yǔ)法 如果語(yǔ)法特別復(fù)雜,構(gòu)建解釋器模式需要的抽象語(yǔ)法樹(shù)的工作是非常艱巨的,再加上有可能會(huì)需要構(gòu)建多個(gè)抽象語(yǔ)法樹(shù)。所以解釋器模式不太適合于復(fù)雜的語(yǔ)法,對(duì)于復(fù)雜的語(yǔ)法,使用語(yǔ)法分析程序或編譯器生成器可能會(huì)更好。

##3.5 思考解釋器模式##

解釋器模式的本質(zhì) 解釋器模式的本質(zhì):分離實(shí)現(xiàn),解釋執(zhí)行。

解釋器模式通過(guò)一個(gè)解釋器對(duì)象處理一個(gè)語(yǔ)法規(guī)則的方式

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/6951.html

相關(guān)文章

  • 設(shè)計(jì)模式十四釋器模式

    摘要:解決方案解釋器模式來(lái)解決用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案,就是使用解釋器模式。使用解釋器模式重寫(xiě)示例通過(guò)上面的講述可以看出,要使用解釋器模式,一個(gè)重要的前提就是要定義一套語(yǔ)法規(guī)則,也稱為文法。#1 場(chǎng)景問(wèn)題# ##1.1 讀取配置文件## 考慮這樣一個(gè)實(shí)際的應(yīng)用,維護(hù)系統(tǒng)自定義的配置文件。 幾乎每個(gè)實(shí)際的應(yīng)用系統(tǒng)都有與應(yīng)用自身相關(guān)的配置文件,這個(gè)配置文件是由開(kāi)發(fā)人員根據(jù)需要自定義的,系統(tǒng)...

    番茄西紅柿 評(píng)論0 收藏0
  • 設(shè)計(jì)模式十四釋器模式

    摘要:解決方案解釋器模式來(lái)解決用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案,就是使用解釋器模式。使用解釋器模式重寫(xiě)示例通過(guò)上面的講述可以看出,要使用解釋器模式,一個(gè)重要的前提就是要定義一套語(yǔ)法規(guī)則,也稱為文法。#1 場(chǎng)景問(wèn)題# ##1.1 讀取配置文件## 考慮這樣一個(gè)實(shí)際的應(yīng)用,維護(hù)系統(tǒng)自定義的配置文件。 幾乎每個(gè)實(shí)際的應(yīng)用系統(tǒng)都有與應(yīng)用自身相關(guān)的配置文件,這個(gè)配置文件是由開(kāi)發(fā)人員根據(jù)需要自定義的,系統(tǒng)...

    番茄西紅柿 評(píng)論0 收藏0
  • dubbo源碼解析(十四)遠(yuǎn)程調(diào)用——dubbo協(xié)議

    摘要:遠(yuǎn)程調(diào)用協(xié)議目標(biāo)介紹遠(yuǎn)程調(diào)用中跟協(xié)議相關(guān)的設(shè)計(jì)和實(shí)現(xiàn),介紹的源碼。二該類繼承了,是協(xié)議中獨(dú)有的服務(wù)暴露者。八該類也是對(duì)的裝飾,其中增強(qiáng)了調(diào)用次數(shù)多功能。 遠(yuǎn)程調(diào)用——dubbo協(xié)議 目標(biāo):介紹遠(yuǎn)程調(diào)用中跟dubbo協(xié)議相關(guān)的設(shè)計(jì)和實(shí)現(xiàn),介紹dubbo-rpc-dubbo的源碼。 前言 Dubbo 缺省協(xié)議采用單一長(zhǎng)連接和 NIO 異步通訊,適合于小數(shù)據(jù)量大并發(fā)的服務(wù)調(diào)用,以及服務(wù)消費(fèi)者...

    rickchen 評(píng)論0 收藏0
  • 機(jī)器視覺(jué)、模式識(shí)別庫(kù)匯總

    摘要:十開(kāi)放模式識(shí)別項(xiàng)目開(kāi)放模式識(shí)別項(xiàng)目,致力于開(kāi)發(fā)出一套包含圖像處理計(jì)算機(jī)視覺(jué)自然語(yǔ)言處理模式識(shí)別機(jī)器學(xué)習(xí)和相關(guān)領(lǐng)域算法的函數(shù)庫(kù)。 一、開(kāi)源生物特征識(shí)別庫(kù) OpenBROpenBR 是一個(gè)用來(lái)從照片中識(shí)別人臉的工具。還支持推算性別與年齡。使用方法:$ br -algorithm FaceRecognition -compare me.jpg you.jpg二、計(jì)算機(jī)視覺(jué)庫(kù) OpenCVOpenC...

    habren 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<