过滤器集合
--一个实现过滤器集合的简易的通用机制
By David Rappoport, JavaWorld.com, 10/18/04
经常地,你必须遍历一个对象集合并基于一些条件(criteria)来过滤它们。JDK提供了有用的机制来排序集合,即Comparator接口。然而,JDK缺少过滤集合的机制。 这篇文章描述了一个仅由一个类和一个接口组成的简单机制,它允许你快速和灵活地过滤集合。当搜索一个集合时,该机制提供了与SQL中的select语句相同的功能。它的隐含的概念是,在遍历集合和过滤集合中的对象时,达到职责的分离。
这里提出的方法有下面的优点:
1、一个核心的过滤器组件的复用产生更清晰的代码。
2、通用过滤组件的复用产生更免于错误的代码。
3、从过滤逻辑中分离出迭代逻辑使你任意地增加和删除过滤器而不影响到其他代码。
4、对于大集合和多个criteria能够获得性能提高。
问题
想象一个搜索接口(mask),用户通过选择众多不同的条件(criteria)来搜索汽车。为了简单地完成这个任务,开发者必须多次遍历(iterate)集合。在每一次遍历中,他必须对集合中的每一个对象执行一定的逻辑来确定该对象是否满足条件(criteria)。通常,这个过程的结果是很难阅读和维护的凌乱的代码。
解决办法
我们定义了一个类CollectionFilter和一个接口FilterCriteria。FilterCriteria仅仅定义了一个方法:public boolean passes(Object o)。这个方法中,集合中的一个对象必须通过一定的测试。如果它通过测试,该方法返回true,否则返回false。CollectionFilter接收一些FilterCriteria作为输入。然后你能调用public void filter(Collection)方法,该方法应用了集合中所有的FilterCriteria,并删除集合中没有通过所有FilterCriteria的任何对象。
CollectionFilter类也定义了public Collection filterCopy(Collection)方法,它完成和filter(Collection)方法相同的任务,但对原始的过滤器做了复制。代码如下(译者添加):
package com.drap.filter;
/**
* <p>Title: FilterCriteria</p>
* <p>Description: A FilterCriteria is added to the CollectionFilter to filter
* all the objects in the collection. </p>
* @author David Rappoport
* @version 1.0
*/
public interface FilterCriteria {
/**
* Implement this method to return true, if a given object in the collection
* should pass this filter.
* Example: Class Car has an attribute color (String). You only want Cars
* whose color equals "red".
* 1) Write the FilterCriteria implementation:
* class RedColorFilterCriteria implements FilterCriteria{
* public boolean passes(Object o){
* return ((Car)o).getColor().equals("red");
* }
* }
* 2) Then add this FilterCriteria to a CollectionFilter:
* CollectionFilter filter = new CollectionFilter();
* filter.addFilterCriteria(new ColorFilterCriteria());
* 3) Now filter:
* filter.filter(carCollection);
* @param o
* @return
*/
public boolean passes(Object o);
}
package com.drap.filter;
import java.lang.reflect.*;
import java.util.*;
/**
* <p>Title: CollectionFilter</p>
* <p>Description: </p>
* @author David Rappoport
* @version 1.0
*/
public class CollectionFilter implements java.io.Serializable {
private ArrayList allFilterCriteria = new ArrayList();
/**
* Adds a FilterCriteria to be used by the filter
* @param filterCriteria
*/
public void addFilterCriteria(FilterCriteria filterCriteria){
allFilterCriteria.add(filterCriteria);
}
/**
* Starts the filtering process. For each object in the collection,
* all FilterCriteria are called. Only if the object passess
* all FilterCriteria it remains in the collection. Otherwise, it is removed.
* @param collection
*/
public void filter(Collection collection){
if(collection != null){
Iterator iter = collection.iterator();
while(iter.hasNext()){
Object o = iter.next();
if(!passesAllCriteria(o)){
iter.remove();
}
}
}
}
/**
* This method does the same as the filter method. However, a copy of
* the original collection is created and filtered. The original collection
* remains unchanged and the copy is returned. Only use this method
* for collection classes that define a default constructor
* @param inputCollection
* @return a filtered copy of the input collection
*/
public Collection filterCopy(Collection inputCollection){
Collection outputCollection = null;
if(inputCollection != null){
outputCollection = (Collection)createObjectSameClass(inputCollection);
outputCollection.addAll(inputCollection);
Iterator iter = outputCollection.iterator();
while(iter.hasNext()){
Object o = iter.next();
if(!passesAllCriteria(o)){
iter.remove();
}
}
}
return outputCollection;
}
/**
* Makes sure the specified object passes all FilterCriteria's passes method.
* @param o
* @return
*/
private boolean passesAllCriteria(Object o){
for(int i = 0; i < allFilterCriteria.size(); i ++){
FilterCriteria filterCriteria = (FilterCriteria)allFilterCriteria.get(i);
if(!filterCriteria.passes(o)){
return false;
}
}
return true;
}
/**
* Call the no arguments constructor of the object passed
* @param object
* @return
*/
public Object createObjectSameClass(Object object){
Class[] NO_ARGS = new Class[0];
Object sameClassObject = null;
try{
if(object != null){
Constructor constructor = object.getClass().getConstructor(NO_ARGS);
sameClassObject = constructor.newInstance(NO_ARGS);
}
}catch(IllegalAccessException e){
//@todo do something
}catch(NoSuchMethodException e){
//@todo do something
}catch(InstantiationException e){
//@todo do something
}catch(Exception e){
//@todo do something
}
return sameClassObject;
}
}
就是这样了!
就像你可能已经注意到了,这个解决方法使用了职责链设计模式的部分思想并应用它们到集合中。
下面的类图说明了类和接口及它们之间的关系。
简单的例子
让我们看一个例子:类car有三个属性:String color, double maxSpeed, boolean fourWheelDrive。
你的应用程序允许基于这些条件来搜索cars:用户能输入她喜欢的颜色,她也能提供她想要car拥有的最大速度,及car是否支持four-wheel驱动。
我们现在创建三个过滤器类来满足用户选择的条件。
1、FilterCriteria的实现如下:
class ColorFilterCriteria implements FilterCriteria{
private String color;
public boolean passes(Object o){
return ((Car)o).getColor().equals(color);
}
}
class MaxSpeedFilterCriteria implements FilterCriteria{
private int maxSpeed;
public boolean passes(Object o){
return ((Car)o).getMaxSpeed() >= maxSpeed;
}
}
class FourWheelDriveFilterCriteria implements FilterCriteria{
private boolean fourWheelDriveRequired;
private boolean fourWheelDriveAllowed;
public boolean passes(Object o){
return fourWheelDriveRequired?((Car)o).isFourWheelDrive():fourWheelDriveAllowed?true:!
((Car)o).isFourWheelDrive();
}
}
2、添加这些FilterCriteria到CollectionFilter中。
CollectionFilter collectionFilter = new CollectionFilter();
filter.addFilterCriteria(new ColorFilterCriteria(color));
filter.addFilterCriteria(new MaxSpeedFilterCriteria(maxSpeed));
filter.addFilterCriteria(new FourWheelDriveFilterCriteria(fourWheelDriveRequired, fourWheelDriveAllowed));
3、调用过滤方法:
collectionFilter.filter(carCollection);
技术的使用(Technicalities)
正如你可能意识到的,和Comparator接口中的compare(Object o1, Object o2)方法相似,FilterCriteria接口中的passes(Object o)方法接收任何类型的对象作为输入参数。这意味着你必须将对象转换成你想要工作的类型并确保你的集合中仅仅包含那种类型的对象。如果这个不是确定的,你能使用instanceof测试指定的对象是否为那种类型。(译者注:根据需要,可以使用范型解决类型转换问题,由于这篇文章实在是太早了,所以作者才有了这段内容。)
有时候,你可能不喜欢为每一个FilterCriteria定义一个单独的类,这种情况下可以使用匿名的内部类。
为了使解决方法简单些,我对这个过滤器并没有使用OR功能。换句话说,每次你增加一个FilterCriteria到你的CollectionFilter,可以当作是SQL中的AND操作。然而,你也能容易的在一个FilterCriteria增加像OR的功能。例如:
class EitherOrColorFilterCriteria implements FilterCriteria{
private String color1;
private String color2;
public boolean passes(Object o){
return ((Car)o).getColor().equals(color1) || ((Car)o).getColor().equals(color2);
}
}
结论
如你所看到的,基于多个条件来过滤集合是简单的。每一个FilterCriteria对象仅对它表示的单个的过滤逻辑负责。CollectionFilter然后联合所有的过滤器来产生想要的结果。对于其它种类的集合处理的相似的解决方法也是可以想到的(除了删除操作)。这个解决方法使用了职责链和迭代器模式:CollectionFilter在集合之上迭代,对于集合中的每一个对象,FilterCriteria对象被当作职责链,而每一个过滤器能决定其它的过滤器是否是需要的。
作者Bio David Rappoport 已经在IBM Global Services 和 Credit Suisse Application Development 工作了五年, 在那儿他开发J2EE领域的软件。他是一个SUN认证的Java 2 程序员,SUN认证的Java 2开发者,SUN认证的J2EE 架构师,和SUN认证的业务组件开发者。他和他的妻子几两个孩子生活在瑞士。
注:该文为本人第一篇和技术相关的翻译文章(论文就不算了)。由于本人水平有限,翻译中难免有不对的地方,欢迎各位朋友指正。该文出处为http://www.javaworld.com/javaworld/jw-10-2004/jw-1018-filter.html,是一篇很早的文章(2004年),内容上也算是比较简单的。