过滤器模式

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)))  # 张三、李四

要点:筛选逻辑在“条件对象”里,调用方只依赖 Criteriafilter_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 组合,最后在“商品”上做价格、分类、库存的筛选与组合,这样对过滤器模式会掌握得比较扎实。

发表评论