过滤器模式练习

过滤器模式练习——简单条件、组合条件与商品筛选

按《Python 设计模式——过滤器模式》的建议,通过三道练习巩固:① 简单条件对象(人 + 性别/年龄)+ 过滤函数;② 组合条件 AND/OR/NOT;③ 商品筛选(价格、分类、库存)+ 多条件组合。每步都有完整可运行代码和验证要点。


练习一:简单条件对象(人 + 性别/年龄)

目的

体会条件即对象:定义 Criteria 接口(如 meet(person) -> bool),每种规则一个具体条件类;对列表过滤时只依赖条件对象,不写 if 性别、if 年龄。

要求

  • 定义 Person:有 name、gender、age。
  • 定义 Criteria 抽象类,有抽象方法 meet(self, person: Person) -> bool
  • 实现 MaleCriteriaFemaleCriteriaAgeAboveCriteria(age)(年龄>=某值)。
  • 写函数 filter_by_criteria(persons: list, criteria: Criteria) -> list:返回列表中所有 meet 为 True 的人。
  • 客户代码:构造 3 个 Person(如张三男20、李四女18、王五男16),分别用 MaleCriteria()AgeAboveCriteria(18) 过滤,验证返回列表正确。

参考答案

from abc import ABC, abstractmethod

class Person:
    def __init__(self, name: str, gender: str, age: int):
        self.name = name
        self.gender = gender
        self.age = age

    def __str__(self):
        return f"{self.name}({self.gender},{self.age})"

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

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([str(p) for p in filter_by_criteria(persons, MaleCriteria())])        # 张三、王五
print([str(p) for p in filter_by_criteria(persons, AgeAboveCriteria(18))])  # 张三、李四

验证要点

  • MaleCriteria 过滤后得到 2 人(张三、王五);AgeAboveCriteria(18) 过滤后得到 2 人(张三、李四)。
  • 确认:filter_by_criteria 只依赖 Criteria 接口,不写 if person.gender、if person.age;换条件只换条件对象

练习二:组合条件(AND / OR / NOT)

目的

在简单条件基础上增加组合条件AndCriteriaOrCriteriaNotCriteria,用它们把已有条件组合成“男性且年龄>=18”“女性或年龄<18”等,无需为每种组合写新类。

要求

  • 在练习一的基础上,实现 AndCriteria(c1, c2):meet 返回 c1.meet(p) and c2.meet(p)
  • 实现 OrCriteria(c1, c2):meet 返回 c1.meet(p) or c2.meet(p)
  • 实现 NotCriteria(c):meet 返回 not c.meet(p)
  • 客户代码:用 AndCriteria(MaleCriteria(), AgeAboveCriteria(18)) 过滤,应只得到张三;用 OrCriteria(FemaleCriteria(), NotCriteria(AgeAboveCriteria(18))) 过滤,应得到李四、王五。验证结果正确。

参考答案

# 沿用练习一的 Person、Criteria、MaleCriteria、FemaleCriteria、AgeAboveCriteria、filter_by_criteria

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)

# 使用(persons 同练习一)
adult_male = AndCriteria(MaleCriteria(), AgeAboveCriteria(18))
print([str(p) for p in filter_by_criteria(persons, adult_male)])  # 只有张三

female_or_young = OrCriteria(FemaleCriteria(), NotCriteria(AgeAboveCriteria(18)))
print([str(p) for p in filter_by_criteria(persons, female_or_young)])  # 李四、王五

验证要点

  • 男性且年龄>=18 只有 张三女性或年龄<18李四、王五
  • 确认:没有 AdultMaleCriteria、FemaleOrYoungCriteria 等类,只有 AndCriteria/OrCriteria/NotCriteria 组合简单条件;新增组合只需 new 组合条件对象,不新增类。

练习三:商品筛选(价格、分类、库存 + 多条件组合)

目的

把条件用在商品上:价格区间分类有库存;再用 And / Or 组合成“价格在 1~100 且 有库存”“分类为水果 或 价格>200”等,体会条件可复用、可组合

要求

  • 定义 Product:有 name、category、price、in_stock(bool)。
  • 定义 ProductCriteria 抽象类,有 meet(self, p: Product) -> bool
  • 实现 PriceRangeCriteria(low, high)CategoryCriteria(category)InStockCriteria(in_stock 为 True)。
  • 实现 AndProductCriteria(criteria_list):meet 为 all(c.meet(p) for c in criteria_list)OrProductCriteria(criteria_list):meet 为 any(c.meet(p) for c in criteria_list)
  • 客户代码:构造 3 个商品(如苹果水果5元有货、键盘数码299有货、香蕉水果3元无货),用“价格 1~100 且 有库存”过滤,应得到苹果;用“水果 或 价格>200”过滤,应得到苹果、键盘、香蕉(或按你的数据调整预期)。验证过滤结果正确。

参考答案

from abc import ABC, abstractmethod

class Product:
    def __init__(self, name: str, category: str, price: float, in_stock: bool):
        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

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

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)

# 使用
products = [
    Product("苹果", "水果", 5.0, True),
    Product("键盘", "数码", 299.0, True),
    Product("香蕉", "水果", 3.0, False),
]
c1 = AndProductCriteria(PriceRangeCriteria(1, 100), InStockCriteria())
print([str(p) for p in products if c1.meet(p)])  # 苹果、键盘(都满足 1~100 且有库存)

c2 = OrProductCriteria(CategoryCriteria("水果"), PriceRangeCriteria(200, 9999))
print([str(p) for p in products if c2.meet(p)])  # 苹果、香蕉(水果)、键盘(价格>200)

验证要点

  • 价格 1~100 且 有库存:苹果、键盘都满足(若只要“苹果”可把价格区间改为 1~10 等)。
  • 水果 或 价格>200:苹果、香蕉、键盘(水果有苹果香蕉,价格>200 有键盘)。
  • 确认:ProductCriteria 可单独用,也可通过 AndProductCriteria/OrProductCriteria 组合;业务只写“条件对象 + 过滤”,不写一长串 if。

三步汇总与自检

练习 重点 关键点
简单条件 条件接口 meet(person);filter_by_criteria(list, criteria);换条件只换对象。
组合条件 AndCriteria、OrCriteria、NotCriteria 内部调子条件的 meet;不新增“男性且成年”等类。
商品多条件 PriceRange、Category、InStock;And/Or 支持多子条件;列表过滤用 [p for p in list if c.meet(p)]。

自检问题

  1. 过滤器模式里“条件”为什么要做成对象?
    做成对象后,条件可以复用(同一条件多处用)、可以组合(And/Or/Not 组合成新条件)、可以单独测试;若用 if-else 写死在函数里,难以复用和组合。

  2. 组合条件(And/Or/Not)和简单条件的关系?
    组合条件持有多个简单条件(或组合条件),meet 时根据逻辑(与/或/非)调用子条件的 meet;这样任意简单条件都能被组合,无需为每种组合写新类

  3. 若要对“年龄在 18~60 之间”写条件,可以怎么做?
    可以写 AndCriteria(AgeAboveCriteria(18), NotCriteria(AgeAboveCriteria(61))),或单独写一个 AgeBetweenCriteria(18, 60)。前者复用已有条件,后者更直观,按需选择。

做完以上三道练习,再对照《过滤器模式》文档,对条件对象与组合条件会掌握得比较扎实。建议先写简单条件与 filter_by_criteria,再加 And/Or/Not,最后在商品上做多条件组合,这样对过滤器模式会掌握得比较扎实。

发表评论