[设计模式/Java] 设计模式之过滤器模式【15】
概述:管道/过滤器模式 := Pipeline/Filter Pattern := 标准模式
模式简介
- 过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式
- 这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。
- 这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
- 管道/过滤器模式的体系结构是面向数据流的软件体系结构。
在管道/过滤器模式中,每个组件(过滤器)都有一组输入/输出, 组件读取输入的数据流,经过内部处理后,产生输出的数据流,该过程主要完成输入流的变换及增量计算。
该模式如下图所示:

- 本模式最典型的应用:编译系统和批处理系统、Java Web Filter(
javax.servlet.Filter/@WebFilter) / Interceptor等。
- 一个普通的编译系统包括词法分析器,语法分析器,语义分析与中间代码生成器,代码优化器,目标代码生成器等一系列对源程序进行处理的过程。
人们可以将编译系统看作一系列过滤器的连接体,按照管道/过滤器的体系结构进行设计。
此外,这种体系结构在其它一些领域也有广泛的应用。
因此,它成为软件工程和软件开发中的一个突出的研究领域。
- 相关文献

- 模式意图
- 用于将对象的筛选过程封装起来,允许使用不同的筛选标准动态地筛选对象。
- 主要解决的问题
- 当需要根据多个不同的条件或标准来筛选一组对象时,过滤器模式提供了一种灵活的方式来定义这些条件,避免在客户端代码中硬编码筛选逻辑。
适用场景
- 当对象集合需要根据不同的标准进行筛选时。
- 当筛选逻辑可能变化,或者需要动态地组合多个筛选条件时。
模式的组成
-
过滤器接口(Filter/Criteria):定义一个接口,用于筛选对象。该接口通常包含一个方法,用于根据特定条件过滤对象。
-
具体过滤器类(Concrete Filter/Concrete Criteria):实现过滤器接口,具体定义筛选对象的条件和逻辑。
-
对象集合(Items/Objects to be filtered):要被过滤的对象集合。这些对象通常是具有共同属性的实例,例如一组人、一组产品等。
-
客户端(Client):使用具体过滤器类来筛选对象集合。客户端将对象集合和过滤器结合起来,以获得符合条件的对象。
实现方式
- 定义筛选接口:创建一个筛选接口,定义一个筛选方法。
- 实现具体筛选器:为每个筛选标准实现筛选接口,封装具体的筛选逻辑。
- 组合筛选器:允许筛选器之间进行组合,形成复杂的筛选逻辑。
关键代码
- 筛选接口:定义筛选方法,如 matches()。
- 具体筛选器类:实现筛选接口,封装具体的筛选逻辑。
- 组合筛选器:实现筛选器的组合逻辑,如逻辑与(AND)、逻辑或(OR)等。
模式特点
优点
- 封装性:筛选逻辑被封装在独立的筛选器对象中。
- 灵活性:可以动态地添加、修改或组合筛选条件。
- 可扩展性:容易添加新的筛选标准,无需修改现有代码。
缺点
- 复杂性:随着筛选条件的增加,系统可能变得复杂。
- 性能问题:如果筛选器组合过于复杂,可能会影响性能。
注意事项
- 当筛选逻辑可能变化或需要根据不同标准动态筛选对象时,考虑使用过滤器模式。
- 在设计时,确保筛选器的接口和实现保持一致,以便于组合和扩展。
- 确保筛选器的组合逻辑正确无误,避免引入逻辑错误。
- 在实现时,考虑性能影响,特别是在处理大量数据时。
案例实践
CASE: 图书管理系统:根据作者、出版年份、类别等不同标准筛选图书
CASE: 在线购物平台:根据价格、品牌、用户评分等条件筛选商品
CASE: 过滤人员信息
我们将创建一个 Person 对象、Criteria 接口和实现了该接口的实体类,来过滤 Person 对象的列表。CriteriaPatternDemo 类使用 Criteria 对象,基于各种标准和它们的结合来过滤 Person 对象的列表。

Person : 被应用标准/过滤器的类
- 创建一个类,在该类上应用标准。
public class Person {
private String name;
private String gender;
private String maritalStatus;
public Person(String name,String gender,String maritalStatus){
this.name = name;
this.gender = gender;
this.maritalStatus = maritalStatus;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getMaritalStatus() {
return maritalStatus;
}
}
Criteria
- 为标准(Criteria)创建一个接口
- Criteria
import java.util.List;
public interface Criteria {
public List<Person> meetCriteria(List<Person> persons);
}
CriteriaMale / CriteriaFemale / CriteriaSingle : 实现标准/过滤器的类
- 创建实现了 Criteria 接口的实体类
- CriteriaMale
import java.util.ArrayList;
import java.util.List;
public class CriteriaMale implements Criteria {
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> malePersons = new ArrayList<Person>();
for (Person person : persons) {
if(person.getGender().equalsIgnoreCase("MALE")){
malePersons.add(person);
}
}
return malePersons;
}
}
- CriteriaFemale
import java.util.ArrayList;
import java.util.List;
public class CriteriaFemale implements Criteria {
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> femalePersons = new ArrayList<Person>();
for (Person person : persons) {
if(person.getGender().equalsIgnoreCase("FEMALE")){
femalePersons.add(person);
}
}
return femalePersons;
}
}
- CriteriaSingle
import java.util.ArrayList;
import java.util.List;
public class CriteriaSingle implements Criteria {
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> singlePersons = new ArrayList<Person>();
for (Person person : persons) {
if(person.getMaritalStatus().equalsIgnoreCase("SINGLE")){
singlePersons.add(person);
}
}
return singlePersons;
}
}
AndCriteria / OrCriteria : 逻辑Filter实现
- AndCriteria
import java.util.List;
public class AndCriteria implements Criteria {
private Criteria criteria;
private Criteria otherCriteria;
public AndCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);
return otherCriteria.meetCriteria(firstCriteriaPersons);
}
}
- OrCriteria
import java.util.List;
public class OrCriteria implements Criteria {
private Criteria criteria;
private Criteria otherCriteria;
public OrCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstCriteriaItems = criteria.meetCriteria(persons);
List<Person> otherCriteriaItems = otherCriteria.meetCriteria(persons);
for (Person person : otherCriteriaItems) {
if(!firstCriteriaItems.contains(person)){
firstCriteriaItems.add(person);
}
}
return firstCriteriaItems;
}
}
Client
- 使用不同的标准(Criteria)和它们的结合来过滤 Person 对象的列表。
import java.util.ArrayList;
import java.util.List;
public class CriteriaPatternDemo {
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>();
persons.add(new Person("Robert","Male", "Single"));
persons.add(new Person("John","Male", "Married"));
persons.add(new Person("Laura","Female", "Married"));
persons.add(new Person("Diana","Female", "Single"));
persons.add(new Person("Mike","Male", "Single"));
persons.add(new Person("Bobby","Male", "Single"));
Criteria male = new CriteriaMale();
Criteria female = new CriteriaFemale();
Criteria single = new CriteriaSingle();
Criteria singleMale = new AndCriteria(single, male);
Criteria singleOrFemale = new OrCriteria(single, female);
System.out.println("Males: ");
printPersons(male.meetCriteria(persons));
System.out.println("\nFemales: ");
printPersons(female.meetCriteria(persons));
System.out.println("\nSingle Males: ");
printPersons(singleMale.meetCriteria(persons));
System.out.println("\nSingle Or Females: ");
printPersons(singleOrFemale.meetCriteria(persons));
}
public static void printPersons(List<Person> persons){
for (Person person : persons) {
System.out.println("Person : [ Name : " + person.getName()
+", Gender : " + person.getGender()
+", Marital Status : " + person.getMaritalStatus()
+" ]");
}
}
}
out
Males:
Person : [ Name : Robert, Gender : Male, Marital Status : Single ]
Person : [ Name : John, Gender : Male, Marital Status : Married ]
Person : [ Name : Mike, Gender : Male, Marital Status : Single ]
Person : [ Name : Bobby, Gender : Male, Marital Status : Single ]
Females:
Person : [ Name : Laura, Gender : Female, Marital Status : Married ]
Person : [ Name : Diana, Gender : Female, Marital Status : Single ]
Single Males:
Person : [ Name : Robert, Gender : Male, Marital Status : Single ]
Person : [ Name : Mike, Gender : Male, Marital Status : Single ]
Person : [ Name : Bobby, Gender : Male, Marital Status : Single ]
Single Or Females:
Person : [ Name : Robert, Gender : Male, Marital Status : Single ]
Person : [ Name : Diana, Gender : Female, Marital Status : Single ]
Person : [ Name : Mike, Gender : Male, Marital Status : Single ]
Person : [ Name : Bobby, Gender : Male, Marital Status : Single ]
Person : [ Name : Laura, Gender : Female, Marital Status : Married ]
CASE: 多条件过滤/查询书籍
- 在有些场景中,需要对一个集合的对象进行过滤。
比如,我有很多本书,想要知道价格为50元以上且出版社包含工业字样的书籍有哪些
Book
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
public class Book implements Serializable {
private static final long serialVersionUID = -6212470156629515269L;
/**
* 书名
*/
private String name;
/**
* 价格
*/
private double price;
/**
* 作者
*/
private List<String> authors;
/**
* 出版社
*/
private String press;
}
传统的过滤做法(硬编码)
- 简单过滤的基本逻辑:
for (Book elem : books) {
// 价格大于50元 且 出版社含有'工业'字样
if (elem.getPrice() > 50 && elem.getPress().contains("工业")) {
filterBooks.add(elem);
}
}
- 示例
import java.util.Collections;
import java.util.List;
import org.springframework.util.CollectionUtils;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BookFilterMain {
public static void main(String[] args) {
List<Book> originBooks = prepareData();
List<Book> filterBooks = filter1(originBooks);
log.info("传统方式-过滤后结果 ==> {}", JSON.toJSONString(filterBooks, true));
}
private static List<Book> prepareData() {
Book book1 = new Book("微服务架构设计模式", 139.00, Lists.newArrayList("克里斯"), "机械工业出版社");
Book book2 = new Book("未来简史", 79.00, Lists.newArrayList("王骥"), "电子工业出版社");
Book book3 = new Book("Java核心", 99.00, Lists.newArrayList("张三", "李四", "王五"), "人民邮电出版社");
return Lists.newArrayList(book1, book2, book3);
}
private static List<Book> filter1(List<Book> books) {
if (CollectionUtils.isEmpty(books)) {
return Collections.emptyList();
}
List<Book> filterBooks = Lists.newArrayList();
for (Book elem : books) {
// 价格大于50元 且 出版社含有'工业'字样
if (elem.getPrice() > 50 && elem.getPress().contains("工业")) {
filterBooks.add(elem);
}
}
return filterBooks;
}
}
- out
由此,我们找到了符合过滤条件的书籍列表
16:17:43.571 [main] INFO BookFilterMain - 传统方式-过滤后结果 ==> [
{
"authors":[
"克里斯"
],
"name":"微服务架构设计模式",
"press":"机械工业出版社",
"price":139.0
},
{
"authors":[
"王骥"
],
"name":"未来简史",
"press":"电子工业出版社",
"price":79.0
}
]
Filter 模式(第1种实现方式)
继续以上述Book为例,大致的结构如下图所示

Filter 接口
import java.util.List;
public interface Filter {
List<Book> apply(List<Book> books);
}
PriceGreaterFilter / ... : 具体Filter实现
PriceGreaterFilter
- PriceGreaterFilter : 如 价格大于50元
import java.util.Collections;
import java.util.List;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;
public class PriceGreaterFilter implements Filter {
private double price;
public PriceGreaterFilter(double price) {
this.price = price;
}
@Override
public List<Book> apply(List<Book> books) {
if (CollectionUtils.isEmpty(books)) {
return Collections.emptyList();
}
List<Book> filterBooks = Lists.newArrayList();
for (Book elem : books) {
if (elem.getPrice() > price) {
filterBooks.add(elem);
}
}
return filterBooks;
}
}
PressContainFilter
- PressContainFilter :如 出版社包含工业字样
import java.util.Collections;
import java.util.List;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;
public class PressContainFilter implements Filter {
private String name;
public PressContainFilter(String name) {
this.name = name;
}
@Override
public List<Book> apply(List<Book> books) {
if(CollectionUtils.isEmpty(books)) {
return Collections.emptyList();
}
List<Book> filterBooks = Lists.newArrayList();
for(Book elem : books) {
if(elem.getPress().contains(this.name)) {
filterBooks.add(elem);
}
}
return filterBooks;
}
}
AndFilter / OrFilter:逻辑Filter实现
- AndFilter
import java.util.List;
public class AndFilter implements Filter {
private Filter first;;
private Filter second;
public AndFilter(Filter first, Filter second) {
this.first = first;
this.second = second;
}
@Override
public List<Book> apply(List<Book> books) {
List<Book> firstFilterBooks = first.apply(books);
List<Book> secondFilterBooks = second.apply(firstFilterBooks);
return secondFilterBooks;
}
}
Client
这样,我们就能通过如下方式完成书本的过滤,找到想要的内容。
// 创建Filter
Filter filter = new AndFilter(new PriceGreaterFilter(50), new PressContainFilter("工业"));
//获取过滤后的结果
List<Book> newFilterBooks = filter.apply(originBooks);
上述过滤器的实现方式,其实现了过滤条件的解耦,但每个具体的过滤器都要遍历一次列表集合,还是比较消耗性能的。
其实,我们只要一次for循环,对循环中的对象按照组合条件的过滤器进行过滤即可。
这样,Filter接口只要一个Book对象,而不需要一个集合,如List。
Filter 模式(第2种实现方式)
接下来,我们来完成第二个实现方式。
Filter 接口
public interface FilterV2 {
boolean apply(Book book);
}
具体Filter实现
PriceGreaterFilterV2
- 价格大于50元
public class PriceGreaterFilterV2 implements FilterV2 {
private final double price;
public PriceGreaterFilterV2(double price) {
this.price = price;
}
@Override
public boolean apply(Book book) {
return book.getPrice() > price;
}
}
PressContainFilterV2
- 出版社包含工业字样
public class PressContainFilterV2 implements FilterV2 {
private final String name;
public PressContainFilterV2(String name) {
this.name = name;
}
@Override
public boolean apply(Book book) {
return book.getPress().contains(name);
}
}
逻辑Filter实现
AndFilter
import java.util.List;
public class AndFilterV2 implements FilterV2 {
private final List<FilterV2> chain;
public AndFilterV2(List<FilterV2> chain) {
this.chain = chain;
}
@Override
public boolean apply(Book book) {
for (FilterV2 filter : chain) {
if (!filter.apply(book)) {
return false;
}
}
return true;
}
}
FilterUtils
这里为了方便,使用
List来替代。同时可以编写一个Util工具类,完成集合的过滤。如:
import java.util.List;
import com.google.common.collect.Lists;
public final class FilterUtils {
private FilterUtils() {
}
public static List<Book> filter(List<Book> books, FilterV2 filterV2) {
List<Book> filterBooks = Lists.newArrayList();
for (Book elem : books) {
if (filterV2.apply(elem)) {
filterBooks.add(elem);
}
}
return filterBooks;
}
}
如何调用?
可以通过如下方式完成调用,获取筛选过的结果集。
FilterV2 filterV2 = new AndFilterV2(
Lists.newArrayList(new PriceGreaterFilterV2(50), new PressContainFilterV2("工业"))
);
List<Book> newFilterV2Books = FilterUtils.filter(filterBooks, filterV2);
采用方法二,相比较方法一,每个具体的过滤器更加简洁,过滤的时候,只要一次遍历即可。
上述给出了2种过滤器模式的实现方式,可以有效应对对象集合的筛选。
但是,其实如果使用了JDK 1.8或者更高的版本,我们可以使用Lambdas语法轻松完成。
Filter 模式(第3种实现方式: 基于 Lambdas 表达式)
- 使用 lambdas 语法轻松搞定
使用 Predicate
定义价格大于50的Predicate
Predicate<Book> greaterThan50 = new Predicate<Book>() {
@Override
public boolean test(Book t) {
return t.getPrice() > 50;
}
};
也可简化为:
Predicate<Book> greaterThan50 = t -> t.getPrice() > 50;
定义出版社包含工业字样的Predicate
Predicate<Book> pressContains = new Predicate<Book>() {
@Override
public boolean test(Book t) {
return t.getPress().contains("工业");
}
};
同理,这也可以简化为:
Predicate<Book> pressContains = t-> t.getPress().contains("工业");
如何调用?
使用
Predicate结合stream可以轻松实现过滤。
filterBooks.stream().filter(greaterThan50.and(pressContains));
延申/研究:Predicate接口组成
- 查看
Predicate源码,可以看到,其定义了一个test方法,以及and、or和negative方法
这些方法的思路其实和上述方式二实现的类似。

CASE : Apache common-io库,针对文件和目录的过滤
Apache common-io库,针对文件和目录的过滤实现,就大量使用了这种设计模式。

- 相关文献
页内搜索关键词:
Filter
CASE: Java Web 的拦截器/过滤器
- 拦截过滤器模式(
Intercepting Filter Pattern)用于对应用程序的请求或响应做一些预处理/后处理。
- 定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。
- 过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。
以下是这种设计模式的实体:
- 过滤器(Filter) - 过滤器在请求处理程序执行请求之前或之后,执行某些任务。
- 过滤器链(Filter Chain) - 过滤器链带有多个过滤器,并在Target上按照定义的顺序执行这些过滤器。
- Target - Target对象是请求处理程序。
- 过滤管理器(FilterManager) - 过滤管理器管理过滤器和过滤器链。
- 客户端(Client) - Client是向Target对象发送请求的对象。
实现思路
- 我们将创建
FilterChain、FilterManager、Target、Client作为表示实体的各种对象。 AuthenticationFilter和DebugFilter表示实体过滤器。InterceptingFilterDemo类使用Client来演示拦截过滤器设计模式。

Step1 创建过滤器接口 Filter
- Filter.java
public interface Filter {
public void execute(String request);
}
Step2 创建实体过滤器
- AuthenticationFilter.java
public class AuthenticationFilter implements Filter {
public void execute(String request){
System.out.println("Authenticating request: " + request);
}
}
- DebugFilter.java
public class DebugFilter implements Filter {
public void execute(String request){
System.out.println("request log: " + request);
}
}
Step3 创建Target
- Target.java
public class Target {
public void execute(String request){
System.out.println("Executing request: " + request);
}
}
Step4 创建过滤器链
- FilterChain.java
import java.util.ArrayList;
import java.util.List;
public class FilterChain {
private List<Filter> filters = new ArrayList<Filter>();
private Target target;
public void addFilter(Filter filter){
filters.add(filter);
}
public void execute(String request){
for (Filter filter : filters) {
filter.execute(request);
}
target.execute(request);
}
public void setTarget(Target target){
this.target = target;
}
}
Step5 创建过滤管理器
- FilterManager.java
public class FilterManager {
FilterChain filterChain;
public FilterManager(Target target){
filterChain = new FilterChain();
filterChain.setTarget(target);
}
public void setFilter(Filter filter){
filterChain.addFilter(filter);
}
public void filterRequest(String request){
filterChain.execute(request);
}
}
Step6 创建客户端Client
- Client.java
public class Client {
FilterManager filterManager;
public void setFilterManager(FilterManager filterManager){
this.filterManager = filterManager;
}
public void sendRequest(String request){
filterManager.filterRequest(request);
}
}
Step7 使用Client来演示拦截过滤器设计模式
- InterceptingFilterDemo.java
public class InterceptingFilterDemo {
public static void main(String[] args) {
FilterManager filterManager = new FilterManager(new Target());
filterManager.setFilter(new AuthenticationFilter());
filterManager.setFilter(new DebugFilter());
Client client = new Client();
client.setFilterManager(filterManager);
client.sendRequest("HOME");
}
}
输出结果:
Authenticating request: HOME
request log: HOME
Executing request: HOME
小结
通过上述内容的说明,想必大家对过滤器模式已经有了一个较好的理解了。
-
过滤模式允许开发人员使用不同的标准来过滤一组数据对象,通过逻辑运算以解耦的方式把它们连接起来,这样可以快速组装过滤条件,而不需要写一大堆整合的
if-else逻辑。 -
如果使用
JDK 1.8及以上版本,使用Predicate甚至不需要写任何的过滤器类即可完成。
如果不支持
lambdas语法,那么上述方法二的实现思想也是一个不错的选择。
其实,common-io中,针对文件和目录的过滤实现,就是这种思想,有兴趣的读者可以自行去了解一下。
Y 推荐文献
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号