过滤器模式练习——简单条件、组合条件与商品筛选
按《Python 设计模式——过滤器模式》的建议,通过三道练习巩固:① 简单条件对象(人 + 性别/年龄)+ 过滤函数;② 组合条件 AND/OR/NOT;③ 商品筛选(价格、分类、库存)+ 多条件组合。每步都有完整可运行代码和验证要点。
练习一:简单条件对象(人 + 性别/年龄)
目的
体会条件即对象:定义 Criteria 接口(如 meet(person) -> bool),每种规则一个具体条件类;对列表过滤时只依赖条件对象,不写 if 性别、if 年龄。
要求
- 定义 Person:有 name、gender、age。
- 定义 Criteria 抽象类,有抽象方法 meet(self, person: Person) -> bool。
- 实现 MaleCriteria、FemaleCriteria、AgeAboveCriteria(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)
目的
在简单条件基础上增加组合条件:AndCriteria、OrCriteria、NotCriteria,用它们把已有条件组合成“男性且年龄>=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)]。 |
自检问题
-
过滤器模式里“条件”为什么要做成对象?
做成对象后,条件可以复用(同一条件多处用)、可以组合(And/Or/Not 组合成新条件)、可以单独测试;若用 if-else 写死在函数里,难以复用和组合。 -
组合条件(And/Or/Not)和简单条件的关系?
组合条件持有多个简单条件(或组合条件),meet 时根据逻辑(与/或/非)调用子条件的 meet;这样任意简单条件都能被组合,无需为每种组合写新类。 -
若要对“年龄在 18~60 之间”写条件,可以怎么做?
可以写 AndCriteria(AgeAboveCriteria(18), NotCriteria(AgeAboveCriteria(61))),或单独写一个 AgeBetweenCriteria(18, 60)。前者复用已有条件,后者更直观,按需选择。
做完以上三道练习,再对照《过滤器模式》文档,对条件对象与组合条件会掌握得比较扎实。建议先写简单条件与 filter_by_criteria,再加 And/Or/Not,最后在商品上做多条件组合,这样对过滤器模式会掌握得比较扎实。