我以前的痛苦,你也擁有嗎?
記得筆者在以前使用Delphi/C++/Java開發軟件時,經常需要撰寫許多的輸入驗證碼,例如當用戶輸入身份證號,或是輸入數值時,都需要根據企業邏輯來檢查用戶輸入的數據是否正確,通常許多的驗證邏輯程序代碼幾乎都是一樣的,但是對於不同的程序語言,不同的開發環境都可能需要不斷重寫。更麻煩的是,由於一個團隊擁有許多的開發人員,因此不同的開發人員可能會使用不同的程序代碼,不同的驗證邏輯,並且分散在整個應用程序不同的地方進行數據驗證的工作。這造成了許多的困擾,例如當驗證邏輯需要修改或是更正時,可能忘記改某些地方,或是由於開發人員撰寫的方式不同,而讓一些臭蟲因此而出現。雖然後來許多的開發人員把這些驗證程序代碼封裝成函式庫統一讓開發人員使用,或是封裝成組件讓開發人員使用,例如現在.NET/Java都提供了一些簡單,常用的Validation組件,但是驗證程序代碼分散在整個應用程序不同的地方的問題仍然無法得到解決,而由這個現象延伸出的問題也一樣存在。那麼我們有什麼好方法可以解決嗎?
在回答之前也許我們應該想想這些驗證程序代碼的目的到底是什麼?在這些驗證程序代碼中除了一般檢查用戶輸入的字符,格式,長度或是語意之外,最重要的驗證工作應該是用戶的輸入應該符合應用程序的企業邏輯規則,不是嗎?
因此如果我們能夠把這些驗證程序代碼進行的工作定義在企業邏輯模型之中,那麼不管日後開發人員使用什麼程序語言,或是在應用程序的什麼地方,開發人員只需要在需要進行驗證程序代碼的工作時從企業邏輯模型中最出定義好的驗證邏輯,再根據這些驗證邏輯來驗證用戶輸入的數據。如此一來一旦驗證邏輯有改變或是更新,我們只需要更新定義在企業邏輯模型之中的驗證邏輯,那麼由於整個應用程序都是從企業邏輯模型中取得驗證邏輯,那麼我們不就解決了可能忘記更改一些分散在不同地方的問題了嗎? 更進一步的想想,如果我們能夠使用一段通用的程序代碼來檢查企業邏輯規則,而不需要對每一個不同的用戶輸入撰寫不同的程序代碼來檢查,那麼不是更棒嗎(如果讀者真的開發過這樣的應用程序驗證邏輯,就知道筆者說的痛苦,以及難以維護的程序代碼了)?
問題是,能夠有方法解決這長久以來的痛苦嗎?
也許有可能,讓我們繼續往下看。
在UML中,開發人員可以使用OCL來定義類別/屬性的驗證邏輯或是約束條件,之後開發人員便可以在程序代碼中藉由EcoSpace取得這些約束條件(Constraints),並且使用來驗證異動的對象是否符合這些約束條件,一旦符合約束條件才能夠更新回數據來源之中。在ECO架框中支持了這樣的機制,ECO藉由提供開發人員使用OCL爲ECO類別/ECO屬性定義約束條件,以及在ECO架框中提供服務讓開發人員能夠在程序代碼中存取這些約束條件並且執行約束條件以便驗證對象。
在您瞭解了上面討論的觀念之後,接下來的內容中將深入的說明如何在企業邏輯模型中使用OCL來定義約束條件,並且使用來驗證對象。
在企業邏輯模型中定義約束條件
開發人員可以在ECO的類別設計家中藉由對象檢視器設定類別或是屬性的Constraints特性來定義約束條件。使用Constraints特性定義約束條件時開發人員必須瞭解下面的觀念:
l Constraints特性可以定義任何數目的約束條件
l 約束條件是使用OCL來撰寫的
l 開發人員使用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程序代碼中,要根據約束條件來驗證對象,它的執行步驟如下:
l 從ECO企業邏輯對象中取得所有的約束條件的IConstraint接口對象
l 一一的使用IOclService執行這些約束條件並且判斷約束條件是否符合
l 如果ECO企業邏輯對象符合約束條件才能夠更新回數據來源
l 如果ECO企業邏輯對象不符合約束條件,那麼就通知用戶進行後續的處理
瞭解了IConstraint界面以及處理約束條件的步驟之後,接下來我們就可以使用範例和程序代碼來展示如何使用約束條件了。
在程序代碼中驗證對象的約束條件
要如何使用程序代碼來檢查定義在企業邏輯模型中的約束條件呢? 其實非常的簡單的,本書在第4章已經介紹了ECO的企業邏輯模型的靜態通用機制,因此我們可以使用下面的步驟來完成檢查企業邏輯模型中的約束條件:
l 在更新異動對象回數據來源之前,藉由使用IDirtyListService服務界面取得用戶所有異動的對象
l 藉由ECO的企業邏輯模型的靜態通用機制取得定義在企業邏輯模型中的約束條件
l 藉由IOclService服務接口一一的執行約束條件並且檢查是否違反約束條件
l 如果沒有違反任何的約束條件就更新對象回數據來源中
l 如果違反約束條件的話,保留違反約束條件的對象,要求用戶進一步的處理
瞭解了進行約束條件檢查的步驟之後,讓我們看一些實際的程序代碼來驗證上面的步驟。
在下面的片段程序代碼中是準備呼叫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組件和約束條件,那麼我相信大多數的程序代碼驗證工作都可以順利,有效率的完成。
您注意到,感覺到了使用模型和約束條件的好處了嗎?