概要
值對象是一些單一的參數
用來聯合一系列的對象——在大多數的情況下
在一個方法調用裡有各種各樣的參數
這些參數描述了一個大數量級的屬性
通常
這些屬性需要單獨檢測
而且大多數情況下是檢測其是否為null
通常
這些檢測帶出來了大量的代碼行
這篇文章描述了如何實現基於著名的Visitor模式和反射的值對象
在業務過程中
你通常有一些屬性不能為空
而另外一些則沒有這樣的要求
在那些必須有實例的屬性的案例中
你不得不實現如下所示的檢測
if( attribute
== null )
{
throw new Attribute
IsNullException()
}
如果值對象有N個屬性
你將會得到如下所示的代碼
if( attribute
== null )
{
throw new Attribute
IsNullException()
}
if( attribute
== null )
{
throw new Attribute
IsNullException()
}
if( attribute N == null )
{
throw new AttributeNIsNullException()
}
結果
一大堆的IF語句
但是你不得不把它們全部打出來
現在假設校驗的數量從
增加到
因為有
個新增的用例必須在一個循環中實現
你是不是失去了勇氣?用來減少這些檢測的一個有效的方法是將他們從值對象類移到值對象的校驗類
從這個觀點看來
你可能承認你永遠執行相同的檢測
唯一的不同是屬性的名稱和類型
在大多數情況下
類型不讓人感興趣
因為由編譯器檢測它
還有重要的一點需要確認
接收這些屬性的值的方法都由同一個名稱開始
在我們的案例中
是get
通過反射調用這些值對象的getters方法非常簡單
如果你使用Eclipse
例如
你可以自動為所有的屬性產品setters和getters方法
對於我們的attribute
getter方法是getAttribute
()
setter方法為setAttribute
(Integer attributeValue)
如果attribute
是Integer類型的屬性
如果這些前提給定了的話
你就能考慮一個一般的解決方案
這篇文章解釋了如何使用Visitor模式和反射來實現這個一般的解決方案
框架類和接口
下面的類圖顯示了建立我們一般的校驗框架需要用到的類和接口之間的關系
注意
你可以從Resources上下載這些類和接口
Validateable接口
Validateable接口有著和Visitable接口相同的功能
那個定義的方法validateWith()是一個和Visitor模式裡的Visitable接口的accept()方法相似的方法
通過validateWith()方法
你就能夠校驗有著不同validators的值對象
因為這個方法以IClassAttributeValidator接口的實現作為參數
IClassAttributeValidator接口
IClassAttributeValidator接口和Visitor模式的Visitor接口相對應
其中的validate(AbstractParameter param)方法和Visitor接口的visit(object SomeObject)方法相似
validate()方法的AbstractParameter參數類型允許我們通過任何AbstractParameter類的子類類型的參數訪問validate
另外
在validateWith()方法裡使用這個接口作為參數允許我們在將來改變使用的validator
用這些改變的validator作為來滿足不同的validation的需求——例如
除了null檢測以外
在一個定義的值范圍測試參數屬性
AbstractParameter
AbstractParameter類實現了Validateable接口的validateWith()方法
就像你將要看到的下面的代碼片斷一樣
這個實現非常簡單
這個方法僅僅是調用給定的validator的validate()方法
並且傳遞參數對象到validator
public void validateWith( IClassAttributeValidator validator ) throws Exception
{
validator
validate( this );
}
而且
AbstractParameter也實現一些常用的其他方法
受保護的方法
addOptionalMethod()使得所有的子類型增加一些可選擇的方法到optionalGetAttributeMethods HashMap()
繼承自AbstractParameter使得你能夠取得哪些可能傳遞null的getters方法
就像你能想象到的一樣
你能夠增加可選擇的方法
例如
到繼承自AbstractParameter的值對象的構造器裡
isOptionalMethod()方法用於檢測屬性是否已經被校驗過了
toString()方法實現了一些便利
因為值對象可能是由很多屬性組成
使得在AbstractParameter的子類裡
不需要寫很多的System
out
printlns實現
這個方法也是使用反射達到目的的
GenericClassAttributeValidator
GenericClassAttributeValidator類實現了IClassAttributeValidator接口的validate()方法
這個類同時也實現了單態模式
validate()的實現看起來象下面這樣
public synchronized void validate( AbstractParameter param ) throws AttributeValidatorException
{
Class clazz = param
getClass();
Method[] methods = clazz
getMethods();
//Cycle over all methods and call the getters!
//Check if the getter result is null
//If result is null throw AttributeValidatorException
Iterator methodIter = Arrays
asList( methods erator();
Method method = null;
String methodName = null;
while ( methodIter
hasNext() )
{
method = (Method) methodIter
next();
methodName = method
getName();
if ( methodName
startsWith(
get
) &&
clazz
equals( method
getDeclaringClass() ) &&
!param
isOptionalMethod( methodName ) )
{
Object methodResult = null;
try
{
methodResult = method
invoke( param
null );
}
catch ( IllegalArgumentException e )
{
throw new AttributeValidatorException( e
getMessage() );
}
catch ( IllegalAccessException e )
{
throw new AttributeValidatorException( e
getMessage() );
}
catch ( InvocationTargetException e )
{
throw new AttributeValidatorException( e
getMessage() );
}
if ( methodResult == null )
{
String attributeName = methodName
substring(
)
toLowerCase() +
methodName
substring(
methodName
length() );
String className = clazz
getName();
className = className
substring( className
lastIndexOf(
) +
);
Integer errorNumber = new Integer(
);
throw new AttributeValidatorException(
Error:
+ errorNumber +
+ attributeName +
in
+ className +
is null!!!
);
}
}
}
}
首先
就像你在代碼裡看到的那樣
我們從值對象裡取得所有的方法
然後
我們遍歷所有方法的集合
如果方法以get開頭
便是AbstractParameter的子類型
而不是可選擇的方法
我們通過反射調用getter方法
並且檢測它的結果
如果結果是null
那麼這就是一個錯誤
如果不是
便是正常情況
那些可選擇的方法和繼承自父類的方法不會被執行
測試我們的類
現在
我們實現了我們所需要的所有的類和接口
我們必須做一些測試來檢驗我們的類是否能夠正常工作
為了做到這一點
我們寫了一點小的測試類和一個main方法來運行測試
TestParameter
TestParameter類繼承自AbstractParameter
並且包括了一些需要校驗的私有屬性
很簡單的
個Integer屬性
Optional attributes
為了識別可選的屬性沒有被檢測
我們定義了為屬性
testParam
可選的getter方法
為了這個目的
我們通過TestParameter的構造器裡的addOptionalMethod(methodName)方法將這個getter方法輸入到父類AbstractParameter的可選方法map裡
校驗框架是如何工作的
為了測試
我們在TestParameter裡使用如下方式輸入
TestParameter param = new TestParameter( );
param
setTestParam
( new Integer(
) );
param
setTestParam
( new Integer(
) );
param
setTestParam
( new Integer(
) );
param
setTestParam
( new Integer(
) );
就像你所看到的那樣
個Integer屬性記作Integer
和
為了校驗
我們僅僅調用
param
validateWith( GenericClassAttributeValidator
getInstance( ) );
這個校驗的結果是
testParam
:
testParam
:
testParam
:
testParam
屬性沒有被校驗
因為我們記錄了它的getter方法為可選的
其他所有的方法得到了校驗
並且結果是正常的
現在
我們希望看到其中的一個屬性值為空
這樣我們就能檢測是否validator能夠檢測到這個錯誤
我們注釋掉下面的行
param
setTestParam
( new Integer(
) );
我們重新開始測試以後
得到如下的結果
testParam
:
Error: testParam
in TestParameter is null!!!
testParam
:
現在我們看到了validator已經檢測到了這個沒有賦值的屬性
如果屬性類型為集合類型
將會怎麼樣呢?
如果屬性類型為集合
它仍然會檢測這個集合是否為空
但是
可能檢測集合是否為null並不是你想要的
在大多數情況下
你希望檢測集合裡的對象是否為null
如果集合的實現不允許null對象
你不需要關心這些在GenericClassAttributeValidator裡的null對象繼承自AbstractParameter
一些為集合保持繼承自AbstractParameter的對象的輔助代碼看起來如下所示
if ( methodResult instanceof Collection )
{
Collection col = (Collection) methodResult;
Iterator iter = erator();
Object subParam = null;
while ( iter
hasNext() )
{
subParam = iter
next();
if ( subParam instanceof AbstractParameter )
{
AbstractParameter abstractParam = ( AbstractParameter ) subParam;
abstractParam
validateWith( this );
}
}
}
集合裡的所有沒有繼承自AbstractParameter類的對象沒有被檢測
因為我們將使用一個不允許null對象的集合實現
所以集合實現為我們完成了檢測
如果你決定使用一個允許null對象的實現
那麼為while循環的所有其他的對象的一個額外的null檢測就是必須的了
else if( subParam == null )
{
System
out
println(
Error: SubParameter not set in Collection!
);
}
值之間的依賴
在一些情況下
只有當值對象的其他屬性被分配了值
一個屬性才有可能是可選的
屬性的
可選性
sometimesOptional依賴於actionType屬性的值
可能action屬性持有的值代表了actions
例如addSomething =
updateSomething =
和 deleteSomthing =
如果action的值是
或者
sometimesOptional屬性不是可選的
如果action的值是
則是可選的
當我們為actionType賦值的時候
我們必須設置sometimesOptional的可選性
public void setActionType(int actionType)
{
this
actionType = actionType;
super
clearOptionalMethods( );
switch( this
actionType )
{
case ActionParameter
ACTION_ADD :
super
addOptionalMethod(
getSometimesOptional
);
super
addOptionalMethod(
getSometimesOptional
);
break;
case ActionParameter
ACTION_UPDATE :
super
addOptionalMethod(
getSometimesOptional
);
break;
case ActionParameter
ACTION_REMOVE :
super
addOptionalMethod(
getSometimesOptional
);
super
addOptionalMethod(
getSometimesOptional
);
super
addOptionalMethod(
getSometimesOptional
);
break;
default :
break;
}
}
你會看到清除可選方法列表是必需的
因為如果你給actionType賦值超過一次的話
越來越多的方法將作為可選的方法添加進來
另外的一個解決方法包括實現一個AddActionParameter
一個UpdateActionParameter和一個RemoveActionParameter
它們都是從AbstractParameter類繼承得來
那麼你可能不需要actionType屬性
但是擁有actionType屬性的類存在並且經常被使用
對該類使用反射非常容易
你必須使用Switch語句
展望
現在
我們可以考慮繼承AbstractParameter的更多的功能——例如
范圍校驗
AbstractParamter需要一個數據結構來存儲范圍值
HashMap能夠做到
它以方法名作為key存儲范圍對象
或者你可以檢測是否一個String類型的值包含一些定義的字
等等
你也可以考慮Perl
的正則表達式
所有的這些檢測都可以在Validator類裡實現
它實現了IClassAttributeValidator接口
如果你想使用屬性的null檢測和附加值檢測
那麼你可以寫一個子類來繼承GenericClassAttributeValidator
在J
EE應用裡
值對象經常被用來在客戶端和服務器之間傳遞業務過程的數據
但是
如果你僅僅在服務器端校驗這些值對象的屬性
你常常不得不因為一個錯誤的非可選屬性為null而取消業務過程
你必須中斷業務過程
而向客戶端發送一個錯誤頁面
這是一個好的實踐
在服務端應用這些validators的同時
你也可以以委派的形式在客戶端應用它們
在客戶端檢測值對象可以避免不必要的對服務器的請求和降低網絡堵塞
寫一次
使用多次
如果你使用我描述的方法來檢測你的值對象的屬性
你可以永遠只增加值對象的屬性——不用改變validator
它們能夠被自動檢測是否為null
你也可以不用改變validator而改變一個屬性的條件
而且
當然
一個已有的validator也能夠校驗一個未來的值對象
如果這個值對象繼承AbstractParameter的話
還有
你也可以不用改變值對象而寫一個額外的validators
因為validators實現的是Visitor模式
這就是所謂的寫一次
使用多次
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27352.html