概要 值对象是一些单一的参数用来联合一系列的对象——在大多数的情况下在一个方法调用里有各种各样的参数这些参数描述了一个大数量级的属性通常这些属性需要单独检测而且大多数情况下是检测其是否为null通常这些检测带出来了大量的代码行这篇文章描述了如何实现基于着名的Visitor模式和反射的值对象 在业务过程中你通常有一些属性不能为空而另外一些则没有这样的要求在那些必须有实例的属性的案例中你不得不实现如下所示的检测 if( attribute == null ) { throw new AttributeIsNullException() } 如果值对象有N个属性你将会得到如下所示的代码 if( attribute == null ) { throw new AttributeIsNullException() } if( attribute == null ) { throw new AttributeIsNullException() }
if( attribute N == null ) { throw new AttributeNIsNullException() } 结果一大堆的IF语句但是你不得不把它们全部打出来 现在假设校验的数量从增加到因为有个新增的用例必须在一个循环中实现你是不是失去了勇气?用来减少这些检测的一个有效的方法是将他们从值对象类移到值对象的校验类 从这个观点看来你可能承认你永远执行相同的检测唯一的不同是属性的名称和类型在大多数情况下类型不让人感兴趣因为由编译器检测它还有重要的一点需要确认接收这些属性的值的方法都由同一个名称开始在我们的案例中是get 通过反射调用这些值对象的getters方法非常简单如果你使用Eclipse例如你可以自动为所有的属性产品setters和getters方法对于我们的attributegetter方法是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 { validatorvalidate( this ); } 而且AbstractParameter也实现一些常用的其他方法受保护的方法addOptionalMethod()使得所有的子类型增加一些可选择的方法到optionalGetAttributeMethods HashMap()继承自AbstractParameter使得你能够取得哪些可能传递null的getters方法就像你能想象到的一样你能够增加可选择的方法例如到继承自AbstractParameter的值对象的构造器里 isOptionalMethod()方法用于检测属性是否已经被校验过了 toString()方法实现了一些便利因为值对象可能是由很多属性组成使得在AbstractParameter的子类里不需要写很多的Systemoutprintlns实现这个方法也是使用反射达到目的的 GenericClassAttributeValidator GenericClassAttributeValidator类实现了IClassAttributeValidator接口的validate()方法这个类同时也实现了单态模式validate()的实现看起来象下面这样 public synchronized void validate( AbstractParameter param ) throws AttributeValidatorException { Class clazz = paramgetClass(); Method[] methods = clazzgetMethods(); //Cycle over all methods and call the getters! //Check if the getter result is null //If result is null throw AttributeValidatorException Iterator methodIter = ArraysasList( methods erator(); Method method = null; String methodName = null; while ( methodIterhasNext() ) { method = (Method) methodIternext(); methodName = methodgetName(); if ( methodNamestartsWith( get ) && clazzequals( methodgetDeclaringClass() ) && !paramisOptionalMethod( methodName ) ) { Object methodResult = null; try { methodResult = methodinvoke( param null ); } catch ( IllegalArgumentException e ) { throw new AttributeValidatorException( egetMessage() ); } catch ( IllegalAccessException e ) { throw new AttributeValidatorException( egetMessage() ); } catch ( InvocationTargetException e ) { throw new AttributeValidatorException( egetMessage() ); } if ( methodResult == null ) { String attributeName = methodNamesubstring( )toLowerCase() + methodNamesubstring( methodNamelength() ); String className = clazzgetName(); className = classNamesubstring( classNamelastIndexOf( ) + ); 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( ); paramsetTestParam( new Integer( ) ); paramsetTestParam( new Integer( ) ); paramsetTestParam( new Integer( ) ); paramsetTestParam( new Integer( ) ); 就像你所看到的那样个Integer属性记作Integer和为了校验我们仅仅调用paramvalidateWith( GenericClassAttributeValidatorgetInstance( ) ); 这个校验的结果是 testParam: testParam: testParam: testParam属性没有被校验因为我们记录了它的getter方法为可选的其他所有的方法得到了校验并且结果是正常的现在我们希望看到其中的一个属性值为空这样我们就能检测是否validator能够检测到这个错误我们注释掉下面的行 paramsetTestParam( 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 ( iterhasNext() ) { subParam = iternext(); if ( subParam instanceof AbstractParameter ) { AbstractParameter abstractParam = ( AbstractParameter ) subParam; abstractParamvalidateWith( this ); } } } 集合里的所有没有继承自AbstractParameter类的对象没有被检测因为我们将使用一个不允许null对象的集合实现所以集合实现为我们完成了检测如果你决定使用一个允许null对象的实现那么为while循环的所有其他的对象的一个额外的null检测就是必须的了 else if( subParam == null ) { Systemoutprintln( Error: SubParameter not set in Collection! ); } 值之间的依赖 在一些情况下只有当值对象的其他属性被分配了值一个属性才有可能是可选的属性的可选性 sometimesOptional依赖于actionType属性的值可能action属性持有的值代表了actions例如addSomething = updateSomething = 和 deleteSomthing = 如果action的值是或者sometimesOptional属性不是可选的如果action的值是则是可选的当我们为actionType赋值的时候我们必须设置sometimesOptional的可选性 public void setActionType(int actionType) { thisactionType = actionType; superclearOptionalMethods( ); switch( thisactionType ) { case ActionParameterACTION_ADD : superaddOptionalMethod( getSometimesOptional ); superaddOptionalMethod( getSometimesOptional ); break; case ActionParameterACTION_UPDATE : superaddOptionalMethod( getSometimesOptional ); break; case ActionParameterACTION_REMOVE : superaddOptionalMethod( getSometimesOptional ); superaddOptionalMethod( getSometimesOptional ); superaddOptionalMethod( getSometimesOptional ); break; default : break; } } 你会看到清除可选方法列表是必需的因为如果你给actionType赋值超过一次的话越来越多的方法将作为可选的方法添加进来另外的一个解决方法包括实现一个AddActionParameter一个UpdateActionParameter和一个RemoveActionParameter它们都是从AbstractParameter类继承得来那么你可能不需要actionType属性但是拥有actionType属性的类存在并且经常被使用对该类使用反射非常容易你必须使用Switch语句 展望 现在我们可以考虑继承AbstractParameter的更多的功能——例如范围校验AbstractParamter需要一个数据结构来存储范围值HashMap能够做到它以方法名作为key存储范围对象或者你可以检测是否一个String类型的值包含一些定义的字等等你也可以考虑Perl 的正则表达式所有的这些检测都可以在Validator类里实现它实现了IClassAttributeValidator接口如果你想使用属性的null检测和附加值检测那么你可以写一个子类来继承GenericClassAttributeValidator 在JEE应用里值对象经常被用来在客户端和服务器之间传递业务过程的数据但是如果你仅仅在服务器端校验这些值对象的属性你常常不得不因为一个错误的非可选属性为null而取消业务过程你必须中断业务过程而向客户端发送一个错误页面这是一个好的实践在服务端应用这些validators的同时你也可以以委派的形式在客户端应用它们在客户端检测值对象可以避免不必要的对服务器的请求和降低网络堵塞 写一次使用多次 如果你使用我描述的方法来检测你的值对象的属性你可以永远只增加值对象的属性——不用改变validator它们能够被自动检测是否为null你也可以不用改变validator而改变一个属性的条件而且当然一个已有的validator也能够校验一个未来的值对象如果这个值对象继承AbstractParameter的话还有你也可以不用改变值对象而写一个额外的validators因为validators实现的是Visitor模式这就是所谓的写一次使用多次 |