維's profileIT : 是工作還是嗜好?PhotosBlogListsMore ![]() | Help |
|
August 10 我以前的痛苦,你也擁有一樣的痛苦嗎?記得筆者在以前使用Delphi/C++/Java開發軟體時,經常需要撰寫許多的輸入驗證碼,例如當使用者輸入身分證字號,或是輸入數值時,都需要根據企業邏輯來檢查使用者輸入的資料是否正確,通常許多的驗證邏輯程式碼幾乎都是一樣的,但是對於不同的程式語言,不同的開發環境都可能需要不斷重寫。更麻煩的是,由於一個團隊擁有許多的開發人員,因此不同的開發人員可能會使用不同的程式碼,不同的驗證邏輯,並且分散在整個應用程式不同的地方進行資料驗證的工作。這造成了許多的困擾,例如當驗證邏輯需要修改或是更正時,可能忘記改某些地方,或是由於開發人員撰寫的方式不同,而讓一些臭蟲因此而出現。雖然後來許多的開發人員把這些驗證程式碼封裝成函式庫統一讓開發人員使用,或是封裝成元件讓開發人員使用,例如現在.NET/Java都提供了一些簡單,常用的Validation元件,但是驗證程式碼分散在整個應用程式不同的地方的問題仍然無法得到解決,而由這個現象延伸出的問題也一樣存在。那麼我們有什麼好方法可以解決嗎? 在回答之前也許我們應該想想這些驗證程式碼的目的到底是什麼?在這些驗證程式碼中除了一般檢查使用者輸入的字元,格式,長度或是語意之外,最重要的驗證工作應該是使用者的輸入應該符合應用程式的企業邏輯規則,不是嗎? 因此如果我們能夠把這些驗證程式碼進行的工作定義在企業邏輯模型之中,那麼不管日後開發人員使用什麼程式語言,或是在應用程式的什麼地方,開發人員只需要在需要進行驗證程式碼的工作時從企業邏輯模型中最出定義好的驗證邏輯,再根據這些驗證邏輯來驗證使用者輸入的資料。如此一來一旦驗證邏輯有改變或是更新,我們只需要更新定義在企業邏輯模型之中的驗證邏輯,那麼由於整個應用程式都是從企業邏輯模型中取得驗證邏輯,那麼我們不就解決了可能忘記更改一些分散在不同地方的問題了嗎? 更進一步的想想,如果我們能夠使用一段通用的程式碼來檢查企業邏輯規則,而不需要對每一個不同的使用者輸入撰寫不同的程式碼來檢查,那麼不是更棒嗎(如果讀者真的開發過這樣的應用程式驗證邏輯,就知道筆者說的痛苦,以及難以維護的程式碼了)? 問題是,能夠有方法解決這長久以來的痛苦嗎? 也許有可能,讓我們繼續往下看。 在UML中,開發人員可以使用OCL來定義類別/屬性的驗證邏輯或是約束條件,之後開發人員便可以在程式碼中藉由EcoSpace取得這些約束條件(Constraints),並且使用來驗證異動的物件是否符合這些約束條件,一旦符合約束條件才能夠更新回資料來源之中。在ECO架框中支援了這樣的機制,ECO藉由提供開發人員使用OCL為ECO類別/ECO屬性定義約束條件,以及在ECO架框中提供服務讓開發人員能夠在程式碼中存取這些約束條件並且執行約束條件以便驗證物件。 在您瞭解了上面討論的觀念之後,接下來的內容中將深入的說明如何在企業邏輯模型中使用OCL來定義約束條件,並且使用來驗證物件。 在企業邏輯模型中定義約束條件 開發人員可以在ECO的類別設計家中藉由物件檢視器設定類別或是屬性的Constraints特性來定義約束條件。使用Constraints特性定義約束條件時開發人員必須瞭解下面的觀念: n Constraints特性可以定義任何數目的約束條件 n 約束條件是使用OCL來撰寫的 n 開發人員使用Constraints特性定義約束條件後,如果使用者違反了約束條件那麼仍然可以更新物件回資料來源。因此檢查物件是否違反約束條件是開發人員的責任,開發人員必須確定物件在更新回資料來源之前沒有違反約束條件。 現在讓我們以圖1的範例模型來說明如何定義約束條件以及如何在程式碼中檢查企業邏輯模型的約束條件。 圖1範例模型
如下圖所示,我們可以點選Joiner類別,並且在物件檢視器中定義Constraints特性: 圖2 在ECO設計家中為類別/屬性定義約束條件
開發人員可以直接在Constraints特性中輸入OCL或是點選Constraints特性旁的按鈕啟動Constraints編輯器,點選Constraints編輯器下方的Add按鈕加入約束條件。下面的圖形加入了兩個約束條件,每一個約束條件必須輸入約束條件的名稱以及約束條件的OCL敘述,例如NameNotEmpty約束條件規定了Name屬性不可為空白,同樣的EMailNotEmpty也是類似的規定。 圖3 啟動約束條件編輯器為Joiner類別定義約束條件
最後,範例企業邏輯模型也為DevCoSeminar的MaxCount屬性定義了最多參加人數的約束條件必須小於120人:
圖4 啟動約束條件編輯器為DevCoSeminar類別定義約束條件
定義完Constraints的約束條件後,讓我們再解釋一下封裝約束條件的IConstraint介面。 IConstraint介面 在ECO架框中企業邏輯模型中的約束條件是由IConstraint介面封裝,定義的,開發人員藉由ECO架框服務介面存取到IConstraint介面,再藉由執行IConstraint介面定義的約束條件來驗證物件是否符合企業邏輯。下面是IConstraint介面的定義: public interface IConstraint: IModelElement { IExpression Body { get; } IEcoConstraint EcoConstraint { get; } } IConstraint介面有兩個唯讀屬性,其中的Body即代表封裝的約束條件,而EcoConstraint則是專屬於ECO的額外資訊。Body屬性型態是IExpression介面,而IExpression介面的定義如下: public interface IExpression { string Language { get; } string Body { get; } } IExpression介面也有兩個唯讀屬性,其中的Language屬性會回傳約束條件使用的語言,通常都是”OCL”,代表是由OCL撰寫的約束條件。而Body屬性則是約束條件本身。例如前面的『NameNotEmpty』約束條件,它的IConstraint. Body. Body屬性便是self.Name.Length > 0。 IEcoConstraint介面也定義了兩個唯讀屬性,IsAutoGenerated屬性回傳這個約束條件是否是自動產生的,而Description則是約束條件的敘述文字: public interface IEcoConstraint { bool IsAutoGenerated { get; } string Description { get; } } 因此在ECO程式碼中,要根據約束條件來驗證物件,它的執行步驟如下: n 從ECO企業邏輯物件中取得所有的約束條件的IConstraint介面物件 n 一一的使用IOclService執行這些約束條件並且判斷約束條件是否符合 n 如果ECO企業邏輯物件符合約束條件才能夠更新回資料來源 n 如果ECO企業邏輯物件不符合約束條件,那麼就通知使用者進行後續的處理 瞭解了IConstraint介面以及處理約束條件的步驟之後,接下來我們就可以使用範例和程式碼來展示如何使用約束條件了。 在程式碼中驗證物件的約束條件 要如何使用程式碼來檢查定義在企業邏輯模型中的約束條件呢? 其實非常的簡單的,本書在第4章已經介紹了ECO的企業邏輯模型的靜態通用機制,因此我們可以使用下面的步驟來完成檢查企業邏輯模型中的約束條件: n 在更新異動物件回資料來源之前,藉由使用IDirtyListService服務介面取得使用者所有異動的物件 n 藉由ECO的企業邏輯模型的靜態通用機制取得定義在企業邏輯模型中的約束條件 n 藉由IOclService服務介面一一的執行約束條件並且檢查是否違反約束條件 n 如果沒有違反任何的約束條件就更新物件回資料來源中 n 如果違反約束條件的話,保留違反約束條件的物件,要求使用者進一步的處理 瞭解了進行約束條件檢查的步驟之後,讓我們看一些實際的程式碼來驗證上面的步驟。 在下面的片段程式碼中是準備呼叫EcoSpace的UpdateDatabase方法把使用者異動的物件更新回資料來源中,但是在這之前,它先呼叫了DoCheckObjectConstraints方法以便確定所有異動的物件是符合企業邏輯模型中的約束條件,否則DoCheckObjectConstraints會產生一個例外錯誤。 001 procedure TWinForm.Button2_Click1(sender: System.Object; e: System.EventArgs); 002 begin 003 try 004 DoCheckObjectConstraints; 005 EcoSpace.UpdateDatabase; 006 except on E: Exception do 007 MessageBox.Show ('物件違反約束條件' + e.Message +'無法更新回資料庫', 008 MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk); 009 end; end; 在DoCheckObjectConstraints方法中先在007行取得IDirtyListService介面,接著進入010行的for迴圈一一的取出每一個異動的物件並且呼叫DoHandleDirtyObject來檢查每一個異動物件的約束條件是否符合。 001 procedure TWinForm.DoCheckObjectConstraints; 002 var 003 dol : IDirtyListService; 004 Iobj : IObject; 005 begin //藉由IdirtyListServices取得異動的物件 007 dol := EcoSpace.DirtyListService; 008 if (dol.HasDirtyObjects) then 009 begin 010 for Iobj in dol.AllDirtyObjects do 011 begin 012 DoHandleDirtyObject(Iobj); 013 end; 014 end; 015 end; 最後的DoHandleDirtyObject方法在008行取得IOclService介面,在009行進入for迴圈,藉由IObject 介面的UmlType.Constraints取得定義在這個物件類別中的所有約束條件,接著在012行呼叫IOclService的Evaluate方法來執行/評量約束條件。如果有任何物件違反了任何的約束條件就產生一個例外錯誤。 001 procedure TWinForm.DoHandleDirtyObject(Iobj : IObject); 002 var 003 IOcl : IOclService; 004 ICnt : IConstraint; 005 iCount: Integer; 006 bResult : boolean; 007 begin 008 IOcl := EcoSpace.OclService; 009 for iCount := 0 to Iobj.UmlType.Constraints.Count - 1 do 010 begin 011 ICnt := Iobj.UmlType.Constraints.Item[iCount]; 012 bResult := boolean(IOcl.Evaluate(Iobj as IElement,iCnt.Body.Body).AsObject); 013 if (not bResult) then 014 raise Exception.Create(ICnt.Name); 015 end; 016 end; 現在如果我們執行上面的範例程式碼並且搭配圖3和圖4定義的約束條件,那麼我們可以看到當執行下面的範例程式並且在DevCoSeminar物件的MaxCount屬性中輸入超過120的數值:
圖5 修改DevCoSeminar物件的MaxCount屬性值
那麼在更新物件回資料來源之前就會看到下面的例外錯誤,代表上面的程式碼果然檢查出了物件違反了約束條件。 圖6 程式碼檢查出使用者輸入的資料違反了約束條件
如果我們接著又如下圖對Joiner類別進行異動並且試著把EMail屬性清為空白:
圖7 程式碼檢查出使用者輸入的資料違反了約束條件
那麼在更新物件回資料來源之前我們又可以看到範例程式檢查出Joiner物件違反了EmailNotEmpty的約束條件。 從上面的程式碼看到了什麼? 如果您有注意的話會發現只需要使用相同的一份程式碼,在一個程序/函式中就可以檢查任何物件的任何約束條件。這和以前許多開發人員需要使用不同的程式碼,在不同的地方檢查不同的使用者輸入資料是完全不一樣的,這可以讓程式碼更容易維護,也不容易產生臭蟲,或是因為疏失而造成的錯誤。 如果我們進一步的結合.NET/開發工具提供的Validation元件和約束條件,那麼我相信大多數的程式碼驗證工作都可以順利,有效率的完成。 您注意到,感覺到了使用模型和約束條件的好處了嗎? Comments (7)
Trackbacks (1)The trackback URL for this entry is: http://gordonliwei.spaces.live.com/blog/cns!CCE1F10BD8108687!1096.trak Weblogs that reference this entry
|
|
|