Python 设计模式——过滤器模式(Filter / Criteria Pattern)详解
本文面向零基础新手,从概念到单一条件、组合条件(AND/OR/NOT),从简单筛选到可复用筛选逻辑,全方位讲解过滤器/条件模式。
一、什么是过滤器模式(Criteria Pattern)?
1.1 通俗理解
过滤器模式(也叫条件模式 / Criteria Pattern)是一种结构型/行为型的设计思路,核心思想是:
把“筛选条件”做成独立的对象(而不再是散落在 if 里),每个条件对象能对列表做一次过滤;多个条件还可以组合(且、或、非),得到新的复合条件,从而让筛选逻辑可复用、可组合、易扩展。
可以这样类比:
- 不用过滤器:你要从一堆人里找出“男性且年龄>18”的,代码里写
if p.gender=="男" and p.age>18;明天又要“女性或年龄<10”,再写一坨 if;条件一多、组合一多,代码又长又难复用。 - 用过滤器:你定义“男性”条件对象、“年龄>18”条件对象,再定义一个“且”组合:把两个条件组合起来,对列表过滤。要“女性或年龄<10”就组合“女性”和“年龄<10”用“或”。条件即对象,可单独用、可组合。
所以,过滤器模式解决的是:用对象表示筛选条件,支持对列表统一过滤,并支持条件的组合(与、或、非),使筛选逻辑清晰、可复用、易扩展。
1.2 为什么需要过滤器?
场景一:条件复杂、重复出现
多处都要“价格在 100~500”“已上架且库存>0”,若每处写一遍 if,改条件就要改多处。把条件封装成对象,改一处即可,且可复用。
场景二:条件要动态组合
用户勾选“红色 + 大码”“红色或蓝色”“排除缺货”,对应的是“条件1 且 条件2”“条件1 或 条件2”“非 条件”。若用对象表示条件,就可以做 AndCriteria、OrCriteria、NotCriteria,把简单条件拼成复杂条件。
场景三:筛选逻辑要单独测试
条件写成类后,可以单独为“价格区间”“性别”等写单元测试;组合逻辑也可以单独测,比一大坨 if 好测。
1.3 适用场景(什么时候用?)
| 场景 | 说明 |
|---|---|
| 对列表做多种条件筛选 | 商品、用户、订单等,按属性、区间、状态筛选。 |
| 条件需要组合 | 且、或、非,或更复杂的组合。 |
| 条件要复用、易改 | 同一条件在多处使用,或经常调整条件。 |
| 筛选逻辑希望可测试 | 每个条件、每种组合可单独测。 |
简单记忆:凡是“从一堆对象里按条件筛出子集,且条件可能组合、复用”时,可以考虑过滤器/条件模式。
二、过滤器模式的结构(两类角色)
| 角色 | 说明 |
|---|---|
| 条件/过滤器接口(Criteria) | 定义“怎么过滤”:通常有一个方法,如 meet(item) 或 filter(list),表示某对象是否满足条件,或对列表做一次过滤。 |
| 具体条件(Concrete Criteria) | 实现具体规则:如“男性”“价格>100”“红色”。 |
| 组合条件(Composite Criteria) | 把多个条件组合起来:如 AndCriteria、OrCriteria、NotCriteria,内部持有多个条件,对每个对象或整份列表应用“且/或/非”逻辑。 |
关系可以理解为:
- 具体条件 只负责“一条”规则(如性别、价格)。
- 组合条件 持有多个“条件”对象,按与/或/非规则计算是否满足,或对列表做过滤。
- 调用方可以只用一个条件,也可以把多个条件组合后再过滤,无需写一堆 if。
三、示例一:最简单的“条件对象”(人 + 性别/年龄)
先不用组合,只体会:条件 = 对象,对列表做 filter。
3.1 被筛选的实体
class Person:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def __str__(self):
return f"{self.name}({self.gender},{self.age})"
3.2 条件接口 + 具体条件
from abc import ABC, abstractmethod
class Criteria(ABC):
"""条件接口:判断一个人是否满足条件"""
@abstractmethod
def meet(self, person: Person) -> bool:
pass
class MaleCriteria(Criteria):
def meet(self, person: Person) -> bool:
return person.gender == "男"
class FemaleCriteria(Criteria):
def meet(self, person: Person) -> bool:
return person.gender == "女"
class AgeAboveCriteria(Criteria):
def __init__(self, age: int):
self.age = age
def meet(self, person: Person) -> bool:
return person.age >= self.age
3.3 对列表过滤(不用组合)
def filter_by_criteria(persons: list, criteria: Criteria) -> list:
return [p for p in persons if criteria.meet(p)]
persons = [
Person("张三", "男", 20),
Person("李四", "女", 18),
Person("王五", "男", 16),
]
print(filter_by_criteria(persons, MaleCriteria())) # 张三、王五
print(filter_by_criteria(persons, AgeAboveCriteria(18))) # 张三、李四
要点:筛选逻辑在“条件对象”里,调用方只依赖 Criteria 和 filter_by_criteria,换条件就换对象,不写 if。
四、示例二:组合条件(AND / OR / NOT)
在“条件即对象”的基础上,增加组合条件:且、或、非。这样就能用简单条件拼出“男性且年龄≥18”“女性或年龄<10”等。
4.1 组合条件类
class AndCriteria(Criteria):
"""且:两个条件都满足"""
def __init__(self, c1: Criteria, c2: Criteria):
self.c1 = c1
self.c2 = c2
def meet(self, person: Person) -> bool:
return self.c1.meet(person) and self.c2.meet(person)
class OrCriteria(Criteria):
"""或:满足其一即可"""
def __init__(self, c1: Criteria, c2: Criteria):
self.c1 = c1
self.c2 = c2
def meet(self, person: Person) -> bool:
return self.c1.meet(person) or self.c2.meet(person)
class NotCriteria(Criteria):
"""非:取反"""
def __init__(self, criteria: Criteria):
self.criteria = criteria
def meet(self, person: Person) -> bool:
return not self.criteria.meet(person)
4.2 使用组合条件
# 男性 且 年龄>=18
adult_male = AndCriteria(MaleCriteria(), AgeAboveCriteria(18))
print(filter_by_criteria(persons, adult_male)) # 只有张三
# 女性 或 年龄<18
female_or_young = OrCriteria(FemaleCriteria(), NotCriteria(AgeAboveCriteria(18)))
print(filter_by_criteria(persons, female_or_young)) # 李四、王五
# 非男性 = 女性
print(filter_by_criteria(persons, NotCriteria(MaleCriteria()))) # 李四
要点:没有为“成年男性”“女性或未成年”各写一个类,而是用 And/Or/Not 把已有条件组合起来,扩展时只需加新的“简单条件”或新组合。
五、示例三:商品筛选(价格、分类、库存)
把条件用在“商品”上,并保留组合能力。
5.1 实体 + 条件接口
class Product:
def __init__(self, name, category, price, in_stock):
self.name = name
self.category = category
self.price = price
self.in_stock = in_stock
def __str__(self):
return f"{self.name}({self.category},{self.price},库存:{self.in_stock})"
class ProductCriteria(ABC):
@abstractmethod
def meet(self, p: Product) -> bool:
pass
5.2 具体条件
class PriceRangeCriteria(ProductCriteria):
def __init__(self, low: float, high: float):
self.low = low
self.high = high
def meet(self, p: Product) -> bool:
return self.low <= p.price <= self.high
class CategoryCriteria(ProductCriteria):
def __init__(self, category: str):
self.category = category
def meet(self, p: Product) -> bool:
return p.category == self.category
class InStockCriteria(ProductCriteria):
def meet(self, p: Product) -> bool:
return p.in_stock
5.3 组合条件(与 / 或)
class AndProductCriteria(ProductCriteria):
def __init__(self, *criteria_list):
self.criteria_list = criteria_list
def meet(self, p: Product) -> bool:
return all(c.meet(p) for c in self.criteria_list)
class OrProductCriteria(ProductCriteria):
def __init__(self, *criteria_list):
self.criteria_list = criteria_list
def meet(self, p: Product) -> bool:
return any(c.meet(p) for c in self.criteria_list)
5.4 使用
products = [
Product("苹果", "水果", 5.0, True),
Product("键盘", "数码", 299.0, True),
Product("香蕉", "水果", 3.0, False),
]
# 价格 1~100 且 有库存
c = AndProductCriteria(PriceRangeCriteria(1, 100), InStockCriteria())
print([str(p) for p in products if c.meet(p)])
# 水果 或 价格>200
c2 = OrProductCriteria(CategoryCriteria("水果"), PriceRangeCriteria(200, 9999))
print([str(p) for p in products if c2.meet(p)])
六、示例四:条件对象直接返回“过滤后的列表”
有的写法让条件对象不仅提供 meet(item),还提供 filter(items),直接返回过滤后的列表,调用更省事。
class Criteria(ABC):
@abstractmethod
def meet(self, person: Person) -> bool:
pass
def filter(self, persons: list) -> list:
"""默认实现:根据 meet 过滤列表"""
return [p for p in persons if self.meet(p)]
# 使用
result = MaleCriteria().filter(persons)
这样调用方可以写 criteria.filter(list) 而不必自己写列表推导;组合条件因为都实现 meet,自动具备 filter 能力。
七、过滤器模式 vs 其他
| 对比 | 过滤器/条件模式 | 策略模式 | 简单 if |
|---|---|---|---|
| 目的 | 筛选列表、条件可组合 | 算法/行为可替换 | 直接写条件 |
| 条件 | 对象,可组合 | 策略对象,通常不组合 | 散落在代码里 |
| 适用 | 列表过滤、多条件与/或/非 | 一种行为多种实现 | 条件简单、不复用 |
简单记忆:过滤器模式把“条件”做成对象并支持组合,适合“从列表里按条件筛 + 条件会复用、会组合”的场景。
八、常见问题与注意点
8.1 条件接口用 meet(item) 还是 filter(list)?
- meet(item):只判断单条是否满足,组合条件容易实现(对每个子条件调 meet 再 and/or/not),推荐作为核心方法。
- filter(list):可直接在接口里提供默认实现:
return [x for x in list if self.meet(x)],方便调用。
8.2 组合条件支持 3 个以上条件吗?
可以。AndCriteria/OrCriteria 可以设计成接收 *criteria,内部用 all(...) / any(...) 即可,如上面商品示例。
8.3 和列表推导 [x for x in list if …] 的区别?
列表推导适合一次性、简单条件。过滤器模式适合:条件要复用、要组合、要单独测试、要从配置或用户选择动态拼时,用对象表示条件更清晰、可扩展。
九、小结
- 过滤器/条件模式:把筛选条件做成对象,对列表做过滤;支持用 And/Or/Not 等组合条件,使筛选逻辑可复用、可组合、易扩展。
- 角色:条件接口(如
meet(item)/filter(list))、具体条件、组合条件(且、或、非)。 - 典型用途:用户/商品/订单等列表的多条件筛选,且条件会组合、会复用。
- 实现要点:每个条件实现“是否满足单条”;组合条件内部调用子条件的 meet 再做逻辑运算。
建议先写“人 + 性别/年龄”的简单条件与 filter 函数,再加 And/Or/Not 组合,最后在“商品”上做价格、分类、库存的筛选与组合,这样对过滤器模式会掌握得比较扎实。