日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据校验器架构模式组

發(fā)布時間:2023/12/9 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据校验器架构模式组 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

劉 岳林 (yuelin_liu@msn.com), 軟件工程師
2007 年 1 月 15 日

本文闡述軟件架構(gòu)與設(shè)計模式,它為架構(gòu)師和開發(fā)人員提供了一組關(guān)于數(shù)據(jù)校驗的架構(gòu)模式(隔離校驗器,可組裝校驗器,動態(tài)策略校驗器,動態(tài)注冊校驗器等),數(shù)據(jù)校驗是任何類型的開發(fā)中都不可或缺的環(huán)節(jié),如果沒有統(tǒng)一的架構(gòu),可能校驗代碼會遍布整個應(yīng)用,如何將數(shù)據(jù)校驗與應(yīng)用邏輯解耦,如何適應(yīng)各種粒度的數(shù)據(jù)和各種復(fù)雜程度業(yè)務(wù)規(guī)則,正是本文要探討的。

在我們各種類型的應(yīng)用開發(fā)中有一個必不可少的環(huán)節(jié)-數(shù)據(jù)校驗,無論是大型企業(yè)應(yīng)用,還是一個簡單的程序。如果沒有統(tǒng)一的架構(gòu),可能校驗代碼會遍布整個應(yīng)用,一旦校驗規(guī)則改變就需要修改多處代碼,這是一種不好的設(shè)計,因為數(shù)據(jù)校驗與應(yīng)用邏輯耦合得太緊。數(shù)據(jù)校驗不外乎語法校驗和語義校驗兩類,本文描述了一組架構(gòu)上的模式來對這兩類需求提供解決方案。該模式組按照待校驗數(shù)據(jù)的粒度大小和業(yè)務(wù)規(guī)則的復(fù)雜程度分成多種類型:隔離校驗器,可組裝校驗器,動態(tài)策略校驗器,動態(tài)注冊校驗器等。大家可以針對自己的應(yīng)用選擇合適的架構(gòu)。應(yīng)用這組模式還可以獲得一個好處,如果需要的話,我們可以把數(shù)據(jù)校驗器當(dāng)作一個橫切關(guān)注點(Crosscut concern),應(yīng)用 AOP(Aspect of Programming)技術(shù),這樣可以徹底分離出數(shù)據(jù)校驗邏輯代碼。

問題引出

讓我們從幾個應(yīng)用場景(user scenario)開始吧,第一個場景是網(wǎng)站上的注冊用戶,注冊時需要填寫很多數(shù)據(jù),這些數(shù)據(jù)都需要校驗后才能寫進數(shù)據(jù)庫,比如用戶名,校驗規(guī)則可能是:用戶名由 a~z 的英文字母(不區(qū)分大小寫)、0~9 的數(shù)字、點、減號或下劃線組成,長度為 3~18 個字符。這種關(guān)于數(shù)據(jù)的結(jié)構(gòu)正確性方面的校驗我們稱之為語法校驗。而身份證號碼這種數(shù)據(jù),它需要根據(jù)出生日期校驗身份證號碼的正確性,不僅僅是填夠了 16 或 19 位數(shù)字就行。這種關(guān)于數(shù)據(jù)的內(nèi)容正確性方面的校驗稱之為語義校驗。一般情況下語法和語義方面的校驗是在一塊處理的,比如身份證號碼,必然也需要校驗數(shù)據(jù)是否全是數(shù)字和必須是 16 或 19 位,這是語法校驗,同時它需要和出生日期相符,這又是語義校驗。從架構(gòu)的角度而言,這種情況下區(qū)分語法和語義的意義不太大,因為沒必要把它分成兩個步驟用兩個方法來處理。但是有些應(yīng)用,比如數(shù)據(jù)是一段 XML 的文本串,首先需要校驗 XML 字符串的結(jié)構(gòu)-語法是否符合相應(yīng)的 schema,然后再校驗其中某個元素的內(nèi)容-語義的正確性,這可能就需要分開來處理比較合適,因為語法校驗是業(yè)務(wù)無關(guān)的,而后者的語義校驗是業(yè)務(wù)相關(guān)的,業(yè)務(wù)相關(guān)就意味著一旦業(yè)務(wù)規(guī)則改變,校驗規(guī)則就可能改變,所以這種情況最好將語義校驗分離出來。

第二個應(yīng)用場景是一個 MDA(Model Driven Architecture)工具開發(fā)的例子,我們都用過大名鼎鼎的 Rational Rose 或 Microsoft Visio。這些工具都提供從 UML 模型生成代碼的功能,這就是 MDA,它們將 UML 模型映射成模型的元數(shù)據(jù)(meta-data)(稱之為元模型 meta-model),然后從元模型可以轉(zhuǎn)換成各種支持語言的代碼,如 Java, C++。當(dāng)我們在視圖上畫一個 UML元素(如類 Class),然后為其定義了某種 Stereotype 來標(biāo)識他的業(yè)務(wù)語義,比如數(shù)據(jù)庫的表 Table,或者自定義的一個用于表示 Web Service 的 Service 元素。接下來我們要將該元素生成相應(yīng)的代碼,這時當(dāng)你選定元素時,運行時系統(tǒng)并不知道該元素是普通的 Class,還是 Table,因為在運行時環(huán)境中都是 UML 的 Class 實例對象,這就需要我們提供校驗邏輯來處理了,處理 Class 的校驗邏輯和 Table 的校驗邏輯自然不應(yīng)該放在一起,更何況如果是自定義的擴展元素,根本不可能把校驗代碼寫到已有系統(tǒng)里去。這就需要我們提供一個統(tǒng)一的校驗器接口,不同的校驗邏輯封裝在單獨的類中。進一步,我們需要對這些獨立的校驗器進行集中組裝和管理,因為我們不必每次都去實例化這些工具類,實例化后將它們緩存起來就可以了。

第三個應(yīng)用場景是一個銀行并購的案例,假如銀行 A 并購了銀行 B,兩家銀行都有各自已有的電子銀行應(yīng)用,并購后要將兩家應(yīng)用整合成一個統(tǒng)一的應(yīng)用,其中有一個余額查詢業(yè)務(wù),在進行具體的查詢操作事務(wù)之前,需要校驗用戶輸入的帳號account,兩家銀行已有的帳號各有不同的創(chuàng)建規(guī)則,比如銀行 A 是 16 位數(shù)字作為帳戶,首 4 位是銀行代號,第二個 4 位是地區(qū)代號,第三個 4 位是網(wǎng)點代號,尾4位是用戶編號。而銀行 B 則是 19 位數(shù)字作為帳戶,各個區(qū)段的含義也和銀行 A 不一樣,這就要求用戶填寫一個帳戶的時候,后臺必須對應(yīng)兩套數(shù)據(jù)校驗規(guī)則,而且應(yīng)用需要根據(jù)一定的規(guī)則來選擇銀行 A 的校驗策略或銀行 B 的校驗策略。而且更復(fù)雜的情況是,銀行的帳戶還可能是升位后的(比如從 12 位升到 16 位),這樣必須同時兼顧新舊帳戶,也就是說有多套校驗規(guī)則來處理,我們的數(shù)據(jù)校驗器需要支持業(yè)務(wù)規(guī)則的動態(tài)切換。這里面可能有一個有爭議的地方,校驗帳號時需要有具體的業(yè)務(wù)規(guī)則支持,那么這算不算是業(yè)務(wù)邏輯呢,當(dāng)然這個校驗邏輯并不那么純粹,軟件設(shè)計并不是個黑白的二元世界,各種層次的對象混合在一起很正常,我們也不大可能什么東西都能做個分水嶺把它們隔離開來。另外,這里的校驗邏輯還是和銀行應(yīng)用別的業(yè)務(wù)邏輯不大一樣,比如轉(zhuǎn)帳交易,這個動作的觸發(fā)是一定要在一個高安全可靠的事務(wù)中執(zhí)行的,而我們的校驗帳號過程可能不需要運行在事務(wù)中,或者只運行在低安全可靠級別的事務(wù)中即可。這是有本質(zhì)區(qū)別的,所以把這種摻有業(yè)務(wù)規(guī)則的校驗劃分到校驗邏輯里而不是業(yè)務(wù)邏輯中是有理由的。




回頁首


隔離校驗器

針對上述第一類應(yīng)用場景,我們只需要把數(shù)據(jù)校驗邏輯從其他業(yè)務(wù)邏輯中剝離出來,將校驗邏輯委任到一個單獨的校驗類中去。把校驗職責(zé)分離出來后,第一個好處是:一旦我們需要更改校驗邏輯,只要修改校驗類代碼即可,而不用修改其他任何業(yè)務(wù)邏輯類。第二個好處是:可以集中管理控制所有的數(shù)據(jù)校驗邏輯,提高了代碼的內(nèi)聚性,而且讓代碼簡潔、清晰。當(dāng)然這里說的所有數(shù)據(jù)集中控制不一定就是全放在一個類中,如果有必要,也可以將數(shù)據(jù)按照不同的類型分組,每一個組封裝在一個校驗類中。第三個好處是可重用性高,校驗邏輯封裝成了一個工具類,自然可重用性大大提高。

在設(shè)計這個隔離校驗器類時還有一些需要權(quán)衡的地方,在設(shè)計某一個數(shù)據(jù)的校驗方法時,比如用戶名的校驗,如果數(shù)據(jù)出錯了,簡單的情況下,我們只需返回一個 boolean 值,告訴用戶數(shù)據(jù)有誤。而如果是身份證號碼這類數(shù)據(jù)出錯了,可能就需要提供更細(xì)粒度的錯誤類型給用戶,告訴用戶是與出生日期不符還是位數(shù)不夠。對這種錯誤種類較多的情況,我們可以返回錯誤代號(如 int 值)來區(qū)別各種錯誤,這是非面向?qū)ο笳Z言的一種做法,在面向?qū)ο笾形覀兛梢杂靡粋€異常 Exception 來返回錯誤類型,這比返回錯誤代號更好,因為錯誤代號需要解析成具體的錯誤信息,這個解析工作還得由校驗器類的API使用者來調(diào),這個使用者是其它的業(yè)務(wù)邏輯類,這就是說業(yè)務(wù)邏輯類還是耦合了數(shù)據(jù)校驗錯誤處理邏輯,顯然不如用異常處理來的徹底。

代碼如下:



清單1: UserInfoValidator.java
public abstract class UserInfoValidator {public static boolean validateUserID(String uid) {boolean isValid = false;//校驗規(guī)則return isValid;}public static boolean validteEmail(String email) {boolean isValid = false;//校驗規(guī)則return isValid;}public static void validateSSN(SSNDataObject ssn)throws DataValidationException {if (ssn == null)throw new DataValidationException("No data found.");String idCard = ssn.getIdCard();if ((idCard == null) || (idCard.equals("")))throw new DataValidationException("No id.card data found.");if (!((idCard.length() == 15) || (idCard.length() == 18)))throw new DataValidationException("ID.card length must be 15 or 18.");Date birthDay = ssn.getBirthDay();if (birthDay == null)throw new DataValidationException("No birthday data found.");int sex = ssn.getSex();if (sex == 0)throw new DataValidationException("No sex data found.");//生日校驗規(guī)則// if (...)// throw new DataValidationException("ID.card didn't match birthday.");int idSex = Integer.parseInt(idCard.substring(idCard.length() - 1));if (idSex % sex != 0)throw new DataValidationException("ID.card didn't match sex.");}}

從上面代碼可以看出,我們用了靜態(tài) static 方法,因為我們這是個工具類,沒有什么狀態(tài)需要存儲,所以不需要實例化類。而且調(diào)用校驗方法會很頻繁,用靜態(tài)方法可以提升性能。

另外還有一點值得一提,我們封裝了一個身份證數(shù)據(jù)類,里面包含了三個屬性:身份證號,出生日期,性別。驗證身份證號需要出生日期和性別奇偶碼這一點是沒有異議的,但為什么不用三個單獨的參數(shù)呢,這里的封裝為以后提供了更大的靈活性,比如將來我們打算將身份證驗證邏輯做得更精細(xì),需要判斷出生地區(qū)的代碼是否和身份證的頭幾位一致,這可能就需要四個參數(shù)了,或者我們的出生日期需要換一個類(Date->Calendar)來表示,顯然我們只需要修改身份證數(shù)據(jù)封裝類,而不用修改調(diào)用接口。




回頁首


可組裝校驗器

針對第二類場景,我們對每一個數(shù)據(jù)類提供一個獨立的校驗規(guī)則類,因為這個數(shù)據(jù)類本身已經(jīng)包含了語法和語義邏輯。語法邏輯是與數(shù)據(jù)結(jié)構(gòu)相關(guān)的,在我們的示例中,是判斷對象是否是UML的Class實例。而語義邏輯是與業(yè)務(wù)規(guī)則相關(guān)的,每一個數(shù)據(jù)類關(guān)聯(lián)的業(yè)務(wù)規(guī)則不盡相同,可能來自不同領(lǐng)域,或不同的業(yè)務(wù)組件或系統(tǒng);另外由于業(yè)務(wù)規(guī)則的易變性較強,可擴展性和可配置性要求也較高,所以有必要為每一個數(shù)據(jù)類設(shè)置專屬的校驗類。這里我們將每一個校驗類稱作一條校驗規(guī)則(Rule)。校驗規(guī)則類的接口和實現(xiàn)代碼如下:



清單2: IVRule.java
public interface IVRule {/*** validate value by domain rule.*/public boolean isValid(Object value);/*** validate value by domain rule.*/public void validate(Object value) throws DataValidationException;}



清單3: ServiceVRule.java
public class ServiceVRule implements IVRule {private static String STEREOTYPE_NAME = "Service";………..public void validate(Object value) throws DataValidationException {if (value instanceof Class) {Stereotype st = ((Class) value).getStereotype();String name = st.getName();if (STEREOTYPE_NAME.equals(name)) {String wsdl = (String) st.getProperty("WSDL");if ((wsdl == null) || (wsdl.equals("")))throw new DataValidationException("No WSDL file is defined.");} elsethrow new DataValidationException("It is not a Service Object.");} elsethrow new DataValidationException("It is not a UML Class model.");}}

在這里,我們還是提供了兩種校驗結(jié)果返回機制,boolean 值和拋出異常,在具體應(yīng)用時大家可以選擇一個即可。這里的校驗規(guī)則實現(xiàn)是關(guān)于 Web Service 的,它的語法校驗是檢查數(shù)據(jù)是否是 UML 的 Class 對象,而語義校驗是檢查 Stereotype 是否是 Service,并檢查 Stereotype 中是否含有 WSDL(Web service description language) 屬性。

接下來,怎么應(yīng)用這些校驗規(guī)則 rule 呢?一個系統(tǒng)中會有很多校驗規(guī)則類,那么就需要一個管理機制來管理這些校驗類,最簡單的我們只需定義一個方法 validate(Object value, IVRule rule),提供一層簡單的封裝,它可以起到一個代理的作用,比如,應(yīng)用 Proxy 模式,我們可以對校驗規(guī)則本身做一些安全性認(rèn)證方面的工作,然后才決定是否可以用該校驗規(guī)則。而更完善的管理機制是提供一個更靈活的環(huán)境,讓用戶可以動態(tài)組裝,改變,查找校驗規(guī)則類。基本思路是:我們將校驗規(guī)則類當(dāng)作一種可重用資源,提供一個組裝工廠環(huán)境,用戶可以將校驗規(guī)則 rule 注冊到工廠里,工廠會實例化和緩存這些類,并提供查找服務(wù);然后提供一個校驗器來從工廠里查找出相應(yīng)的校驗規(guī)則 rule 類,為用戶提供校驗服務(wù)。這里面用到了 Factory,Flyweight,Registry 模式。(本文引用的模式請參考相關(guān)模式)

第一步,我們設(shè)計一個組裝工廠類,它提供實例化、緩存、和查找服務(wù),很顯然,實例化類是一個 Factory 模式的基本職責(zé),將實例緩存 cache 是一個 Flyweight 模式的基本職責(zé),查找服務(wù)可以很簡單,在我們的例子中就是從一個 Map 中取出校驗規(guī)則 rule 實例,也可以復(fù)雜化,比如我們的校驗規(guī)則類是一個遠(yuǎn)程資源,或者是實例化這個類需要用到其它的遠(yuǎn)程資源,如數(shù)據(jù)庫,那這個查找功能實現(xiàn)起來可能就復(fù)雜些,可以通過 JNDI 來查找,也可以將遠(yuǎn)程資源暴露成服務(wù)(Web Service)并注冊到 UDDI (Universal Discover Description and Integration),然后從 UDDI 中查找服務(wù)。從這里我們可以看到這個組裝工廠類具備管理校驗規(guī)則 rule 類的整個生命周期的職責(zé),這為校驗器應(yīng)用提供了很大的靈活性和可擴展性,假如我們今后需要實現(xiàn)一個實例池或資源池 Pool 來管理這些校驗規(guī)則實例,那么只需要將組裝工廠類的功能稍作修改和擴展就可,而不必觸及校驗器應(yīng)用的其他類,因為我們已經(jīng)將實例的管理邏輯從整個校驗器應(yīng)用中剝離出來,管理職責(zé)的變化只局限在組裝工廠類內(nèi),對別的類是封閉的,這正體現(xiàn)面向?qū)ο蟮幕驹O(shè)計原則之一 Open-Close 原則(對修改封閉,對擴展開放)。組裝工廠類的代碼如下:



清單4: VRuleAssemblerFactory.java
public class VRuleAssemblerFactory {private static Map rules = new HashMap();/*** 查找校驗規(guī)則Rule。*/public static IVRule lookupVRule(String ruleHandler) {if (ruleHandler == null)return null;IVRule rule = null;if (rules.containsKey(ruleHandler))rule = (IVRule) rules.get(ruleHandler);return rule;}/*** 注冊/加入一個校驗規(guī)則Rule.*/public static void addVRule(String ruleHandler, Class ruleClass) {if ((ruleHandler != null) && (ruleClass != null)) {try {rules.put(ruleHandler, ruleClass.newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}/*** 批量載入校驗規(guī)則Rule,一般在應(yīng)用系統(tǒng)初始化時調(diào)用。*/public static void assembleVRules() {addVRule("Table", TableVRule.class);addVRule("Service", ServiceVRule.class);}}

從上面的代碼可以看到,我們用一個 Map 作為緩存庫,注冊一個校驗規(guī)則時以字符串作為關(guān)鍵字,當(dāng)然也可以用別的自定義類型,在查詢時利用了 Map 的查找功能很簡單高效地實現(xiàn)了查找功能,另外我們還定義了一個批量載入校驗規(guī)則的方法,這個功能是為了用戶使用方便,在應(yīng)用系統(tǒng)初始化時執(zhí)行一次,而且可以透明地載入校驗規(guī)則,在本例中只是硬編碼了這些校驗規(guī)則類,需要的話我們可以從別的元數(shù)據(jù)文件中(XML 或 CSV 文件)導(dǎo)入。

還需注意一點的是,這個組裝工廠是一個全局類,在這里是以靜態(tài)static方式實現(xiàn)的,當(dāng)然也可以以Singleton方式來實現(xiàn),還可以以線程安全ThreadLocal的方式來實現(xiàn)。

第二步,我們設(shè)計校驗器類,校驗器類應(yīng)該作為整個校驗器應(yīng)用的 Fa?ade,用戶需要校驗數(shù)據(jù)時只需和它打交道,這就很好的把校驗規(guī)則類隱藏了起來,因此校驗器的職責(zé)有查找相應(yīng)的校驗規(guī)則類和執(zhí)行校驗。校驗器類的接口和實現(xiàn)代碼如下:



清單5: IValidator.java
public interface IValidator {/*** 通過關(guān)鍵字ruleHandler查詢校驗規(guī)則來校驗數(shù)據(jù)。*/public boolean isValid(Object value, String ruleHandler);/***通過關(guān)鍵字ruleHandler查詢校驗規(guī)則來校驗數(shù)據(jù)。返回異常。*/public void validate(Object value, String ruleHandler)throws DataValidationException;/*** 直接指定校驗規(guī)則類來校驗數(shù)據(jù)。*/public boolean isValid(Object value, IVRule rule);/***直接指定校驗規(guī)則類來校驗數(shù)據(jù),返回異常。*/public void validate(Object value, IVRule rule) throws Exception;}



清單6: AssemblyValidator.java
public class AssemblyValidator implements IValidator {private static AssemblyValidator instance = null;private AssemblyValidator() { }public static synchronized AssemblyValidator getInstance() {if (instance == null)instance = new AssemblyValidator();return instance;}public boolean isValid(Object value, String ruleHandler) {boolean valid = false;IVRule rule = VRuleAssemblerFactory.lookupVRule(ruleHandler);if (rule != null)valid = rule.isValid(value);return valid;}public void validate(Object value, String ruleHandler)throws DataValidationException {IVRule rule = VRuleAssemblerFactory.lookupVRule(ruleHandler);if (rule != null)rule.validate(value);}public boolean isValid(Object value, IVRule rule) {boolean valid = false;if (rule != null)valid = rule.isValid(value);return valid;}public void validate(Object value, IVRule rule) throws Exception {if (rule != null)rule.validate(value);}}

從上面代碼可以看到,我們使用了Singleton模式,因為沒必要每次都實例化校驗器類。另外我們在這里提供了兩套返回機制,還有兩套取校驗規(guī)則的方式,這都可以根據(jù)實際應(yīng)用作出取舍。

可組裝校驗器的架構(gòu)圖如下:



圖 1:可組裝校驗器的架構(gòu)圖
?



回頁首


動態(tài)策略校驗器

針對第三類場景,我們必須支持一種數(shù)據(jù)對應(yīng)有多套業(yè)務(wù)校驗規(guī)則,我們可以把每種業(yè)務(wù)規(guī)則都建模成一個校驗規(guī)則類,但這樣做靈活性和可擴展性就很差了。如果我們對一種數(shù)據(jù)只用一個校驗規(guī)則類,而將多套業(yè)務(wù)規(guī)則建模成多種策略,在校驗規(guī)則類中應(yīng)用這些策略,這樣做好處在于:一可以對用戶隱藏業(yè)務(wù)規(guī)則,二是將來對策略進行修改或增加新的策略都不需要更改用戶的調(diào)用接口,三是我們可以在運行時動態(tài)地改變業(yè)務(wù)規(guī)則-策略。要實現(xiàn)上述需求,我們只需要在可組裝校驗器架構(gòu)的基礎(chǔ)上,對校驗規(guī)則 rule 類引入 Strategy 模式。首先我們設(shè)計策略類,對于銀行并購這個例子,對帳號的校驗可以分成以下幾步:校驗銀行代號,地區(qū)代號,網(wǎng)點代號,用戶代號,因此在這里我們先根據(jù)各個銀行的業(yè)務(wù)策略對帳號進行分割,比如 19 位的帳號分成 4 位銀行代號,4 位地區(qū)代號,4 位網(wǎng)點代號,和 7 位用戶代號,然后再執(zhí)行上述幾步校驗。業(yè)務(wù)策略類的接口和實現(xiàn)代碼如下:



清單7:IAccountStrategy.java
public interface IAccountStrategy {/*** 獲得字符串分割規(guī)則,比如19位的帳號分成{4,4,4,7}。*/public int[] getSeperatedNumbers();/*** 校驗銀行代號。*/public boolean validateBankCode(String bankCode);/*** 校驗地區(qū)代號。*/public boolean validateDistrictCode(String districtCode);/*校驗網(wǎng)點代號。*/public boolean validateSiteCode(String siteCode);/*校驗用戶代號。*/public boolean validateUserCode(String userCode);}



清單8:BankAAccountStrategy.java
public class BankAAccountStrategy implements IAccountStrategy {private static int[] seperatedNumbers = { 4, 4, 4, 4 };public int[] getSeperatedNumbers() {return seperatedNumbers;}public boolean validateBankCode(String bankCode) {// query bank id from local database or meta-data file.String bankID = "9880";if (bankID.equals(bankCode))return true;return false;}public boolean validateDistrictCode(String districtCode) {if ((districtCode != null)&& (districtCode.length() == seperatedNumbers[1]))if (districtCode.startsWith("8"))return true;return false;}public boolean validateSiteCode(String siteCode) {if ((siteCode != null) && (siteCode.length() == seperatedNumbers[2]))if (siteCode.startsWith("1"))return true;return false;}public boolean validateUserCode(String userCode) {if ((userCode != null) && (userCode.length() == seperatedNumbers[3]))return true;return false;}}

接下來我們設(shè)計校驗規(guī)則類,該類主要有選擇策略和使用策略來校驗兩種職責(zé),代碼如下:



清單9:
public class AccountVRule implements IVRule {private static Map strategies = new HashMap();static { //注冊策略類strategies.put(new Integer(16), new BankAAccountStrategy());strategies.put(new Integer(19), new BankBAccountStrategy());}public void validate(Object value) throws DataValidationException {if (value == null)throw new DataValidationException("Account can't be empty.");if (!(value instanceof String))throw new DataValidationException("Can't cast Object to String.");String val = (String) value;if (strategies.containsKey(new Integer(val.length()))) {IAccountStrategy strat = (IAccountStrategy) strategies.get(new Integer(val.length()));int[] sepNum = strat.getSeperatedNumbers();//validate bank codeString bankCode = val.substring(0, sepNum[0]);if (!strat.validateBankCode(bankCode))throw new DataValidationException("Bank code " + bankCode+ " doesn't match account rule");//validate district code.String disCode = val.substring(sepNum[0], sepNum[0] + sepNum[1]);if (!strat.validateDistrictCode(disCode))throw new DataValidationException("District code " + disCode+ " doesn't match account rule");//validate site codeString siteCode = val.substring(sepNum[0] + sepNum[1], sepNum[0]+ sepNum[1] + sepNum[2]);if (!strat.validateSiteCode(siteCode))throw new DataValidationException("Site code " + siteCode+ " doesn't match account rule");//validate user codeString userCode = val.substring(val.length() - sepNum[3]);if (!strat.validateUserCode(userCode))throw new DataValidationException("User code " + userCode+ " doesn't match account rule");} elsethrow new DataValidationException("The length of input account NO "+ val.length() + " doesn't match account rule.");}}

在這里我們再一次用到了 Registry 模式,可以看到這個校驗規(guī)則類具有管理和緩存策略類的職責(zé)。當(dāng)然我們還可以在該類中增加一個方法 regiesterStrategy() 用來在運行時動態(tài)地增加業(yè)務(wù)規(guī)則策略,但目前我們的應(yīng)用沒有這么復(fù)雜的需求,就算將來有也很容易重構(gòu)目前的架構(gòu),所以這個設(shè)計活動應(yīng)該點到為止。這也是設(shè)計中的一個權(quán)衡點,設(shè)計是沒有絕對完美的,人們在追逐絕對完美設(shè)計的過程中經(jīng)常把對未來的種種揣測當(dāng)作真正的需求,結(jié)果只能是危及整個設(shè)計,導(dǎo)致代碼臃腫,難以維護,僵化,靈活性差。適度的設(shè)計才是完美的。

動態(tài)策略校驗器的架構(gòu)圖如下:



圖 2:可組裝校驗器的架構(gòu)圖

在本例中,我們并不是從整合遺留資產(chǎn)的角度出發(fā)的,在實際的例子中,銀行A和銀行B可能都已存在各自的校驗類,這些類的接口不會是一致的,而且返回類型可能是boolean也可能是異常,甚至銀行A是Java應(yīng)用而銀行B是C應(yīng)用,這樣的話我們必須將這些遺留應(yīng)用中已有的校驗類適配成現(xiàn)在的接口,這里可以對具體的Strategy實現(xiàn)類應(yīng)用Adapter模式。




回頁首


模式與價值觀

模式的三要素-問題,語境,解決方案我們在前面已經(jīng)論述過了,每個模式都有它自己獨特的價值觀,那么這組架構(gòu)模式給我們帶來了什么?

首先,它將校驗邏輯從應(yīng)用邏輯中解耦出來,使得應(yīng)用和校驗器可以獨立變化。第二,它促進了代碼重用,校驗器可以用到任何應(yīng)用邏輯中去,不必局限于一處。如果需要的話,我們甚至可以將整個校驗器當(dāng)作一個橫切關(guān)注點 (Crosscut concern),應(yīng)用 AOP(Aspect of Programming)技術(shù),將待校驗數(shù)據(jù)當(dāng)作 Pointcut,這樣在應(yīng)用的代碼中會看不到任何校驗代碼的痕跡,這就徹底分離出了數(shù)據(jù)校驗邏輯代碼。第三,從應(yīng)用場合來看,隔離校驗器主要用在那些數(shù)據(jù)類型簡單而且校驗規(guī)則簡單的數(shù)據(jù)校驗中,可組裝校驗器用在那些數(shù)據(jù)類型復(fù)雜或校驗規(guī)則復(fù)雜、多變的數(shù)據(jù)校驗中,而動態(tài)策略校驗器則用在同一個數(shù)據(jù)的校驗就有多種校驗規(guī)則策略的數(shù)據(jù)校驗中。可以看到,這幾種模式是根據(jù)待校驗數(shù)據(jù)的粒度大小和業(yè)務(wù)規(guī)則的復(fù)雜程度來劃分的。

接下來,我們研究一下模式的變體。可組裝校驗器是這組模式的核心,它有很多變體,其實動態(tài)策略校驗器就是它的一種變體,其他變體還有復(fù)合規(guī)則檢驗器,鏈?zhǔn)綑z驗器,動態(tài)注冊檢驗器等。比如在 XML 校驗器 SAX 的實現(xiàn)中,用戶可以動態(tài)地插入校驗 handle,或者我們需要對一個數(shù)據(jù)依次執(zhí)行多套校驗規(guī)則,而不像之前一次只有一個校驗規(guī)則會被執(zhí)行。對于這種需求,我們有三種方案可選,第一種是復(fù)合規(guī)則檢驗器,利用Composite 模式來實現(xiàn)校驗規(guī)則 IVRule 接口,復(fù)合的校驗規(guī)則類中包含一組簡單的校驗規(guī)則類 VRule,當(dāng)調(diào)用復(fù)合類的 validate() 方法時,復(fù)合類會依次調(diào)用所有的簡單校驗規(guī)則類。第二種是鏈?zhǔn)叫r炂?#xff0c;利用 Chain of responsibility 模式來實現(xiàn)校驗規(guī)則 IVRule 接口,前一個校驗規(guī)則類執(zhí)行校驗后傳遞到下一個校驗規(guī)則類,一層層按固定順序傳遞下去,每一個校驗規(guī)則類關(guān)注的校驗點不一樣,這適合于順序固定的情況。第三種是動態(tài)注冊校驗器,利用 Registry 模式,將校驗規(guī)則類 VRule 動態(tài)地注冊到校驗器類 Validator 中去,比如注冊到一個 List 或 Map 中,在校驗器類的 validate() 方法中可以按某種算法來實現(xiàn)調(diào)用校驗規(guī)則類的順序或更復(fù)雜的調(diào)用邏輯。很顯然動態(tài)注冊校驗器很靈活,可擴展性也很強,但同時對校驗器使用者來說,它復(fù)雜了,校驗規(guī)則也不透明了。所以并非靈活性和可擴展性越強就越好,一切應(yīng)該取決于需求,如果你一次只有一個校驗規(guī)則執(zhí)行就沒必要再引入復(fù)雜性了。

另外還可以對校驗方法的返回類型作一下擴展,boolean 值和異常作為返回一般來說足夠了,但如果我們的返回結(jié)果比較復(fù)雜,比如前面講到的一個數(shù)據(jù)需要執(zhí)行多個校驗規(guī)則的情況,返回的結(jié)果可能需要將多個校驗規(guī)則的返回結(jié)果匯總,也可能需要更細(xì)級別的結(jié)果。這就需要一種工業(yè)級的返回機制,在 Eclipse 中就有這么一個返回類型,稱為狀況對象 (Status object),它可以對返回類型進行分級:OK、 Warning、 Error,甚至還封裝了更低級的異常。而且針對返回狀態(tài)比較復(fù)雜的情況,還應(yīng)用了 Composite 模式實現(xiàn)了一個 MultiStatus 類來組合多個錯誤狀態(tài)。這在校驗結(jié)果不僅僅是 true 或 false 兩種狀態(tài)的場合下非常有用,而且可以記錄跟蹤校驗規(guī)則信息。不過這是一種重量級的返回機制。下面展示了狀況對象的接口代碼:



//清單 10:IStatus.java
public interface IStatus {public static final int OK = 0;public static final int INFO = 0x01;public static final int WARNING = 0x02;public static final int ERROR = 0x04;//返回更低級的異常。public Throwable getException();//返回消息,如錯誤信息。public String getMessage();//返回狀況類型,OK,INFO, WARNING,ERRORpublic int getSeverity();public boolean isOK();//是否復(fù)合狀況。public boolean isMultiStatus();//如果是復(fù)合狀況,返回子狀況類。public IStatus[] getChildren();




回頁首


結(jié)束語

這組架構(gòu)模式不局限于某種語言、應(yīng)用,可以應(yīng)用到任何場合。如果我們將數(shù)據(jù)校驗當(dāng)作一項業(yè)務(wù)操作的話,可以將它擴展到其他領(lǐng)域。模式可以促進好的架構(gòu),也可能導(dǎo)致萬劫不復(fù),關(guān)鍵取決于設(shè)計者的把握。所以,我們在選擇模式的時候,一定要考量模式的何種特性對你最有價值,模式所提供的價值觀與您的需求期望是否吻合。




回頁首


相關(guān)模式

  • Factory Method:工廠模式用于產(chǎn)生類的實例。
  • Singleton:單例模式用于保證一個類只產(chǎn)生一個實例。
  • Flyweight:享元模式用于緩存和維護一組實例。
  • Composite:復(fù)合模式用于組合一組類,而這些類和復(fù)合類具有同一接口。
  • Facade:門面模式用于為客戶提供統(tǒng)一的外觀,隱藏復(fù)雜的實現(xiàn)細(xì)節(jié)。
  • Proxy:代理模式用于代理一個對象,控制其他對象對該對象的訪問。
  • Strategy:策略模式用于提供一組可互換的算法/策略,它們遵循同一接口。
  • Adapter:適配器模式用于將類的接口適配為另一種接口。
  • Chain of responsibility:職責(zé)鏈模式用于為鏈?zhǔn)絺鬟f處理請求。

以上模式來自《GOF設(shè)計模式》。

  • Registry:注冊器模式用于注冊和管理一個或多個對象。(來自《企業(yè)架構(gòu)模式》)



回頁首


參考文獻

  • 《Design Patterns:Elements of Reusable Object-Oriented software》-《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》 ERICH GAMMA RICHARD HELM RALPH JOHNSON JOHN VLISSIDES (GOF)。
  • 《Patterns of Enterprise Application Architecture》-《企業(yè)架構(gòu)模式》 Martin Fowler。




回頁首


下載

名字大小下載方法
DataValidator.jar39 KHTTP
關(guān)于下載方法的信息Get Adobe? Reader?


參考資料

  • 界面組裝器模式(developerWorks 中國,2006 年 6 月):本文作者提出的另外一種界面設(shè)計中的架構(gòu)模式-界面組裝器模式,它致力于分解界面,將界面和組裝行為解耦,將界面邏輯處理與領(lǐng)域邏輯處理解耦。

  • Java 技術(shù)專區(qū):數(shù)百篇有關(guān) Java 編程各方面的文章。


關(guān)于作者

?

劉岳林,IBM 中國軟件實驗室成員,在 OOAD, RUP, XP, Architecture/Design Pattern 方面有著豐富的項目實踐經(jīng)驗,對架構(gòu)設(shè)計,項目、過程管理有過深入的研究,技術(shù)方向為 J2EE, SOA, Grid, AOP ,PKI。你可以通過 yuelin_liu@msn.com 聯(lián)系他。

總結(jié)

以上是生活随笔為你收集整理的数据校验器架构模式组的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。