对Web页面上的输入进行校验是我们必须的工作,既简单又繁琐.本文结合Visitor模式和反射,给出了一个该校验的一劳永逸的解决方案;即使你不想在你的实际应用中使用该方案,本文对我们应用Visitor模式和反射也就极富有启发意义的.
校验值对象
——应用Visitor模式和反射
概要
值对象是一些单一的参数,用来联合一系列的对象——在大多数的情况下,在一个方法调用里有各种各样的参数。这些参数描述了一个大数量级的属性,通常,这些属性需要单独检测,而且大多数情况下是检测其是否为null。通常,这些检测带出来了大量的代码行。这篇文章描述了如何实现基于著名的Visitor模式和反射的值对象。
在业务过程中,你通常有一些属性不能为空,而另外一些则没有这样的要求。在那些必须有实例的属性的案例中,你不得不实现如下所示的检测:
if( attribute1 == null )
{
throw new Attribute1IsNullException()
}
{
throw new Attribute1IsNullException()
}
如果值对象有N个属性,你将会得到如下所示的代码:
if( attribute1 == null )
{
throw new Attribute1IsNullException()
}
if( attribute2 == null )
{
throw new Attribute2IsNullException()
}
...
if( attribute N == null )
{
throw new AttributeNIsNullException()
}
{
throw new Attribute1IsNullException()
}
if( attribute2 == null )
{
throw new Attribute2IsNullException()
}
...
if( attribute N == null )
{
throw new AttributeNIsNullException()
}
结果:一大堆的IF语句,但是你不得不把它们全部打出来。
现在假设校验的数量从10增加到25,因为有15个新增的用例必须在一个循环中实现。你是不是失去了勇气?用来减少这些检测的一个有效的方法是将他们从值对象类移到值对象的校验类。
从这个观点看来,你可能承认你永远执行相同的检测。唯一的不同是属性的名称和类型。在大多数情况下,类型不让人感兴趣,因为由编译器检测它。还有重要的一点需要确认:接收这些属性的值的方法都由同一个名称开始,在我们的案例中,是get。
通过反射调用这些值对象的getters方法非常简单。如果你使用Eclipse,例如,你可以自动为所有的属性产品setters和getters方法。对于我们的
attribute1
,getter方法是getAttribute1()
,setter方法为setAttribute1(Integer attributeValue)
。如果attribute1
是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 );
}
{
validator.validate( this );
}
而且,
AbstractParameter
也实现一些常用的其他方法。受保护的方法:addOptionalMethod()
使得所有的子类型增加一些可选择的方法到optionalGetAttributeMethods HashMap()
。继承自AbstractParameter
使得你能够取得哪些可能传递null的getters方法。就像你能想象到的一样,你能够增加可选择的方法,例如,到继承自AbstractParameter
的值对象的构造器里。isOptionalMethod()
方法用于检测属性是否已经被校验过了。toString()
方法实现了一些便利,因为值对象可能是由很多属性组成。使得在AbstractParameter
的子类里,不需要写很多的System.out.println
s实现。这个方法也是使用反射达到目的的。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 ).iterator();
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 )
{
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 ).iterator();
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( 3, 4 ).toLowerCase() +
methodName.substring( 4, methodName.length() );
String className = clazz.getName();
className = className.substring( className.lastIndexOf( '.' ) + 1 );
Integer errorNumber = new Integer( 1000 );
throw new AttributeValidatorException( "Error: " + errorNumber + " "
+ attributeName + " in " + className +" is null!!!");
}
}
}
}
throw new AttributeValidatorException( e.getMessage() );
}
if ( methodResult == null )
{
String attributeName = methodName.substring( 3, 4 ).toLowerCase() +
methodName.substring( 4, methodName.length() );
String className = clazz.getName();
className = className.substring( className.lastIndexOf( '.' ) + 1 );
Integer errorNumber = new Integer( 1000 );
throw new AttributeValidatorException( "Error: " + errorNumber + " "
+ attributeName + " in " + className +" is null!!!");
}
}
}
}
首先,就像你在代码里看到的那样,我们从值对象里取得所有的方法。然后,我们遍历所有方法的集合。如果方法以get开头,便是
AbstractParameter
的子类型,而不是可选择的方法。我们通过反射调用getter方法,并且检测它的结果。如果结果是null,那么这就是一个错误;如果不是,便是正常情况。那些可选择的方法和继承自父类的方法不会被执行。测试我们的类
现在,我们实现了我们所需要的所有的类和接口。我们必须做一些测试来检验我们的类是否能够正常工作。为了做到这一点,我们写了一点小的测试类和一个main方法来运行测试。
TestParameter
TestParameter
类继承自AbstractParameter
,并且包括了一些需要校验的私有属性:很简单的4个Integer属性。Optional attributes
为了识别可选的属性没有被检测,我们定义了为属性:
testParam3
可选的getter方法。为了这个目的,我们通过TestParameter
的构造器里的addOptionalMethod(methodName)
方法将这个getter方法输入到父类AbstractParameter
的可选方法map里。校验框架是如何工作的
为了测试,我们在
TestParameter
里使用如下方式输入:TestParameter param = new TestParameter( );
param.setTestParam1( new Integer( 1 ) );
param.setTestParam2( new Integer( 2 ) );
param.setTestParam3( new Integer( 3 ) );
param.setTestParam4( new Integer( 4 ) );
param.setTestParam1( new Integer( 1 ) );
param.setTestParam2( new Integer( 2 ) );
param.setTestParam3( new Integer( 3 ) );
param.setTestParam4( new Integer( 4 ) );
就像你所看到的那样,4个
Integer
属性记作Integer
1,2,3和4。为了校验,我们仅仅调用:param.validateWith( GenericClassAttributeValidator.getInstance( ) );这个校验的结果是:
testParam1: 1
testParam2: 2
testParam4: 4
testParam2: 2
testParam4: 4
testParam3
属性没有被校验,因为我们记录了它的getter方法为可选的。其他所有的方法得到了校验,并且结果是正常的。现在,我们希望看到其中的一个属性值为空,这样我们就能检测是否validator能够检测到这个错误。我们注释掉下面的行:param.setTestParam2( new Integer( 2 ) );
我们重新开始测试以后,得到如下的结果:
testParam1: 1
Error: testParam2 in TestParameter is null!!!
testParam4: 4
Error: testParam2 in TestParameter is null!!!
testParam4: 4
现在我们看到了validator已经检测到了这个没有赋值的属性。
如果属性类型为集合类型,将会怎么样呢?
如果属性类型为集合,它仍然会检测这个集合是否为空。但是,可能检测集合是否为null并不是你想要的。在大多数情况下,你希望检测集合里的对象是否为null。如果集合的实现不允许null对象,你不需要关心这些在
GenericClassAttributeValidator
里的null对象继承自AbstractParameter
。一些为集合保持继承自AbstractParameter
的对象的辅助代码看起来如下所示:if ( methodResult instanceof Collection )
{
Collection col = (Collection) methodResult;
Iterator iter = col.iterator();
Object subParam = null;
while ( iter.hasNext() )
{
subParam = iter.next();
if ( subParam instanceof AbstractParameter )
{
AbstractParameter abstractParam = ( AbstractParameter ) subParam;
abstractParam.validateWith( this );
}
}
}
{
Collection col = (Collection) methodResult;
Iterator iter = col.iterator();
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!" );
}
{
System.out.println( "Error: SubParameter not set in Collection!" );
}
值之间的依赖
在一些情况下,只有当值对象的其他属性被分配了值,一个属性才有可能是可选的。属性的“可选性”
sometimesOptional
依赖于actionType
属性的值。可能action
属性持有的值代表了actions:例如addSomething
= 1
, updateSomething
= 2
, 和 deleteSomthing
= 3
。如果action
的值是1或者3,sometimesOptional
属性不是可选的;如果action的值是2,则是可选的。当我们为actionType
赋值的时候,我们必须设置sometimesOptional
的可选性:public void setActionType(int actionType)
{
this.actionType = actionType;
super.clearOptionalMethods( );
switch( this.actionType )
{
case ActionParameter.ACTION_ADD :
super.addOptionalMethod( "getSometimesOptional4" );
super.addOptionalMethod( "getSometimesOptional5" );
break;
case ActionParameter.ACTION_UPDATE :
super.addOptionalMethod( "getSometimesOptional1" );
break;
case ActionParameter.ACTION_REMOVE :
super.addOptionalMethod( "getSometimesOptional1" );
super.addOptionalMethod( "getSometimesOptional2" );
super.addOptionalMethod( "getSometimesOptional3" );
break;
default :
break;
}
}
{
this.actionType = actionType;
super.clearOptionalMethods( );
switch( this.actionType )
{
case ActionParameter.ACTION_ADD :
super.addOptionalMethod( "getSometimesOptional4" );
super.addOptionalMethod( "getSometimesOptional5" );
break;
case ActionParameter.ACTION_UPDATE :
super.addOptionalMethod( "getSometimesOptional1" );
break;
case ActionParameter.ACTION_REMOVE :
super.addOptionalMethod( "getSometimesOptional1" );
super.addOptionalMethod( "getSometimesOptional2" );
super.addOptionalMethod( "getSometimesOptional3" );
break;
default :
break;
}
}
你会看到清除可选方法列表是必需的,因为如果你给
actionType
赋值超过一次的话,越来越多的方法将作为可选的方法添加进来。另外的一个解决方法包括实现一个AddActionParameter
,一个UpdateActionParameter
和一个RemoveActionParameter
,它们都是从AbstractParameter
类继承得来。那么你可能不需要actionType
属性。但是拥有actionType
属性的类存在并且经常被使用,对该类使用反射非常容易,你必须使用Switch语句。展望
现在,我们可以考虑继承
AbstractParameter
的更多的功能——例如,范围校验。AbstractParamter
需要一个数据结构来存储范围值。HashMap
能够做到,它以方法名作为key存储范围对象。或者你可以检测是否一个String类型的值包含一些定义的字。等等。你也可以考虑Perl 5的正则表达式。所有的这些检测都可以在Validator
类里实现,它实现了IClassAttributeValidator
接口。如果你想使用属性的null检测和附加值检测,那么你可以写一个子类来继承GenericClassAttributeValidator
。写一次,使用多次
如果你使用我描述的方法来检测你的值对象的属性,你可以永远只增加值对象的属性——不用改变validator,它们能够被自动检测是否为null。你也可以不用改变validator而改变一个属性的条件。而且,当然,一个已有的validator也能够校验一个未来的值对象,如果这个值对象继承
AbstractParameter
的话。还有,你也可以不用改变值对象而写一个额外的validators,因为validators实现的是Visitor模式。这就是所谓的写一次,使用多次。关于作者
Sascha Frevel在德国的Zweibrücken的the University of Applied Sciences学习计算机科学。他自从1998年以来使用Java编程。自从2001以来,他的主要兴趣在于J2EE的应用设计和开发。他现在的工作身份是the software subsidiary company of ProsiebenSat1 Media AG的Java/J2EE开发人员和技术经理、软件架构师,这是德国的一家最大的corporate TV集团。
资源
- Download the source code that accompanies this article:
http://www.javaworld.com/javaworld/jw-06-2004/validate/jw-0614-validate.zip - Design Patterns, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Addison-Wesley, January 1995; ISBN: 0201633612):
http://www.amazon.com/exec/obidos/ASIN/0201633612/javaworld - Core J2EE Patterns, Deepak Alur, Dan Malks, John Crupi (Prentice Hall PTR, June 2003: ISBN: 0131422464):
http://www.amazon.com/exec/obidos/ASIN/0131422464/javaworld - J2EE Patterns, Adam Bien (Addison-Wesley, February 2002; ISBN: 382731903X):
http://www.amazon.de/exec/obidos/ASIN/382731903X/javaworld - "Java Tip 98: Reflect on the Visitor Design Pattern," Jeremy Blosser (JavaWorld):
http://www.javaworld.com/javatips/jw-javatip98.html - For more on design patterns, please browse these JavaWorld resources:
- The Design Patterns section of our Topical Index:
http://www.javaworld.com/channel_content/jw-patterns-index.shtml - David Geary's Java Design Patterns column:
http://www.javaworld.com/columns/jw-java-design-patterns-index.shtml
- The Design Patterns section of our Topical Index:
- Browse the Development Tools section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-tools-index.shtml
发表评论
-
Google Finance Chart 类似例子收集
2010-11-10 11:51 2399Google威武,Google开发的这个组件打开速度非常快,用 ... -
基于RBAC的权限设计模型
2007-10-28 13:51 8379c: 1 RBAC 介绍 RBAC ... -
基于角色管理的系统访问控制
2007-10-28 13:50 1779基于角色管理的系统访 ... -
使用DWR实现搜索自动提示(三)
2007-06-28 21:59 2419事件处理类 js 代码 //控制器类 ... -
使用DWR实现搜索自动提示(二)
2007-06-28 21:49 2884js 代码 TextSuggest = ... -
使用DWR实现搜索自动提示(一)
2007-06-28 21:42 3473最近在公司实现了一个类似google suggest的功能,拿 ... -
详细解析Java中抽象类和接口的区别(来自:java060515)
2007-05-11 20:35 1336在Java语言中, abstr ...
相关推荐
本项目基于“visitor模式”和“访问者模式”,实现了用于计算表达式的求值引擎,这涉及到一种将数学表达式转化为数据结构(表达式树)的方法,然后通过遍历该树来执行计算。下面我们将详细探讨这些概念。 1. **...
在本篇文章中,我们将深入探讨面向对象设计模式中的一个非常重要的模式——**Visitor(访问者)模式**。此模式属于行为型模式的一种,主要应用于在不修改现有类的情况下向其添加新功能。这对于保持代码的灵活性和可...
《设计模式——Java语言中的应用》是一本专为Java开发者深入理解面向对象设计而编写的经典书籍。设计模式是软件工程领域中经过实践验证的、解决常见问题的有效方案,它们代表了在特定上下文中,针对特定问题的最优...
访问者模式(Visitor)是一种行为设计模式,它允许在不修改对象结构的前提下向对象结构中的元素添加新的操作。这种模式将算法与数据结构分离,使得算法可以独立于数据结构进行变化,增强了系统的可扩展性。 在C++中...
模板方法模式-Template Method Pattern 模板方法模式深度解析(一) 模板方法模式深度解析(二) 模板方法模式深度解析(三) 访问者模式-Visitor Pattern 操作复杂对象结构——访问者模式(一) 操作复杂对象结构...
Visitor模式是设计模式中的一种行为模式,它在对象结构中引入了一个访问者角色,使得访问者能够对结构中的每个元素进行操作,而不改变元素本身的行为。这种模式允许我们在不修改已有类的情况下增加新的操作,遵循了...
访问者模式(Visitor Pattern)是一种行为设计模式,它使你能在不修改对象结构的前提下,为对象添加新的操作。这种模式在处理具有复杂逻辑和多种类型的对象结构时特别有用,因为它允许你在不改变原有结构的情况下,...
**访问者模式(Visitor)详解** 访问者模式是一种行为设计模式,它使你可以在不修改对象结构的情况下,为对象添加新的操作。这种模式的核心在于将数据结构与对这些数据的操作解耦,使得增加新的操作变得容易,同时...
struts2中Visitor校验器以及域对象级别上验证和使用上下文优化的使用,我自己写的一个完整代码,详细的展示了如何使用Visitor校验器以及怎样在域对象上验证和注意事项,代码解压后可以直接部署到MyEclipse上运行,...
书名: 设计模式可复用面向对象软件的基础 英文原书名: Design Patterns:Elements of Reusable Object-Oriented software 作者: Erich Gamma 等 译者: 李英军 马晓星 蔡敏 刘建中 书号: 7-111-07575-7 页码: 254 定价...
压缩包中的"visitor"文件可能包含对该模式的详细代码示例、讲解文档或者案例分析,供学习者深入理解访问者模式的工作原理和实际应用。通过阅读这些材料,你可以更全面地掌握这一设计模式,以便在未来开发中灵活运用...
- 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 这些设计模式不仅在PHP中适用,也是跨语言的编程思想,可以帮助开发者...
### 设计模式精解——GoF23中设计模式解析 #### 重要性与起源 设计模式是软件工程领域的一项重要研究,它提供了一系列解决常见软件设计问题的模板。GoF23指的是由Erich Gamma、Richard Helm、Ralph Johnson和John ...
2. **结构型模式**(Structural Patterns):主要关注如何组合类和对象以实现更大的结构,包括适配器模式(Adapter)、桥接模式(Bridge)、装饰器模式(Decorator)、外观模式(Facade)、享元模式(Flyweight)、...
2. **模式的实现**:学习如何在Java代码中实现各种设计模式,包括类图和对象图的绘制,以及相应的代码结构。 3. **模式间的相互关系**:了解不同设计模式之间的关联和区别,比如装饰器和代理模式的区别,或者单例...
### 设计模式-访问者(Visitor)模式详解和应用 #### 一、访问者模式简介 访问者模式(Visitor Pattern)是一种行为型设计模式,它允许我们向一组已存在的类添加新的行为,而无需修改这些类。这种模式的核心思想是在...
也许最开始出现这种模式,是因为另外的原因: 我有一堆数据放在一个库里头,不想让其它人拿着, 如果你要用数据干活,那你就把函数指针给我,我来替你使用这个数据。...然后人们就说,这是visitor模式。
在访问者模式中,有两个主要的角色:**Element(元素)** 和 **Visitor(访问者)**。元素是对象结构中的基本组成单位,它们拥有自己的职责,并且可以接受访问者的访问。访问者则定义了一个访问元素的接口,每个元素...