|
版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明 作者:Bill Siggelkow;niuji 原文地址:http://www./pub/a/onjava/2005/03/02/commonchains.html 中文地址:http://www./resource/article/44/44049_Commons+Chain.html 關(guān)鍵詞: Commons Chain 作為程序開發(fā)人員,我們經(jīng)常需要對一個(gè)實(shí)際上程序性的系統(tǒng)應(yīng)用面向?qū)ο蟮姆椒?。商業(yè)分析家和管理人員描述這樣的系統(tǒng)時(shí)通常不使用類層次和序列圖,而是使用流程圖和工作流圖表。但是不論如何,使用面向?qū)ο蟮姆椒ń鉀Q這些問題時(shí)會(huì)帶來更多的靈活性。面向?qū)ο蟮脑O(shè)計(jì)模式提供了有用的結(jié)構(gòu)和行為來描述這種順序的處理,比如模版方法(Template Method)[GoF]和責(zé)任鏈(Chain of Responsibility)[GoF]。 Jakarta Commons的子項(xiàng)目Chain將上述兩個(gè)模式組合成一個(gè)可復(fù)用的Java框架用于描述順序的處理流程。這個(gè)在Jakarta Commons project社區(qū)中開發(fā)的框架,已經(jīng)被廣泛的接受并且使用于許多有趣的應(yīng)用中,特別的是他被Struts和Shale應(yīng)用框架作為處理HTTP請求處理的基礎(chǔ)機(jī)制。你可以在需要定義和執(zhí)行一組連續(xù)的步驟時(shí)使用Commons Chain。 至于經(jīng)典設(shè)計(jì)模式,開發(fā)者和架構(gòu)師普遍使用模版方法(Template Method)造型順序處理。模版方法(Template Method)中使用一個(gè)抽象的父類定義使用的算法:處理的步驟,具體實(shí)現(xiàn)交給子類。當(dāng)然,父類也可以為算法所使用的方法提供一個(gè)缺省實(shí)現(xiàn)。 由于模版方法(Template Method)依賴?yán)^承——子類必須繼承定義了算法的父類——因此使用這個(gè)模式的軟件表現(xiàn)出緊耦合而且缺少靈活性。又由于實(shí)現(xiàn)類添加自己的行為前必須擴(kuò)展父類,溝每⑷嗽北幌拗樸誒嗖憒沃?,磦蝤限制另樈{蟶杓頻牧榛钚浴ommons Chain使用配置文件定義算法,在程序運(yùn)行時(shí)解析配置文件,從而很好的解決了這個(gè)問題。 現(xiàn)在來看一下Commons Chain是怎樣工作的,我們從一個(gè)人造的例子開始:二手車銷售員的商業(yè)流程。下面是銷售流程的步驟: 1. 得到用戶信息 2. 試車 3. 談判銷售 4. 安排財(cái)務(wù) 5. 結(jié)束銷售 現(xiàn)在假設(shè)使用模版方法(Template Method)造型這個(gè)流程。首先建立一個(gè)定義了算法的抽象類: 清單1 public abstract class SellVehicleTemplate {
現(xiàn)在來看一下怎樣用Commons Chain實(shí)現(xiàn)這個(gè)流程。首先,下載Commons Chain。你可以直接下載最新的zip或tar文件,也可以從CVS或者SubVersion源碼庫檢出Commons Chain模塊得到最新的代碼。解壓縮打包文件,將commons-chain.jar放入你的classpath中。 使用Commons Chain實(shí)現(xiàn)這個(gè)商業(yè)流程,必須將流程中的每一步寫成一個(gè)類,這個(gè)類需要有一個(gè)public的方法execute()。這和傳統(tǒng)的命令模式(Command pattern)實(shí)現(xiàn)相同。下面簡單實(shí)現(xiàn)了“得到用戶信息”: 清單2 package com.jadecove.chain.sample; 由于只是演示,這個(gè)類并沒有做很多工作。這里將用戶名放入了Context對象ctx中。這個(gè)Context對象連接了各個(gè)命令。暫時(shí)先將這個(gè)對象想象成根據(jù)關(guān)鍵字存取值的哈希表。所有后來的命令可以通過它訪問剛才放入的用戶名。TestDriveVehicle,NegotiateSale和ArrangeFinancing命令的實(shí)現(xiàn)只是簡單的打印了將執(zhí)行什么操作。 清單3 package com.jadecove.chain.sample; CloseSale從Context對象中取出GetCustomerInfo放入的用戶名,并將其打印。 清單4 package com.jadecove.chain.sample; 現(xiàn)在你可以將這個(gè)流程定義成一個(gè)序列(或者說“命令鏈”)。 清單5 package com.jadecove.chain.sample; 運(yùn)行這個(gè)類將會(huì)輸出以下結(jié)果: Get customer info Test drive the vehicle Negotiate sale Arrange financing Congratulations George Burdell, you bought a new car! 在進(jìn)一步深入之前,讓我們來看一下我們使用了的Commons Chain的類和接口。 ![]() 圖1 Command類和Chain類的關(guān)系就是組合模式(Composite pattern)[GoF]的例子:Chain不僅由多個(gè)Command組成,而且自己也是Command。這使你可以非常簡單得將單個(gè)命令(Command)替換成由多個(gè)命令(Command)組成的鏈(Chain)。這個(gè)由Command對象唯一操作定義的方法代表了一個(gè)直接的命令: public boolean execute(Context context); 參數(shù)context僅僅是一個(gè)存放了名稱-值對的集合。接口Context在這里作為一個(gè)標(biāo)記接口:它擴(kuò)展了java.util.Map但是沒有添加任何特殊的行為。于此相反,類ContextBase不僅提供了對Map的實(shí)現(xiàn)而且增加了一個(gè)特性:屬性-域透明。這個(gè)特性可以通過使用Map的put和get方法操作JavaBean的域,當(dāng)然這些域必須使用標(biāo)準(zhǔn)的getFoo和setFoo方法定義。那些通過JavaBean的“setter”方法設(shè)置的值,可以通過對應(yīng)的域名稱,用Map的get方法得到。同樣,那些用Map的put方法設(shè)置的值可以通過JavaBean的“getter”方法得到。 例如,我們可以創(chuàng)建一個(gè)專門的context提供顯式的customerName屬性支持。 清單6 package com.jadecove.chain.sample; 現(xiàn)在你既可以進(jìn)行Map的一般屬性存取操作同時(shí)也可以使用顯式的JavaBean的訪問和修改域的方法,這兩個(gè)將產(chǎn)生同樣的效果。但是首先你需要在運(yùn)行SellVehicleChain時(shí)實(shí)例化SellVehiceContext而不是ContextBase。 清單7 public static void main(String[] args) throws Exception {
盡管你不改變GetCustomerInfo中存放用戶名的方法——仍然使用ctx.put("customerName", "George Burdell")——你可以在CloseSale中使用getCustomerName()方法得到用戶名。 清單8 public boolean execute(Context ctx) throws Exception {
那些依賴類型安全和context的顯式域的命令(Command)可以利用標(biāo)準(zhǔn)的getter和setter方法。當(dāng)一些新的命令(Command)被添加時(shí),它們可以不用考慮context的具體實(shí)現(xiàn),直接通過Map的get和put操作屬性。不論采用何種機(jī)制,ContextBase類都可以保證命令(Command)間可以通過context互操作。 下面這個(gè)例子展示了如何使用Commons Chain的API建立并執(zhí)行一組順序的命令。當(dāng)然,和現(xiàn)在大多數(shù)Java軟件一樣,Commons Chain可以使用XML文件作為配置文件。你可以將“汽車銷售”流程的步驟在XML文件中定義。這個(gè)文件有個(gè)規(guī)范的命名chain-config.xml。 清單9 <catalog> Chain的配置文件可以包含多個(gè)鏈定義,這些鏈定義可以集合進(jìn)不同的編目中。在這個(gè)例子中,鏈定義在一個(gè)默認(rèn)的編目中定義。事實(shí)上,你可以在這個(gè)文件中定義多個(gè)名字的編目,每個(gè)編目可擁有自己的鏈組。 現(xiàn)在你可以使用Commons Chain提供的類載入編目并得到指定的鏈,而不用像SellVehicleChain中那樣自己在程序中定義一組命令: 清單10 package com.jadecove.chain.sample; Chain使用Commons Digester來讀取和解析配置文件。因此你需要將Commons Digester.jar加入classpath中。我使用了1.6版本并且工作得很好。Digester使用了Commons Collectios(我使用的版本是3.1),Commons Logging(版本1.0.4),Commons BeanUtils(1.7.0),因此你也需要將它們的jar文件加入classpath中。在加入這些jar后,CatalogLoader就可以被編譯和運(yùn)行,它的輸出和另外兩個(gè)測試完全相同。 現(xiàn)在你可以在XML文件中定義鏈,并可以在程序中得到這個(gè)鏈(別忘了鏈也是命令),這樣擴(kuò)展的可能性和程序的靈活性可以說是無限的。假設(shè)過程“安排財(cái)務(wù)”實(shí)際上由一個(gè)完全分離的商業(yè)部門處理。這個(gè)部門希望為這種銷售建立自己的工作流程。Chain提供了嵌套鏈來實(shí)現(xiàn)這個(gè)要求。因?yàn)殒湵旧砭褪敲?,因此你可以用指向另一個(gè)鏈的引用替換一個(gè)單一用途的命令。下面是增加了新流程的鏈的定義: 清單11 <catalog name="auto-sales"> Commons Chain提供了一個(gè)常用的命令LookupCommand來查找和執(zhí)行另一個(gè)鏈。屬性optional用于控制當(dāng)指定的嵌套鏈沒有找到時(shí)如何處理。optional=true時(shí),即使鏈沒找到,處理也會(huì)繼續(xù)。反之,LookupCommand將拋出IllegalArgumentException,告知指定的命令未找到。 在下面三種情況下,命令鏈將結(jié)束: 1. 命令的execute方法返回true 2. 運(yùn)行到了鏈的盡頭 3. 命令拋出異常 當(dāng)鏈完全處理完一個(gè)過程后,命令就返回true。這是責(zé)任鏈模式(Chain of Responsibility)的基本概念。處理從一個(gè)命令傳遞到另一個(gè)命令,直到某個(gè)命令(Command)處理了這個(gè)命令。如果在到達(dá)命令序列盡頭時(shí)仍沒有處理返回true,也假設(shè)鏈已經(jīng)正常結(jié)束。 當(dāng)有命令拋出錯(cuò)誤時(shí)鏈就會(huì)非正常結(jié)束。在Commons Chain中,如果有命令拋出錯(cuò)誤,鏈的執(zhí)行就會(huì)中斷。不論是運(yùn)行時(shí)錯(cuò)誤(runtime exception)還是應(yīng)用錯(cuò)誤(application exception),都會(huì)拋出給鏈的調(diào)用者。但是許多應(yīng)用都需要對在命令之外定義的錯(cuò)誤做明確的處理。Commons Chain提供了Filter接口來滿足這個(gè)要求。Filter繼承了Command,添加了一個(gè)名為postprocess的方法。 public boolean postprocess(Context context, Exception exception); 只要Filter的execute方法被調(diào)用,不論鏈的執(zhí)行過程中是否拋出錯(cuò)誤,Commons Chain都將保證Filter的postprocess方法被調(diào)用。和servlet的過濾器(filter)相同,Commons Chain的Filter按它們在鏈中的順序依次執(zhí)行。同樣,F(xiàn)ilter的postprocess方法按倒序執(zhí)行。你可以使用這個(gè)特性實(shí)現(xiàn)自己的錯(cuò)誤處理。下面是一個(gè)用于處理我們例子中的錯(cuò)誤的Filter: 清單12 package com.jadecove.chain.sample; Filter在配置文件中的定義就和普通的命令(Command)定義相同: 清單13 <chain name="sell-vehicle"> Filter的execute方法按定義的序列調(diào)用。然而,它的postprocess方法將在鏈執(zhí)行完畢或拋出錯(cuò)誤后執(zhí)行。當(dāng)一個(gè)錯(cuò)誤被拋出時(shí),postprocess方法處理完后會(huì)返回true,表示錯(cuò)誤處理已經(jīng)完成。鏈的執(zhí)行并不會(huì)就此結(jié)束,但是本質(zhì)上來說這個(gè)錯(cuò)誤被捕捉而且不會(huì)再向外拋出。如果postprocess方法返回false,那錯(cuò)誤會(huì)繼續(xù)向外拋出,然后鏈就會(huì)非正常結(jié)束。 讓我們假設(shè)ArrangeFinancing因?yàn)橛脩粜庞每〒p壞拋出錯(cuò)誤。SellVehicleExceptionHandler就能捕捉到這個(gè)錯(cuò)誤,程序輸出如下: Filter.execute() called. Get customer info Test drive the vehicle Negotiate sale Exception Bad credit occurred. 結(jié)合了過濾器(filter)和子鏈技術(shù)后,你就可以造型很復(fù)雜的工作流程。 Commons Chain是一個(gè)很有前途的框架,現(xiàn)在仍在開發(fā),新的功能被頻繁地添加到其中。在下一篇關(guān)于Commons Chain的文章中,我們將研究Struts 1.3中是如何使用Commons Chain的。 Struts 1.3中用完全使用Commons Chain的類替換了原來的處理HTTP請求的類。如果你以前自己定制過Struts的請求處理(request processor),你將發(fā)現(xiàn)處理這個(gè)問題時(shí)Commons Chain為程序帶來了很好的靈活性。 |
|
|