paramtype="java.lang.Object"/>
上述所做的是指示Digester创建一个新的Person实例,当它遇到一个person元素时,调用add()来将Person对象加入到一个ArrayList中,设置person元素中相匹配的属性,并从下一级元素name和age中设置name和age的属性。
现在你已经看到了Person类,会被解析的文档,和以XML的形式定义的Digester规则。
现在你需要创建一个由person-rules.xml定义了规则的Digester的实例。
下面的代码创建了一个Digester,通过将person-rules.xml的URL传递给DigesterLoader
既然person-rules.xml文件是与解析它的类在同一个包内的类路径资源,URL可以通过getClass().getResource()来得到。
DigesterLoader然后解析规则并将它加到新创建的Digester上:
importmons.digester.Digester;
importmons.digester.xmlrules.DigesterLoader;
//从XML规则集中配置Digester
URLrules=getClass().getResource("./person-rules.xml");
Digesterdigester=
DigesterLoader.createDigester(rules);
//将空的List推入到Digester的堆栈
Listpeople=newArrayList();
digester.push(people);
//解析XML文档
InputStreaminput=newFileInputStream("data.xml");
digester.parse(input);
一旦Digester完成对data.xml的解析,三个Person对象将会在ArrayListpeople中。
与将规则定义在XML不同的方法是使用简便的方法将它们加入到一个Digester实例中。
大多数文章和例子都用这种方法,使用addObjectCreate()和addBeanPropertySetter()这样的方法来将规则加入中Digester上。
下面的代码加入了与定义在person-rules.xml中相同的规则:
digester.addObjectCreate("people/person",
Person.class);
digester.addSetNext("people/person",
"add",
"java.lang.Object");
digester.addBeanPropertySetter("people/person",
"name");
digester.addBeanPropertySetter("people/person",
"age");
如果你曾经发现自己正在用一个有着2500行代码的类,用SAX来解析一个巨大的XML文档,或者使用DOM或JDOM的完整的一个集合类,你就会理解XML的解析比它应该做的要复杂的多,就大多数情况来说。
如果你正在建一个有着严格的速度和内存要求的高效的系统,你会需要SAX解析器的速度。
如果你需要DOM级别3的复杂度,你会需要像ApacheXerces的解析器。
但如果你只是简单的试图将几个XML文档解析到对象中去的话,看一下CommonsDigester,并把你的规则定义在一个XML文件中。
任何时候你都应该将配置信息从硬编码中移出来。
我会建议你在一个XML文件中定义规则并从文件系统或类路径中装入它。
这样可以使你的程序更好地适应XML文档以及对象模型的变化。
有关在XML文件中定义Digester规则的更多的资料,参看JakartaCommonsCookbook一书的6.2节,“将XML文档转换为对象”
2.CommonsCollections中的算子
算子成为CommonsCollections3.1中的有趣的部分有两个原因:
它们没有得到应得的重视并且它们有改变你编程的方式的潜力。
算子只是一个奇特的名字,它代表了一个包装了函数的对象—一个“函数对象”。
当然,它们不是一回事。
如果你曾经使用过C和C++的方法指针,你就会理解算子的威力。
一个算子是一个对象—一个Predicate,一个Closure,一个Transformer。
Predicates求对象的值并返回一个boolean,Transformer求对象的值并返回新对象,Closure接受对象并执行代码。
算子可以被组合成组合算子来模仿循环,逻辑表达式,和控制结构,并且算子也可以被用来过滤和操作集合中的元素。
在这么短的篇幅中解释清楚算子是不可能的,所以跳过介绍,我将会通过使用和不使用算子来解决同一问题(解释算子)。
在这个例子中,从一个ArrayList中而来的Student对象会被排序到两个List中,如果他们符合某种标准的话。
成绩为A的学生会被加到honorRollStudents(光荣榜)中,得D和F的学生被加到problemStudents(问题学生)list中。
学生分开以后,系统将会遍历每个list,给加入到光荣榜中学生一个奖励,并安排与问题学生的家长谈话的时间表。
下面的代码不使用算子实现了这个过程:
ListallStudents=getAllStudents();
//创建两个ArrayList来存放荣誉学生和问题学生
ListhonorRollStudents=newArrayList();
ListproblemStudents=newArrayList();
//遍历所有学生,将荣誉学生放入一个List,问题学生放入另一个
IteratorallStudentsIter=allStudents.iterator();
while(allStudentsIter.hasNext()){
Students=(Student)allStudentsIter.next();
if(s.getGrade().equals("A")){
honorRollStudents.add(s);
}elseif(s.getGrade().equals("B")&&
s.getAttendance()==PERFECT){
honorRollStudents.add(s);
}elseif(s.getGrade().equals("D")||
s.getGrade().equals("F")){
problemStudents.add(s);
}elseif(s.getStatus()==SUSPENDED){
problemStudents.add(s);
}
}
//对于的有荣誉学生,增加一个奖励并存储到数据库中
IteratorhonorRollIter=
honorRollStudents.iterator();
while(honorRollIter.hasNext()){
Students=(Student)honorRollIter.next();
//给学生记录增加一个奖励
s.addAward("honorroll",2005);
Database.saveStudent(s);
}
//对所有问题学生,增加一个注释并存储到数据库中
IteratorproblemIter=problemStudents.iterator();
while(problemIter.hasNext()){
Students=(Student)problemIter.next();
//将学生标记为需特殊注意
s.addNote("talktostudent",2005);
s.addNote("meetingwithparents",2005);
Database.saveStudent(s);
}
上述例子是非常过程化的;要想知道Student对象发生了什么事必须遍历每一行代码。
例子的第一部分是基于成绩和考勤对Student对象进行逻辑判断。
第二部分对Student对象进行操作并存储到数据库中。
像上述这个有着50行代码程序也是大多程序所开始的—可管理的过程化的复杂性。
但是当需求变化时,问题出现了。
一旦判断逻辑改变,你就需要在第一部分中增加更多的逻辑表达式。
举例来说,如果一个有着成绩B和良好出勤记录,但有五次以上的留堂记录的学生被判定为问题学生,那么你的逻辑表达式将会如何处理?
或者对于第二部分中,只有在上一年度不是问题学生的学生才能进入光荣榜的话,如何处理?
当例外和需求开始改变进而影响到过程代码时,可管理的复杂性就会变成不可维护的面条式的代码。
从上面的例子中回来,考虑一下那段代码到底在做什么。
它在一个List遍历每一个对象,检查标准,如果适用该标准,对此对象进行某些操作。
上述例子可以进行改进的关键一处在于从代码中将标准与动作解藕开来。
下面的两处代码引用以一种非常不同的方法解决了上述的问题。
首先,荣誉榜和问题学生的标准被两个Predicate对象模型化了,并且加之于荣誉学生和问题学生上的动作也被两个Closure对象模型化了。
这四个对象如下定义:
importmons.collections.Closure;
importmons.collections.Predicate;
//匿名的Predicate决定一个学生是否加入荣誉榜
PredicateisHonorRoll=newPredicate(){
publicbooleanevaluate(Objectobject){
Students=(Student)object;
return((s.getGrade().equals("A"))||
(s.getGrade().equals("B")&&
s.getAttendance()==PERFECT));
}
};
//匿名的Predicate决定一个学生是否是问题学生
PredicateisProblem=newPredicate(){
publicbooleanevaluate(Objectobject){
Students=(Student)object;
return((s.getGrade().equals("D")||
s.getGrade().equals("F"))||
s.getStatus()==SUSPENDED);
}
};
//匿名的Closure将一个学生加入荣誉榜
ClosureaddToHonorRoll=newClosure(){
publicvoidexecute(Objectobject){
Students=(Student)object;
//对学生增加一个荣誉记录
s.addAward("honorroll",2005);
Database.saveStudent(s);
}
};
//匿名的Closure将学生标记为需特殊注意
ClosureflagForAttention=newClosure(){
publicvoidexecute(Objectobject){
Students=(Student)object;
//标记学生为需特殊注意
s.addNote("talktostudent",2005);
s.addNote("meetingwithparents",2005);
Database.saveStudent(s);
}
};
这四个匿名的Predicate和Closure是从作为一个整体互相分离的。
flagForAttention(标记为注意)并不知道什么是确定一个问题学生的标准。
现在需要的是将正确的Predicate和正确的Closure结合起来的方法,这将在下面的例子中展示:
importmons.collections.ClosureUtils;
importmons.collections.CollectionUtils;
importmons.collections.functors.NOPClosure;
MappredicateMap=newHashMap();
predicateMap.put(isHonorRoll,addToHonorRoll);
predicateMap.put(isProblem,flagForAttention);
predicateMap.put(null,ClosureUtils.nopClosure());
ClosureprocessStudents=
ClosureUtils.switchClosure(predicateMap);
CollectionUtils.forAllDo(allStudents,processStudents);
在上面的代码中,predicateMap将Predicate与Closure进行了配对;如果一个学生满足作为键值的Predicate的条件,那么它将把它的值传到作为Map的值的Closure中。
通过提供一个NOPClosure值和null键对,我们将把不符合任何Predicate条件的Student对象传给由ClosureUtils调用创建的“不做任何事”或者“无操作”的NOPClosure。
一个SwitchClosure,processStudents,从predicateMap中创建。
并且通过使用CollectionUtils.forAllDo()方法,将processStudentsClosure应用到allStudents中的每一个Student对象上。
这是非常不一样的处理方法;记住,你并没有遍历任何队列。
而是通过设置规则和因果关系,以及CollectionUtils和SwitchClosur来完成了这些操作。
当你将使用Predicate的标准与使用Closure的动作将分离开来时,你的代码的过程式处理就少了,而且更容易测试了。
isHonorRollPredicate能够与addToHonorRoll