数据校验器架构模式组
劉 岳林 (yuelin_liu@msn.com), 軟件工程師
2007 年 1 月 15 日
本文闡述軟件架構與設計模式,它為架構師和開發人員提供了一組關于數據校驗的架構模式(隔離校驗器,可組裝校驗器,動態策略校驗器,動態注冊校驗器等),數據校驗是任何類型的開發中都不可或缺的環節,如果沒有統一的架構,可能校驗代碼會遍布整個應用,如何將數據校驗與應用邏輯解耦,如何適應各種粒度的數據和各種復雜程度業務規則,正是本文要探討的。
在我們各種類型的應用開發中有一個必不可少的環節-數據校驗,無論是大型企業應用,還是一個簡單的程序。如果沒有統一的架構,可能校驗代碼會遍布整個應用,一旦校驗規則改變就需要修改多處代碼,這是一種不好的設計,因為數據校驗與應用邏輯耦合得太緊。數據校驗不外乎語法校驗和語義校驗兩類,本文描述了一組架構上的模式來對這兩類需求提供解決方案。該模式組按照待校驗數據的粒度大小和業務規則的復雜程度分成多種類型:隔離校驗器,可組裝校驗器,動態策略校驗器,動態注冊校驗器等。大家可以針對自己的應用選擇合適的架構。應用這組模式還可以獲得一個好處,如果需要的話,我們可以把數據校驗器當作一個橫切關注點(Crosscut concern),應用 AOP(Aspect of Programming)技術,這樣可以徹底分離出數據校驗邏輯代碼。
問題引出
讓我們從幾個應用場景(user scenario)開始吧,第一個場景是網站上的注冊用戶,注冊時需要填寫很多數據,這些數據都需要校驗后才能寫進數據庫,比如用戶名,校驗規則可能是:用戶名由 a~z 的英文字母(不區分大小寫)、0~9 的數字、點、減號或下劃線組成,長度為 3~18 個字符。這種關于數據的結構正確性方面的校驗我們稱之為語法校驗。而身份證號碼這種數據,它需要根據出生日期校驗身份證號碼的正確性,不僅僅是填夠了 16 或 19 位數字就行。這種關于數據的內容正確性方面的校驗稱之為語義校驗。一般情況下語法和語義方面的校驗是在一塊處理的,比如身份證號碼,必然也需要校驗數據是否全是數字和必須是 16 或 19 位,這是語法校驗,同時它需要和出生日期相符,這又是語義校驗。從架構的角度而言,這種情況下區分語法和語義的意義不太大,因為沒必要把它分成兩個步驟用兩個方法來處理。但是有些應用,比如數據是一段 XML 的文本串,首先需要校驗 XML 字符串的結構-語法是否符合相應的 schema,然后再校驗其中某個元素的內容-語義的正確性,這可能就需要分開來處理比較合適,因為語法校驗是業務無關的,而后者的語義校驗是業務相關的,業務相關就意味著一旦業務規則改變,校驗規則就可能改變,所以這種情況最好將語義校驗分離出來。
第二個應用場景是一個 MDA(Model Driven Architecture)工具開發的例子,我們都用過大名鼎鼎的 Rational Rose 或 Microsoft Visio。這些工具都提供從 UML 模型生成代碼的功能,這就是 MDA,它們將 UML 模型映射成模型的元數據(meta-data)(稱之為元模型 meta-model),然后從元模型可以轉換成各種支持語言的代碼,如 Java, C++。當我們在視圖上畫一個 UML元素(如類 Class),然后為其定義了某種 Stereotype 來標識他的業務語義,比如數據庫的表 Table,或者自定義的一個用于表示 Web Service 的 Service 元素。接下來我們要將該元素生成相應的代碼,這時當你選定元素時,運行時系統并不知道該元素是普通的 Class,還是 Table,因為在運行時環境中都是 UML 的 Class 實例對象,這就需要我們提供校驗邏輯來處理了,處理 Class 的校驗邏輯和 Table 的校驗邏輯自然不應該放在一起,更何況如果是自定義的擴展元素,根本不可能把校驗代碼寫到已有系統里去。這就需要我們提供一個統一的校驗器接口,不同的校驗邏輯封裝在單獨的類中。進一步,我們需要對這些獨立的校驗器進行集中組裝和管理,因為我們不必每次都去實例化這些工具類,實例化后將它們緩存起來就可以了。
第三個應用場景是一個銀行并購的案例,假如銀行 A 并購了銀行 B,兩家銀行都有各自已有的電子銀行應用,并購后要將兩家應用整合成一個統一的應用,其中有一個余額查詢業務,在進行具體的查詢操作事務之前,需要校驗用戶輸入的帳號account,兩家銀行已有的帳號各有不同的創建規則,比如銀行 A 是 16 位數字作為帳戶,首 4 位是銀行代號,第二個 4 位是地區代號,第三個 4 位是網點代號,尾4位是用戶編號。而銀行 B 則是 19 位數字作為帳戶,各個區段的含義也和銀行 A 不一樣,這就要求用戶填寫一個帳戶的時候,后臺必須對應兩套數據校驗規則,而且應用需要根據一定的規則來選擇銀行 A 的校驗策略或銀行 B 的校驗策略。而且更復雜的情況是,銀行的帳戶還可能是升位后的(比如從 12 位升到 16 位),這樣必須同時兼顧新舊帳戶,也就是說有多套校驗規則來處理,我們的數據校驗器需要支持業務規則的動態切換。這里面可能有一個有爭議的地方,校驗帳號時需要有具體的業務規則支持,那么這算不算是業務邏輯呢,當然這個校驗邏輯并不那么純粹,軟件設計并不是個黑白的二元世界,各種層次的對象混合在一起很正常,我們也不大可能什么東西都能做個分水嶺把它們隔離開來。另外,這里的校驗邏輯還是和銀行應用別的業務邏輯不大一樣,比如轉帳交易,這個動作的觸發是一定要在一個高安全可靠的事務中執行的,而我們的校驗帳號過程可能不需要運行在事務中,或者只運行在低安全可靠級別的事務中即可。這是有本質區別的,所以把這種摻有業務規則的校驗劃分到校驗邏輯里而不是業務邏輯中是有理由的。
|
隔離校驗器
針對上述第一類應用場景,我們只需要把數據校驗邏輯從其他業務邏輯中剝離出來,將校驗邏輯委任到一個單獨的校驗類中去。把校驗職責分離出來后,第一個好處是:一旦我們需要更改校驗邏輯,只要修改校驗類代碼即可,而不用修改其他任何業務邏輯類。第二個好處是:可以集中管理控制所有的數據校驗邏輯,提高了代碼的內聚性,而且讓代碼簡潔、清晰。當然這里說的所有數據集中控制不一定就是全放在一個類中,如果有必要,也可以將數據按照不同的類型分組,每一個組封裝在一個校驗類中。第三個好處是可重用性高,校驗邏輯封裝成了一個工具類,自然可重用性大大提高。
在設計這個隔離校驗器類時還有一些需要權衡的地方,在設計某一個數據的校驗方法時,比如用戶名的校驗,如果數據出錯了,簡單的情況下,我們只需返回一個 boolean 值,告訴用戶數據有誤。而如果是身份證號碼這類數據出錯了,可能就需要提供更細粒度的錯誤類型給用戶,告訴用戶是與出生日期不符還是位數不夠。對這種錯誤種類較多的情況,我們可以返回錯誤代號(如 int 值)來區別各種錯誤,這是非面向對象語言的一種做法,在面向對象中我們可以用一個異常 Exception 來返回錯誤類型,這比返回錯誤代號更好,因為錯誤代號需要解析成具體的錯誤信息,這個解析工作還得由校驗器類的API使用者來調,這個使用者是其它的業務邏輯類,這就是說業務邏輯類還是耦合了數據校驗錯誤處理邏輯,顯然不如用異常處理來的徹底。
代碼如下:
清單1: UserInfoValidator.java
| public abstract class UserInfoValidator {public static boolean validateUserID(String uid) {boolean isValid = false;//校驗規則return isValid;}public static boolean validteEmail(String email) {boolean isValid = false;//校驗規則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.");//生日校驗規則// 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.");}} |
從上面代碼可以看出,我們用了靜態 static 方法,因為我們這是個工具類,沒有什么狀態需要存儲,所以不需要實例化類。而且調用校驗方法會很頻繁,用靜態方法可以提升性能。
另外還有一點值得一提,我們封裝了一個身份證數據類,里面包含了三個屬性:身份證號,出生日期,性別。驗證身份證號需要出生日期和性別奇偶碼這一點是沒有異議的,但為什么不用三個單獨的參數呢,這里的封裝為以后提供了更大的靈活性,比如將來我們打算將身份證驗證邏輯做得更精細,需要判斷出生地區的代碼是否和身份證的頭幾位一致,這可能就需要四個參數了,或者我們的出生日期需要換一個類(Date->Calendar)來表示,顯然我們只需要修改身份證數據封裝類,而不用修改調用接口。
|
可組裝校驗器
針對第二類場景,我們對每一個數據類提供一個獨立的校驗規則類,因為這個數據類本身已經包含了語法和語義邏輯。語法邏輯是與數據結構相關的,在我們的示例中,是判斷對象是否是UML的Class實例。而語義邏輯是與業務規則相關的,每一個數據類關聯的業務規則不盡相同,可能來自不同領域,或不同的業務組件或系統;另外由于業務規則的易變性較強,可擴展性和可配置性要求也較高,所以有必要為每一個數據類設置專屬的校驗類。這里我們將每一個校驗類稱作一條校驗規則(Rule)。校驗規則類的接口和實現代碼如下:
清單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.");}} |
在這里,我們還是提供了兩種校驗結果返回機制,boolean 值和拋出異常,在具體應用時大家可以選擇一個即可。這里的校驗規則實現是關于 Web Service 的,它的語法校驗是檢查數據是否是 UML 的 Class 對象,而語義校驗是檢查 Stereotype 是否是 Service,并檢查 Stereotype 中是否含有 WSDL(Web service description language) 屬性。
接下來,怎么應用這些校驗規則 rule 呢?一個系統中會有很多校驗規則類,那么就需要一個管理機制來管理這些校驗類,最簡單的我們只需定義一個方法 validate(Object value, IVRule rule),提供一層簡單的封裝,它可以起到一個代理的作用,比如,應用 Proxy 模式,我們可以對校驗規則本身做一些安全性認證方面的工作,然后才決定是否可以用該校驗規則。而更完善的管理機制是提供一個更靈活的環境,讓用戶可以動態組裝,改變,查找校驗規則類。基本思路是:我們將校驗規則類當作一種可重用資源,提供一個組裝工廠環境,用戶可以將校驗規則 rule 注冊到工廠里,工廠會實例化和緩存這些類,并提供查找服務;然后提供一個校驗器來從工廠里查找出相應的校驗規則 rule 類,為用戶提供校驗服務。這里面用到了 Factory,Flyweight,Registry 模式。(本文引用的模式請參考相關模式)
第一步,我們設計一個組裝工廠類,它提供實例化、緩存、和查找服務,很顯然,實例化類是一個 Factory 模式的基本職責,將實例緩存 cache 是一個 Flyweight 模式的基本職責,查找服務可以很簡單,在我們的例子中就是從一個 Map 中取出校驗規則 rule 實例,也可以復雜化,比如我們的校驗規則類是一個遠程資源,或者是實例化這個類需要用到其它的遠程資源,如數據庫,那這個查找功能實現起來可能就復雜些,可以通過 JNDI 來查找,也可以將遠程資源暴露成服務(Web Service)并注冊到 UDDI (Universal Discover Description and Integration),然后從 UDDI 中查找服務。從這里我們可以看到這個組裝工廠類具備管理校驗規則 rule 類的整個生命周期的職責,這為校驗器應用提供了很大的靈活性和可擴展性,假如我們今后需要實現一個實例池或資源池 Pool 來管理這些校驗規則實例,那么只需要將組裝工廠類的功能稍作修改和擴展就可,而不必觸及校驗器應用的其他類,因為我們已經將實例的管理邏輯從整個校驗器應用中剝離出來,管理職責的變化只局限在組裝工廠類內,對別的類是封閉的,這正體現面向對象的基本設計原則之一 Open-Close 原則(對修改封閉,對擴展開放)。組裝工廠類的代碼如下:
清單4: VRuleAssemblerFactory.java
| public class VRuleAssemblerFactory {private static Map rules = new HashMap();/*** 查找校驗規則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;}/*** 注冊/加入一個校驗規則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();}}}/*** 批量載入校驗規則Rule,一般在應用系統初始化時調用。*/public static void assembleVRules() {addVRule("Table", TableVRule.class);addVRule("Service", ServiceVRule.class);}} |
從上面的代碼可以看到,我們用一個 Map 作為緩存庫,注冊一個校驗規則時以字符串作為關鍵字,當然也可以用別的自定義類型,在查詢時利用了 Map 的查找功能很簡單高效地實現了查找功能,另外我們還定義了一個批量載入校驗規則的方法,這個功能是為了用戶使用方便,在應用系統初始化時執行一次,而且可以透明地載入校驗規則,在本例中只是硬編碼了這些校驗規則類,需要的話我們可以從別的元數據文件中(XML 或 CSV 文件)導入。
還需注意一點的是,這個組裝工廠是一個全局類,在這里是以靜態static方式實現的,當然也可以以Singleton方式來實現,還可以以線程安全ThreadLocal的方式來實現。
第二步,我們設計校驗器類,校驗器類應該作為整個校驗器應用的 Fa?ade,用戶需要校驗數據時只需和它打交道,這就很好的把校驗規則類隱藏了起來,因此校驗器的職責有查找相應的校驗規則類和執行校驗。校驗器類的接口和實現代碼如下:
清單5: IValidator.java
| public interface IValidator {/*** 通過關鍵字ruleHandler查詢校驗規則來校驗數據。*/public boolean isValid(Object value, String ruleHandler);/***通過關鍵字ruleHandler查詢校驗規則來校驗數據。返回異常。*/public void validate(Object value, String ruleHandler)throws DataValidationException;/*** 直接指定校驗規則類來校驗數據。*/public boolean isValid(Object value, IVRule rule);/***直接指定校驗規則類來校驗數據,返回異常。*/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模式,因為沒必要每次都實例化校驗器類。另外我們在這里提供了兩套返回機制,還有兩套取校驗規則的方式,這都可以根據實際應用作出取舍。
可組裝校驗器的架構圖如下:
圖 1:可組裝校驗器的架構圖
?
|
動態策略校驗器
針對第三類場景,我們必須支持一種數據對應有多套業務校驗規則,我們可以把每種業務規則都建模成一個校驗規則類,但這樣做靈活性和可擴展性就很差了。如果我們對一種數據只用一個校驗規則類,而將多套業務規則建模成多種策略,在校驗規則類中應用這些策略,這樣做好處在于:一可以對用戶隱藏業務規則,二是將來對策略進行修改或增加新的策略都不需要更改用戶的調用接口,三是我們可以在運行時動態地改變業務規則-策略。要實現上述需求,我們只需要在可組裝校驗器架構的基礎上,對校驗規則 rule 類引入 Strategy 模式。首先我們設計策略類,對于銀行并購這個例子,對帳號的校驗可以分成以下幾步:校驗銀行代號,地區代號,網點代號,用戶代號,因此在這里我們先根據各個銀行的業務策略對帳號進行分割,比如 19 位的帳號分成 4 位銀行代號,4 位地區代號,4 位網點代號,和 7 位用戶代號,然后再執行上述幾步校驗。業務策略類的接口和實現代碼如下:
清單7:IAccountStrategy.java
| public interface IAccountStrategy {/*** 獲得字符串分割規則,比如19位的帳號分成{4,4,4,7}。*/public int[] getSeperatedNumbers();/*** 校驗銀行代號。*/public boolean validateBankCode(String bankCode);/*** 校驗地區代號。*/public boolean validateDistrictCode(String districtCode);/*校驗網點代號。*/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;}} |
接下來我們設計校驗規則類,該類主要有選擇策略和使用策略來校驗兩種職責,代碼如下:
清單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 模式,可以看到這個校驗規則類具有管理和緩存策略類的職責。當然我們還可以在該類中增加一個方法 regiesterStrategy() 用來在運行時動態地增加業務規則策略,但目前我們的應用沒有這么復雜的需求,就算將來有也很容易重構目前的架構,所以這個設計活動應該點到為止。這也是設計中的一個權衡點,設計是沒有絕對完美的,人們在追逐絕對完美設計的過程中經常把對未來的種種揣測當作真正的需求,結果只能是危及整個設計,導致代碼臃腫,難以維護,僵化,靈活性差。適度的設計才是完美的。
動態策略校驗器的架構圖如下:
圖 2:可組裝校驗器的架構圖
在本例中,我們并不是從整合遺留資產的角度出發的,在實際的例子中,銀行A和銀行B可能都已存在各自的校驗類,這些類的接口不會是一致的,而且返回類型可能是boolean也可能是異常,甚至銀行A是Java應用而銀行B是C應用,這樣的話我們必須將這些遺留應用中已有的校驗類適配成現在的接口,這里可以對具體的Strategy實現類應用Adapter模式。
|
模式與價值觀
模式的三要素-問題,語境,解決方案我們在前面已經論述過了,每個模式都有它自己獨特的價值觀,那么這組架構模式給我們帶來了什么?
首先,它將校驗邏輯從應用邏輯中解耦出來,使得應用和校驗器可以獨立變化。第二,它促進了代碼重用,校驗器可以用到任何應用邏輯中去,不必局限于一處。如果需要的話,我們甚至可以將整個校驗器當作一個橫切關注點 (Crosscut concern),應用 AOP(Aspect of Programming)技術,將待校驗數據當作 Pointcut,這樣在應用的代碼中會看不到任何校驗代碼的痕跡,這就徹底分離出了數據校驗邏輯代碼。第三,從應用場合來看,隔離校驗器主要用在那些數據類型簡單而且校驗規則簡單的數據校驗中,可組裝校驗器用在那些數據類型復雜或校驗規則復雜、多變的數據校驗中,而動態策略校驗器則用在同一個數據的校驗就有多種校驗規則策略的數據校驗中??梢钥吹?#xff0c;這幾種模式是根據待校驗數據的粒度大小和業務規則的復雜程度來劃分的。
接下來,我們研究一下模式的變體。可組裝校驗器是這組模式的核心,它有很多變體,其實動態策略校驗器就是它的一種變體,其他變體還有復合規則檢驗器,鏈式檢驗器,動態注冊檢驗器等。比如在 XML 校驗器 SAX 的實現中,用戶可以動態地插入校驗 handle,或者我們需要對一個數據依次執行多套校驗規則,而不像之前一次只有一個校驗規則會被執行。對于這種需求,我們有三種方案可選,第一種是復合規則檢驗器,利用Composite 模式來實現校驗規則 IVRule 接口,復合的校驗規則類中包含一組簡單的校驗規則類 VRule,當調用復合類的 validate() 方法時,復合類會依次調用所有的簡單校驗規則類。第二種是鏈式校驗器,利用 Chain of responsibility 模式來實現校驗規則 IVRule 接口,前一個校驗規則類執行校驗后傳遞到下一個校驗規則類,一層層按固定順序傳遞下去,每一個校驗規則類關注的校驗點不一樣,這適合于順序固定的情況。第三種是動態注冊校驗器,利用 Registry 模式,將校驗規則類 VRule 動態地注冊到校驗器類 Validator 中去,比如注冊到一個 List 或 Map 中,在校驗器類的 validate() 方法中可以按某種算法來實現調用校驗規則類的順序或更復雜的調用邏輯。很顯然動態注冊校驗器很靈活,可擴展性也很強,但同時對校驗器使用者來說,它復雜了,校驗規則也不透明了。所以并非靈活性和可擴展性越強就越好,一切應該取決于需求,如果你一次只有一個校驗規則執行就沒必要再引入復雜性了。
另外還可以對校驗方法的返回類型作一下擴展,boolean 值和異常作為返回一般來說足夠了,但如果我們的返回結果比較復雜,比如前面講到的一個數據需要執行多個校驗規則的情況,返回的結果可能需要將多個校驗規則的返回結果匯總,也可能需要更細級別的結果。這就需要一種工業級的返回機制,在 Eclipse 中就有這么一個返回類型,稱為狀況對象 (Status object),它可以對返回類型進行分級:OK、 Warning、 Error,甚至還封裝了更低級的異常。而且針對返回狀態比較復雜的情況,還應用了 Composite 模式實現了一個 MultiStatus 類來組合多個錯誤狀態。這在校驗結果不僅僅是 true 或 false 兩種狀態的場合下非常有用,而且可以記錄跟蹤校驗規則信息。不過這是一種重量級的返回機制。下面展示了狀況對象的接口代碼:
//清單 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();//是否復合狀況。public boolean isMultiStatus();//如果是復合狀況,返回子狀況類。public IStatus[] getChildren(); |
|
結束語
這組架構模式不局限于某種語言、應用,可以應用到任何場合。如果我們將數據校驗當作一項業務操作的話,可以將它擴展到其他領域。模式可以促進好的架構,也可能導致萬劫不復,關鍵取決于設計者的把握。所以,我們在選擇模式的時候,一定要考量模式的何種特性對你最有價值,模式所提供的價值觀與您的需求期望是否吻合。
|
相關模式
- Factory Method:工廠模式用于產生類的實例。
- Singleton:單例模式用于保證一個類只產生一個實例。
- Flyweight:享元模式用于緩存和維護一組實例。
- Composite:復合模式用于組合一組類,而這些類和復合類具有同一接口。
- Facade:門面模式用于為客戶提供統一的外觀,隱藏復雜的實現細節。
- Proxy:代理模式用于代理一個對象,控制其他對象對該對象的訪問。
- Strategy:策略模式用于提供一組可互換的算法/策略,它們遵循同一接口。
- Adapter:適配器模式用于將類的接口適配為另一種接口。
- Chain of responsibility:職責鏈模式用于為鏈式傳遞處理請求。
以上模式來自《GOF設計模式》。
- Registry:注冊器模式用于注冊和管理一個或多個對象。(來自《企業架構模式》)
|
參考文獻
- 《Design Patterns:Elements of Reusable Object-Oriented software》-《設計模式:可復用面向對象軟件的基礎》 ERICH GAMMA RICHARD HELM RALPH JOHNSON JOHN VLISSIDES (GOF)。
- 《Patterns of Enterprise Application Architecture》-《企業架構模式》 Martin Fowler。
|
下載
| DataValidator.jar | 39 K | HTTP |
| 關于下載方法的信息 | Get Adobe? Reader? | |||
參考資料
- 界面組裝器模式(developerWorks 中國,2006 年 6 月):本文作者提出的另外一種界面設計中的架構模式-界面組裝器模式,它致力于分解界面,將界面和組裝行為解耦,將界面邏輯處理與領域邏輯處理解耦。
- Java 技術專區:數百篇有關 Java 編程各方面的文章。
關于作者
| ? | 劉岳林,IBM 中國軟件實驗室成員,在 OOAD, RUP, XP, Architecture/Design Pattern 方面有著豐富的項目實踐經驗,對架構設計,項目、過程管理有過深入的研究,技術方向為 J2EE, SOA, Grid, AOP ,PKI。你可以通過 yuelin_liu@msn.com 聯系他。 | |
總結
以上是生活随笔為你收集整理的数据校验器架构模式组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 尼得科与日本电产三协共同研发出一款搭载有
- 下一篇: 送给销售一族