[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-08":26,"\u002Farticles\u002Fdesign-pattern-08\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-08\u002Fadjacent":70,"\u002Fcategories":82,"render-design-pattern-08":108},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113931511261564929","design-pattern-08","设计模式修炼之路 ⑧ 过滤器模式——与或非条件当积木","非 GoF、与或非条件当积木、Predicate 是 JDK 收编版","2113897628365230081",1,3801,7442,19,"2024-01-08T20:51:45","结构型模式的前两位——适配器、桥接——都是 GoF 23 人名单里的正牌选手。今天这第三位是位编外人员：**过滤器模式（Filter Pattern）**，也叫 **Criteria 模式（标准模式）**，GoF 名单里没有它，是后世教程的\"增补选手\"，但论日常业务里的出场率，它未必输给几位正牌——列表页的多条件筛选、风控规则的组合判断、权限系统的条件匹配，背后都是它的影子。上一篇预告的\"条件像积木一样自由拼接\"，这一篇兑现。\n\n## 一、什么是过滤器模式？\n\n先看定义：\n\n> 用不同的标准（Criteria）过滤一组对象，通过逻辑运算（与、或、非）把标准组合起来，实现灵活的条件筛选。\n\n翻译成人话：**把每个筛选条件封装成一个类，条件之间用\"与、或、非\"拼积木，要什么组合，现场拼**。\n\n生活类比是 HR 筛简历：\"本科以上\"\"五年经验\"\"会 Java\"，每条都是一个独立的筛子；真实招聘需求往往是\"本科以上**且**五年经验\"，或者\"会 Java **或** 会 Go\"——HR 不会为每种组合发明一套新流程，只是把筛子叠着用、换着用。过滤器模式就是把\"叠筛子\"这件事代码化。\n\n它有三个角色：\n\n- **Criteria（筛选条件接口）**：所有筛子的统一接口，`meetCriteria(List) → List`，进一批、出一批；\n- **ConcreteCriteria（具体条件）**：品类、价格、库存……一个维度一个类；\n- **组合条件（AndCriteria \u002F OrCriteria \u002F NotCriteria）**：与、或、非三个组合器。\n\n精髓在最后半句：**组合器自己也实现 Criteria 接口**。正因为它自己也是个\"筛子\"，And 里才能嵌 Or、Or 里嵌 Not，最后拼出一棵条件树——这个味道是不是很熟悉？And\u002FOr 套娃出来的结构，已经隐隐是第九篇组合模式里那棵\"树\"了。\n\n顺带交代户口问题：它不在 GoF 名单里，分类归属也乱——多数资料（含本系列目录篇）把它排在结构型里讲，也有人说它行为型味道更浓。编外选手，户口本乱很正常，记住它\"筛集合、拼条件\"的本事就行。\n\n## 二、过滤器模式的优缺点\n\n### 优点\n\n1. **条件复用**：一个条件类写一次，哪里需要哪里 new，不用到处复制 if。\n2. **组合自由**：与、或、非加嵌套，任意复杂的筛选逻辑都是积木拼接，表达能力从\"一串 AND\"升级为任意布尔表达式。\n3. **新增条件不改老代码**：加\"品牌\"筛选 = 新建一个 BrandCriteria 完事，反面教材里\"改签名、改方法体、全调用方跟着改\"的事故现场不再重演——开闭原则。\n4. **条件成了\"一等公民\"**：条件可以当参数传、当返回值返、存进集合、运行时动态拼装——这套操作有个学名，叫**行为参数化**。\n\n### 缺点\n\n1. **类数量膨胀**：每个条件一个类，为三行 if 建一个类，简单场景纯属杀鸡用牛刀。\n2. **Java 8 之后存在感下降**：Stream + Predicate 把这套玩法内建进了 JDK，一个 lambda 顶一个类，第五节专门展开。\n3. **组合正确性全靠人肉**：积木拼错了编译器不吭声，复杂组合的可读性全靠变量命名硬撑。\n\n## 三、过滤器模式的应用场景\n\n- **JDK（Java 8+）**：这个模式最提气的地方——它被官方收编了。`java.util.function.Predicate` 就是 Criteria 接口的 JDK 版，`and() \u002F or() \u002F negate()` 三个默认方法就是三个组合器，`Stream.filter()` 就是 meetCriteria。某种意义上，全世界的 Java 工程师每天都在用它。\n- **MyBatis-Plus**：`QueryWrapper.eq(\"category\", \"数码\").lt(\"price\", 4000)` 链式拼条件，或条件用 `or()` \u002F `nested()` 接——把\"条件对象\"拼装好交给 SQL，思路同款。\n- **风控与规则引擎**：\"（单笔 > 5000 且 夜间交易）或 命中黑名单\"——规则本身就是条件的与或非组合，很多规则引擎的核心就是一棵可配置的条件树。\n- **后台列表页**：多条件搜索把前端传来的参数动态拼成组合条件再查库，是它最常见的日常形态。\n\n**什么时候该用**：要按多个条件筛一批对象，条件会持续增加、组合方式五花八门（不止 AND）时。要是永远只有一个固定条件，一个 if 就够了，别请它出场。\n\n顺带排个雷：Spring 的 `Filter`、Servlet 的 `FilterChain` 是**拦截过滤器模式**（J2EE 模式，本系列㉛ 专讲）——那是\"请求过安检门\"，和这里\"筛对象集合\"是两回事，只是共用了 Filter 这个名字，别被带偏。\n\n## 四、过滤器模式的实现方式\n\n示例代码在 design-pattern 项目的 filter 包里：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── filter                       \u002F\u002F 过滤器模式\n    ├── Product.java             \u002F\u002F 被筛选的商品\n    ├── BadFilter.java           \u002F\u002F 反面教材：参数地狱\n    ├── Criteria.java            \u002F\u002F 筛选条件接口\n    ├── CategoryCriteria.java    \u002F\u002F 具体条件：按品类\n    ├── PriceBelowCriteria.java  \u002F\u002F 具体条件：价格低于\n    ├── InStockCriteria.java     \u002F\u002F 具体条件：有货\n    ├── AndCriteria.java         \u002F\u002F 组合条件：与\n    ├── OrCriteria.java          \u002F\u002F 组合条件：或\n    ├── NotCriteria.java         \u002F\u002F 组合条件：非\n    └── FilterTest.java          \u002F\u002F 测试入口\n```\n\n场景：电商列表页，商品按品类、价格、库存筛选。先看用\"万能方法\"硬扛的下场，再看 Criteria 怎么救。\n\n### 1. 反面教材：参数地狱\n\n最直觉的写法：一个静态方法，每个维度一个参数，\"不筛就传 null\"：\n\n```java\n\u002F\u002F 反面教材：每加一个筛选维度，签名和所有调用方都得跟着改\npublic class BadFilter {\n\n    public static List\u003CProduct> filter(List\u003CProduct> products, String category, Double maxPrice, Boolean inStock) {\n        List\u003CProduct> result = new ArrayList\u003C>();\n        for (Product p : products) {\n            if (category != null && !category.equals(p.getCategory())) {\n                continue;\n            }\n            if (maxPrice != null && p.getPrice() > maxPrice) {\n                continue;\n            }\n            if (inStock != null && p.isInStock() != inStock) {\n                continue;\n            }\n            result.add(p);\n        }\n        return result;\n    }\n}\n```\n\n筛\"数码 且 价格低于 4000\"：\n\n```java\nList\u003CProduct> result = BadFilter.filter(products, \"数码\", 4000.0, null);\n```\n\n运行结果：\n\n```\n===== 反面教材：参数地狱 =====\n小米手环（数码，249.0元，有货）\n```\n\n能跑，但三个雷已经埋下：\n\n1. **\"传 null 表示不筛\"是隐式协议**——签名上看不出哪个参数能空、空了是什么语义，全靠口口相传；\n2. **只会 AND**——想筛\"食品或图书\"？这个方法无能为力，只能另写一个方法，或者往 if 里继续堆逻辑；\n3. **加维度全链路遭殃**——哪天要按品牌筛：签名加参数、方法体加 if、所有调用方跟着改、测试全部重跑——开闭原则碎了一地。\n\n### 2. Criteria 版：条件做成积木\n\n先立筛子接口，这是全模式的定海神针：\n\n```java\n\u002F\u002F 筛选条件接口：具体条件和组合器都实现它\npublic interface Criteria {\n\n    List\u003CProduct> meetCriteria(List\u003CProduct> products);\n}\n```\n\n具体条件，一个维度一个类。按品类筛：\n\n```java\n\u002F\u002F 具体条件：按品类筛\npublic class CategoryCriteria implements Criteria {\n\n    private final String category;\n\n    public CategoryCriteria(String category) {\n        this.category = category;\n    }\n\n    @Override\n    public List\u003CProduct> meetCriteria(List\u003CProduct> products) {\n        List\u003CProduct> result = new ArrayList\u003C>();\n        for (Product p : products) {\n            if (category.equals(p.getCategory())) {\n                result.add(p);\n            }\n        }\n        return result;\n    }\n}\n```\n\n`PriceBelowCriteria`（价格低于指定值）和 `InStockCriteria`（只留有货的）结构完全一样，只是 if 的条件不同，就不贴了。\n\n重头戏是三个组合器。**与**——把第一个条件筛完的结果，交给第二个条件接着筛：\n\n```java\n\u002F\u002F 组合条件：两个条件都满足才留下\npublic class AndCriteria implements Criteria {\n\n    private final Criteria first;\n    private final Criteria second;\n\n    public AndCriteria(Criteria first, Criteria second) {\n        this.first = first;\n        this.second = second;\n    }\n\n    @Override\n    public List\u003CProduct> meetCriteria(List\u003CProduct> products) {\n        List\u003CProduct> firstResult = first.meetCriteria(products);\n        return second.meetCriteria(firstResult);\n    }\n}\n```\n\n**或**——两边各筛一遍，第二边的成果里去掉重复的并进来：\n\n```java\n\u002F\u002F 组合条件：满足任一条件就留下\npublic class OrCriteria implements Criteria {\n\n    private final Criteria first;\n    private final Criteria second;\n\n    public OrCriteria(Criteria first, Criteria second) {\n        this.first = first;\n        this.second = second;\n    }\n\n    @Override\n    public List\u003CProduct> meetCriteria(List\u003CProduct> products) {\n        List\u003CProduct> firstResult = first.meetCriteria(products);\n        List\u003CProduct> secondResult = second.meetCriteria(products);\n        for (Product p : secondResult) {\n            if (!firstResult.contains(p)) {\n                firstResult.add(p);\n            }\n        }\n        return firstResult;\n    }\n}\n```\n\n**非**——先算出要排除的，剩下的全留下：\n\n```java\n\u002F\u002F 组合条件：反着筛，留下不满足条件的\npublic class NotCriteria implements Criteria {\n\n    private final Criteria criteria;\n\n    public NotCriteria(Criteria criteria) {\n        this.criteria = criteria;\n    }\n\n    @Override\n    public List\u003CProduct> meetCriteria(List\u003CProduct> products) {\n        List\u003CProduct> excluded = criteria.meetCriteria(products);\n        List\u003CProduct> result = new ArrayList\u003C>();\n        for (Product p : products) {\n            if (!excluded.contains(p)) {\n                result.add(p);\n            }\n        }\n        return result;\n    }\n}\n```\n\n注意三个组合器的构造参数：装的**不是商品，是 Criteria**。组合器自己实现了 Criteria，所以它也能被装进别的组合器——套娃合法，这就是\"积木\"的全部秘密。\n\n看测试代码。五个商品先备好，从单条件到组合到嵌套，层层递进：\n\n```java\nprivate static void combine(List\u003CProduct> products) {\n    Criteria digital = new CategoryCriteria(\"数码\");\n    Criteria cheap = new PriceBelowCriteria(4000);\n    System.out.println(\"--- 数码 且 价格\u003C4000 ---\");\n    print(new AndCriteria(digital, cheap).meetCriteria(products));\n    System.out.println(\"--- 食品 或 图书 ---\");\n    print(new OrCriteria(new CategoryCriteria(\"食品\"), new CategoryCriteria(\"图书\")).meetCriteria(products));\n    System.out.println(\"--- 没货的（非：有货） ---\");\n    print(new NotCriteria(new InStockCriteria()).meetCriteria(products));\n}\n\nprivate static void nest(List\u003CProduct> products) {\n    Criteria cheapDigital = new AndCriteria(new CategoryCriteria(\"数码\"), new PriceBelowCriteria(4000));\n    System.out.println(\"--- (数码 且 价格\u003C4000) 或 图书 ---\");\n    print(new OrCriteria(cheapDigital, new CategoryCriteria(\"图书\")).meetCriteria(products));\n}\n```\n\n运行结果（单条件部分略，完整输出见项目）：\n\n```\n===== 与、或、非组合 =====\n--- 数码 且 价格\u003C4000 ---\n小米手环（数码，249.0元，有货）\n--- 食品 或 图书 ---\n薯片（食品，9.9元，有货）\n三体（图书，59.0元，有货）\n--- 没货的（非：有货） ---\niPhone 15（数码，4999.0元，缺货）\n洗衣机（家电，2999.0元，缺货）\n===== 条件当积木：套娃组合 =====\n--- (数码 且 价格\u003C4000) 或 图书 ---\n小米手环（数码，249.0元，有货）\n三体（图书，59.0元，有货）\n```\n\n对照反面教材逐条验收：\n\n- 反面教材唯一的组合\"数码 且 价格\u003C4000\"，Criteria 版用 `AndCriteria(digital, cheap)` 平替，还不用背 null 协议；\n- \"食品或图书\"\"没货的\"这两个需求，BadFilter 直接投降，Criteria 版各一行拼完；\n- 套娃组合\"(数码 且 价格\u003C4000) 或 图书\"——AndCriteria 的产物被塞进 OrCriteria，因为组合器自己也是 Criteria，**积木的接口统一了，无限嵌套就是自然结果**；\n- 哪天要按品牌筛：新建 BrandCriteria 一个类，老代码零改动——对照反面教材的\"签名、方法体、调用方三处改\"。\n\n这套\"组合器也实现同一接口\"的设计，和桥接篇\"两棵树用组合相连\"、适配器篇\"组合优于继承\"一脉相承——接口统一 + 组合，是结构型模式反复出现的底牌。\n\n## 五、Java 8 之后：Predicate 是官方收编版\n\n写到这里必须回答一个灵魂拷问：都 2025 年了，谁还手写这套？同一套筛选，Stream 版长这样：\n\n```java\nPredicate\u003CProduct> digital = p -> \"数码\".equals(p.getCategory());\nPredicate\u003CProduct> cheap = p -> p.getPrice() \u003C 4000;\nPredicate\u003CProduct> book = p -> \"图书\".equals(p.getCategory());\n\n\u002F\u002F (数码 且 价格\u003C4000) 或 图书\nList\u003CProduct> result = products.stream()\n        .filter(digital.and(cheap).or(book))\n        .collect(Collectors.toList());\n```\n\n逐项对账，几乎一一对应：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Criteria 手写版\u003C\u002Fth>\u003Cth>Stream \u002F Predicate 版\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Criteria 接口\u003C\u002Ftd>\u003Ctd>`Predicate\u003CT>` 函数式接口\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>CategoryCriteria(\"数码\")\u003C\u002Ftd>\u003Ctd>`p -> \"数码\".equals(p.getCategory())`\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>AndCriteria \u002F OrCriteria \u002F NotCriteria\u003C\u002Ftd>\u003Ctd>`and()` \u002F `or()` \u002F `negate()`\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>meetCriteria(list)\u003C\u002Ftd>\u003Ctd>`list.stream().filter(...).collect(...)`\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n所以业务代码里的结论很直接：**能用 Stream 就用 Stream**，一个 lambda 顶一个类，还没有类膨胀问题。那手写版还剩什么用？三个场景：\n\n1. **讲解与面试**——把模式拆开揉碎，才看得清 Predicate 为什么长这样、and\u002For\u002Fnegate 三个默认方法在设计上抄了谁的作业；\n2. **老项目**——还在 Java 7 及更早的项目没有 lambda，手写 Criteria 是唯一选择；\n3. **条件要\"带走\"的场景**——lambda 没法存进数据库、没法跨服务传输、没法给运营改（lambda 序列化是个公认的 fragile 玩法）；而规则引擎里的条件必须入库、回放、可视化编辑，所以它必须是真正的对象。手写 Criteria 本质上就是\"**可序列化、可配置的 Predicate**\"——这正是 Drools 这类规则引擎存在的理由之一。\n\n## 写在最后\n\n回顾这一篇：过滤器模式（Criteria）把每个筛选条件封装成实现同一接口的类，再配上与、或、非三个组合器，条件像积木一样自由拼接、合法套娃；反面教材\"参数地狱\"只会 AND、加维度全链路遭殃；它不在 GoF 名单里，却被 JDK 收编成了 Predicate + Stream——业务里优先用 Stream，条件需要入库配置时手写版依然是正解；另外记牢名字排雷：拦截过滤器是\"请求过安检门\"，㉛ 再讲。\n\n下一篇回到 GoF 正牌阵容，结构型第四位：**组合模式（Composite）**。这一篇 And 嵌 Or 拼出的条件树已经提前剧透了它的主题——把\"一对多\"组织成树形结构，让单片叶子和整棵树用同一个接口处理：文件夹与文件、菜单与子菜单、部门与员工，都是它的舞台。","\u003Cp>结构型模式的前两位——适配器、桥接——都是 GoF 23 人名单里的正牌选手。今天这第三位是位编外人员：\u003Cstrong>过滤器模式（Filter Pattern）\u003C\u002Fstrong>，也叫 \u003Cstrong>Criteria 模式（标准模式）\u003C\u002Fstrong>，GoF 名单里没有它，是后世教程的\"增补选手\"，但论日常业务里的出场率，它未必输给几位正牌——列表页的多条件筛选、风控规则的组合判断、权限系统的条件匹配，背后都是它的影子。上一篇预告的\"条件像积木一样自由拼接\"，这一篇兑现。\u003C\u002Fp>\n\u003Ch2>一、什么是过滤器模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>用不同的标准（Criteria）过滤一组对象，通过逻辑运算（与、或、非）把标准组合起来，实现灵活的条件筛选。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>把每个筛选条件封装成一个类，条件之间用\"与、或、非\"拼积木，要什么组合，现场拼\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>生活类比是 HR 筛简历：\"本科以上\"\"五年经验\"\"会 Java\"，每条都是一个独立的筛子；真实招聘需求往往是\"本科以上\u003Cstrong>且\u003C\u002Fstrong>五年经验\"，或者\"会 Java \u003Cstrong>或\u003C\u002Fstrong> 会 Go\"——HR 不会为每种组合发明一套新流程，只是把筛子叠着用、换着用。过滤器模式就是把\"叠筛子\"这件事代码化。\u003C\u002Fp>\n\u003Cp>它有三个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Criteria（筛选条件接口）\u003C\u002Fstrong>：所有筛子的统一接口，\u003Ccode>meetCriteria(List) → List\u003C\u002Fcode>，进一批、出一批；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteCriteria（具体条件）\u003C\u002Fstrong>：品类、价格、库存……一个维度一个类；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>组合条件（AndCriteria \u002F OrCriteria \u002F NotCriteria）\u003C\u002Fstrong>：与、或、非三个组合器。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>精髓在最后半句：\u003Cstrong>组合器自己也实现 Criteria 接口\u003C\u002Fstrong>。正因为它自己也是个\"筛子\"，And 里才能嵌 Or、Or 里嵌 Not，最后拼出一棵条件树——这个味道是不是很熟悉？And\u002FOr 套娃出来的结构，已经隐隐是第九篇组合模式里那棵\"树\"了。\u003C\u002Fp>\n\u003Cp>顺带交代户口问题：它不在 GoF 名单里，分类归属也乱——多数资料（含本系列目录篇）把它排在结构型里讲，也有人说它行为型味道更浓。编外选手，户口本乱很正常，记住它\"筛集合、拼条件\"的本事就行。\u003C\u002Fp>\n\u003Ch2>二、过滤器模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>条件复用\u003C\u002Fstrong>：一个条件类写一次，哪里需要哪里 new，不用到处复制 if。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>组合自由\u003C\u002Fstrong>：与、或、非加嵌套，任意复杂的筛选逻辑都是积木拼接，表达能力从\"一串 AND\"升级为任意布尔表达式。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>新增条件不改老代码\u003C\u002Fstrong>：加\"品牌\"筛选 = 新建一个 BrandCriteria 完事，反面教材里\"改签名、改方法体、全调用方跟着改\"的事故现场不再重演——开闭原则。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>条件成了\"一等公民\"\u003C\u002Fstrong>：条件可以当参数传、当返回值返、存进集合、运行时动态拼装——这套操作有个学名，叫\u003Cstrong>行为参数化\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>类数量膨胀\u003C\u002Fstrong>：每个条件一个类，为三行 if 建一个类，简单场景纯属杀鸡用牛刀。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Java 8 之后存在感下降\u003C\u002Fstrong>：Stream + Predicate 把这套玩法内建进了 JDK，一个 lambda 顶一个类，第五节专门展开。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>组合正确性全靠人肉\u003C\u002Fstrong>：积木拼错了编译器不吭声，复杂组合的可读性全靠变量命名硬撑。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、过滤器模式的应用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK（Java 8+）\u003C\u002Fstrong>：这个模式最提气的地方——它被官方收编了。\u003Ccode>java.util.function.Predicate\u003C\u002Fcode> 就是 Criteria 接口的 JDK 版，\u003Ccode>and() \u002F or() \u002F negate()\u003C\u002Fcode> 三个默认方法就是三个组合器，\u003Ccode>Stream.filter()\u003C\u002Fcode> 就是 meetCriteria。某种意义上，全世界的 Java 工程师每天都在用它。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>MyBatis-Plus\u003C\u002Fstrong>：\u003Ccode>QueryWrapper.eq(\"category\", \"数码\").lt(\"price\", 4000)\u003C\u002Fcode> 链式拼条件，或条件用 \u003Ccode>or()\u003C\u002Fcode> \u002F \u003Ccode>nested()\u003C\u002Fcode> 接——把\"条件对象\"拼装好交给 SQL，思路同款。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>风控与规则引擎\u003C\u002Fstrong>：\"（单笔 &gt; 5000 且 夜间交易）或 命中黑名单\"——规则本身就是条件的与或非组合，很多规则引擎的核心就是一棵可配置的条件树。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>后台列表页\u003C\u002Fstrong>：多条件搜索把前端传来的参数动态拼成组合条件再查库，是它最常见的日常形态。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：要按多个条件筛一批对象，条件会持续增加、组合方式五花八门（不止 AND）时。要是永远只有一个固定条件，一个 if 就够了，别请它出场。\u003C\u002Fp>\n\u003Cp>顺带排个雷：Spring 的 \u003Ccode>Filter\u003C\u002Fcode>、Servlet 的 \u003Ccode>FilterChain\u003C\u002Fcode> 是\u003Cstrong>拦截过滤器模式\u003C\u002Fstrong>（J2EE 模式，本系列㉛ 专讲）——那是\"请求过安检门\"，和这里\"筛对象集合\"是两回事，只是共用了 Filter 这个名字，别被带偏。\u003C\u002Fp>\n\u003Ch2>四、过滤器模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 filter 包里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── filter                       \u002F\u002F 过滤器模式\n    ├── Product.java             \u002F\u002F 被筛选的商品\n    ├── BadFilter.java           \u002F\u002F 反面教材：参数地狱\n    ├── Criteria.java            \u002F\u002F 筛选条件接口\n    ├── CategoryCriteria.java    \u002F\u002F 具体条件：按品类\n    ├── PriceBelowCriteria.java  \u002F\u002F 具体条件：价格低于\n    ├── InStockCriteria.java     \u002F\u002F 具体条件：有货\n    ├── AndCriteria.java         \u002F\u002F 组合条件：与\n    ├── OrCriteria.java          \u002F\u002F 组合条件：或\n    ├── NotCriteria.java         \u002F\u002F 组合条件：非\n    └── FilterTest.java          \u002F\u002F 测试入口\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：电商列表页，商品按品类、价格、库存筛选。先看用\"万能方法\"硬扛的下场，再看 Criteria 怎么救。\u003C\u002Fp>\n\u003Ch3>1. 反面教材：参数地狱\u003C\u002Fh3>\n\u003Cp>最直觉的写法：一个静态方法，每个维度一个参数，\"不筛就传 null\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 反面教材：每加一个筛选维度，签名和所有调用方都得跟着改\npublic class BadFilter {\n\n    public static List&lt;Product&gt; filter(List&lt;Product&gt; products, String category, Double maxPrice, Boolean inStock) {\n        List&lt;Product&gt; result = new ArrayList&lt;&gt;();\n        for (Product p : products) {\n            if (category != null &amp;&amp; !category.equals(p.getCategory())) {\n                continue;\n            }\n            if (maxPrice != null &amp;&amp; p.getPrice() &gt; maxPrice) {\n                continue;\n            }\n            if (inStock != null &amp;&amp; p.isInStock() != inStock) {\n                continue;\n            }\n            result.add(p);\n        }\n        return result;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>筛\"数码 且 价格低于 4000\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">List&lt;Product&gt; result = BadFilter.filter(products, \"数码\", 4000.0, null);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 反面教材：参数地狱 =====\n小米手环（数码，249.0元，有货）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>能跑，但三个雷已经埋下：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>\"传 null 表示不筛\"是隐式协议\u003C\u002Fstrong>——签名上看不出哪个参数能空、空了是什么语义，全靠口口相传；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>只会 AND\u003C\u002Fstrong>——想筛\"食品或图书\"？这个方法无能为力，只能另写一个方法，或者往 if 里继续堆逻辑；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>加维度全链路遭殃\u003C\u002Fstrong>——哪天要按品牌筛：签名加参数、方法体加 if、所有调用方跟着改、测试全部重跑——开闭原则碎了一地。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>2. Criteria 版：条件做成积木\u003C\u002Fh3>\n\u003Cp>先立筛子接口，这是全模式的定海神针：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 筛选条件接口：具体条件和组合器都实现它\npublic interface Criteria {\n\n    List&lt;Product&gt; meetCriteria(List&lt;Product&gt; products);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体条件，一个维度一个类。按品类筛：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 具体条件：按品类筛\npublic class CategoryCriteria implements Criteria {\n\n    private final String category;\n\n    public CategoryCriteria(String category) {\n        this.category = category;\n    }\n\n    @Override\n    public List&lt;Product&gt; meetCriteria(List&lt;Product&gt; products) {\n        List&lt;Product&gt; result = new ArrayList&lt;&gt;();\n        for (Product p : products) {\n            if (category.equals(p.getCategory())) {\n                result.add(p);\n            }\n        }\n        return result;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>PriceBelowCriteria\u003C\u002Fcode>（价格低于指定值）和 \u003Ccode>InStockCriteria\u003C\u002Fcode>（只留有货的）结构完全一样，只是 if 的条件不同，就不贴了。\u003C\u002Fp>\n\u003Cp>重头戏是三个组合器。\u003Cstrong>与\u003C\u002Fstrong>——把第一个条件筛完的结果，交给第二个条件接着筛：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 组合条件：两个条件都满足才留下\npublic class AndCriteria implements Criteria {\n\n    private final Criteria first;\n    private final Criteria second;\n\n    public AndCriteria(Criteria first, Criteria second) {\n        this.first = first;\n        this.second = second;\n    }\n\n    @Override\n    public List&lt;Product&gt; meetCriteria(List&lt;Product&gt; products) {\n        List&lt;Product&gt; firstResult = first.meetCriteria(products);\n        return second.meetCriteria(firstResult);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>或\u003C\u002Fstrong>——两边各筛一遍，第二边的成果里去掉重复的并进来：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 组合条件：满足任一条件就留下\npublic class OrCriteria implements Criteria {\n\n    private final Criteria first;\n    private final Criteria second;\n\n    public OrCriteria(Criteria first, Criteria second) {\n        this.first = first;\n        this.second = second;\n    }\n\n    @Override\n    public List&lt;Product&gt; meetCriteria(List&lt;Product&gt; products) {\n        List&lt;Product&gt; firstResult = first.meetCriteria(products);\n        List&lt;Product&gt; secondResult = second.meetCriteria(products);\n        for (Product p : secondResult) {\n            if (!firstResult.contains(p)) {\n                firstResult.add(p);\n            }\n        }\n        return firstResult;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>非\u003C\u002Fstrong>——先算出要排除的，剩下的全留下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 组合条件：反着筛，留下不满足条件的\npublic class NotCriteria implements Criteria {\n\n    private final Criteria criteria;\n\n    public NotCriteria(Criteria criteria) {\n        this.criteria = criteria;\n    }\n\n    @Override\n    public List&lt;Product&gt; meetCriteria(List&lt;Product&gt; products) {\n        List&lt;Product&gt; excluded = criteria.meetCriteria(products);\n        List&lt;Product&gt; result = new ArrayList&lt;&gt;();\n        for (Product p : products) {\n            if (!excluded.contains(p)) {\n                result.add(p);\n            }\n        }\n        return result;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意三个组合器的构造参数：装的\u003Cstrong>不是商品，是 Criteria\u003C\u002Fstrong>。组合器自己实现了 Criteria，所以它也能被装进别的组合器——套娃合法，这就是\"积木\"的全部秘密。\u003C\u002Fp>\n\u003Cp>看测试代码。五个商品先备好，从单条件到组合到嵌套，层层递进：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void combine(List&lt;Product&gt; products) {\n    Criteria digital = new CategoryCriteria(\"数码\");\n    Criteria cheap = new PriceBelowCriteria(4000);\n    System.out.println(\"--- 数码 且 价格&lt;4000 ---\");\n    print(new AndCriteria(digital, cheap).meetCriteria(products));\n    System.out.println(\"--- 食品 或 图书 ---\");\n    print(new OrCriteria(new CategoryCriteria(\"食品\"), new CategoryCriteria(\"图书\")).meetCriteria(products));\n    System.out.println(\"--- 没货的（非：有货） ---\");\n    print(new NotCriteria(new InStockCriteria()).meetCriteria(products));\n}\n\nprivate static void nest(List&lt;Product&gt; products) {\n    Criteria cheapDigital = new AndCriteria(new CategoryCriteria(\"数码\"), new PriceBelowCriteria(4000));\n    System.out.println(\"--- (数码 且 价格&lt;4000) 或 图书 ---\");\n    print(new OrCriteria(cheapDigital, new CategoryCriteria(\"图书\")).meetCriteria(products));\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果（单条件部分略，完整输出见项目）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 与、或、非组合 =====\n--- 数码 且 价格&lt;4000 ---\n小米手环（数码，249.0元，有货）\n--- 食品 或 图书 ---\n薯片（食品，9.9元，有货）\n三体（图书，59.0元，有货）\n--- 没货的（非：有货） ---\niPhone 15（数码，4999.0元，缺货）\n洗衣机（家电，2999.0元，缺货）\n===== 条件当积木：套娃组合 =====\n--- (数码 且 价格&lt;4000) 或 图书 ---\n小米手环（数码，249.0元，有货）\n三体（图书，59.0元，有货）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对照反面教材逐条验收：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>反面教材唯一的组合\"数码 且 价格&lt;4000\"，Criteria 版用 \u003Ccode>AndCriteria(digital, cheap)\u003C\u002Fcode> 平替，还不用背 null 协议；\u003C\u002Fli>\n \u003Cli>\"食品或图书\"\"没货的\"这两个需求，BadFilter 直接投降，Criteria 版各一行拼完；\u003C\u002Fli>\n \u003Cli>套娃组合\"(数码 且 价格&lt;4000) 或 图书\"——AndCriteria 的产物被塞进 OrCriteria，因为组合器自己也是 Criteria，\u003Cstrong>积木的接口统一了，无限嵌套就是自然结果\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>哪天要按品牌筛：新建 BrandCriteria 一个类，老代码零改动——对照反面教材的\"签名、方法体、调用方三处改\"。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这套\"组合器也实现同一接口\"的设计，和桥接篇\"两棵树用组合相连\"、适配器篇\"组合优于继承\"一脉相承——接口统一 + 组合，是结构型模式反复出现的底牌。\u003C\u002Fp>\n\u003Ch2>五、Java 8 之后：Predicate 是官方收编版\u003C\u002Fh2>\n\u003Cp>写到这里必须回答一个灵魂拷问：都 2025 年了，谁还手写这套？同一套筛选，Stream 版长这样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">Predicate&lt;Product&gt; digital = p -&gt; \"数码\".equals(p.getCategory());\nPredicate&lt;Product&gt; cheap = p -&gt; p.getPrice() &lt; 4000;\nPredicate&lt;Product&gt; book = p -&gt; \"图书\".equals(p.getCategory());\n\n\u002F\u002F (数码 且 价格&lt;4000) 或 图书\nList&lt;Product&gt; result = products.stream()\n        .filter(digital.and(cheap).or(book))\n        .collect(Collectors.toList());\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>逐项对账，几乎一一对应：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>Criteria 手写版\u003C\u002Fth>\n   \u003Cth>Stream \u002F Predicate 版\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Criteria 接口\u003C\u002Ftd>\n   \u003Ctd>`Predicate` 函数式接口\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>CategoryCriteria(\"数码\")\u003C\u002Ftd>\n   \u003Ctd>`p -&gt; \"数码\".equals(p.getCategory())`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>AndCriteria \u002F OrCriteria \u002F NotCriteria\u003C\u002Ftd>\n   \u003Ctd>`and()` \u002F `or()` \u002F `negate()`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>meetCriteria(list)\u003C\u002Ftd>\n   \u003Ctd>`list.stream().filter(...).collect(...)`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>所以业务代码里的结论很直接：\u003Cstrong>能用 Stream 就用 Stream\u003C\u002Fstrong>，一个 lambda 顶一个类，还没有类膨胀问题。那手写版还剩什么用？三个场景：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>讲解与面试\u003C\u002Fstrong>——把模式拆开揉碎，才看得清 Predicate 为什么长这样、and\u002For\u002Fnegate 三个默认方法在设计上抄了谁的作业；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>老项目\u003C\u002Fstrong>——还在 Java 7 及更早的项目没有 lambda，手写 Criteria 是唯一选择；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>条件要\"带走\"的场景\u003C\u002Fstrong>——lambda 没法存进数据库、没法跨服务传输、没法给运营改（lambda 序列化是个公认的 fragile 玩法）；而规则引擎里的条件必须入库、回放、可视化编辑，所以它必须是真正的对象。手写 Criteria 本质上就是\"\u003Cstrong>可序列化、可配置的 Predicate\u003C\u002Fstrong>\"——这正是 Drools 这类规则引擎存在的理由之一。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：过滤器模式（Criteria）把每个筛选条件封装成实现同一接口的类，再配上与、或、非三个组合器，条件像积木一样自由拼接、合法套娃；反面教材\"参数地狱\"只会 AND、加维度全链路遭殃；它不在 GoF 名单里，却被 JDK 收编成了 Predicate + Stream——业务里优先用 Stream，条件需要入库配置时手写版依然是正解；另外记牢名字排雷：拦截过滤器是\"请求过安检门\"，㉛ 再讲。\u003C\u002Fp>\n\u003Cp>下一篇回到 GoF 正牌阵容，结构型第四位：\u003Cstrong>组合模式（Composite）\u003C\u002Fstrong>。这一篇 And 嵌 Or 拼出的条件树已经提前剧透了它的主题——把\"一对多\"组织成树形结构，让单片叶子和整棵树用同一个接口处理：文件夹与文件、菜单与子菜单、部门与员工，都是它的舞台。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,62],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113931567565901825","design-pattern-09","设计模式修炼之路 ⑨ 组合模式——叶子和容器一视同仁","部分-整体、叶子和容器一视同仁、在 DOM\u002FMyBatis 里找它",3281,7735,20,"2024-01-25T15:35:13",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":52,"publishedAt":61},"2113931625669595138","design-pattern-10","设计模式修炼之路 ⑩ 装饰器模式——不改原类，动态加功能","不改原类动态加功能、\"我是组件+我有组件\"、Java IO 流的套娃设计",1003,7847,"2024-02-10T15:14:16",{"id":63,"slug":64,"title":65,"summary":66,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":67,"likeCount":4,"wordCount":68,"readingMinutes":36,"publishedAt":69},"2113935201573801985","design-pattern-31","设计模式修炼之路 ㉛ 拦截过滤器模式——一个过滤器，只管一件事","一个过滤器一个横切关注点、return 走不出的审计盲区、契约两句话放行就 next、后置代码包住整条链、顺序即范围、与责任链对账",1038,7452,"2025-05-27T20:35:21",{"code":4,"message":5,"data":71},{"prev":72,"next":81},{"id":73,"slug":74,"title":75,"summary":76,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":77,"likeCount":4,"wordCount":78,"readingMinutes":79,"publishedAt":80},"2113931455091445761","design-pattern-07","设计模式修炼之路 ⑦ 桥接模式——m×n 降为 m+n","继承爆炸的解药、m×n 降为 m+n、在 JDBC 里找它",1907,5226,14,"2023-12-15T15:12:08",{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},{"code":4,"message":5,"data":83},[84,90,95,101],{"id":85,"name":86,"slug":87,"description":88,"sortOrder":4,"articleCount":89},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":91,"slug":92,"description":93,"sortOrder":4,"articleCount":94},"设计模式","design-pattern","设计模式系列","34",{"id":96,"name":97,"slug":98,"description":99,"sortOrder":33,"articleCount":100},"1946000000000000002","随笔","essay","生活与思考","0",{"id":102,"name":103,"slug":104,"description":105,"sortOrder":106,"articleCount":107},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":109,"toc":110},"\u003Cp>结构型模式的前两位——适配器、桥接——都是 GoF 23 人名单里的正牌选手。今天这第三位是位编外人员：\u003Cstrong>过滤器模式（Filter Pattern）\u003C\u002Fstrong>，也叫 \u003Cstrong>Criteria 模式（标准模式）\u003C\u002Fstrong>，GoF 名单里没有它，是后世教程的\"增补选手\"，但论日常业务里的出场率，它未必输给几位正牌——列表页的多条件筛选、风控规则的组合判断、权限系统的条件匹配，背后都是它的影子。上一篇预告的\"条件像积木一样自由拼接\"，这一篇兑现。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、什么是过滤器模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>用不同的标准（Criteria）过滤一组对象，通过逻辑运算（与、或、非）把标准组合起来，实现灵活的条件筛选。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>把每个筛选条件封装成一个类，条件之间用\"与、或、非\"拼积木，要什么组合，现场拼\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>生活类比是 HR 筛简历：\"本科以上\"\"五年经验\"\"会 Java\"，每条都是一个独立的筛子；真实招聘需求往往是\"本科以上\u003Cstrong>且\u003C\u002Fstrong>五年经验\"，或者\"会 Java \u003Cstrong>或\u003C\u002Fstrong> 会 Go\"——HR 不会为每种组合发明一套新流程，只是把筛子叠着用、换着用。过滤器模式就是把\"叠筛子\"这件事代码化。\u003C\u002Fp>\n\u003Cp>它有三个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Criteria（筛选条件接口）\u003C\u002Fstrong>：所有筛子的统一接口，\u003Ccode>meetCriteria(List) → List\u003C\u002Fcode>，进一批、出一批；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteCriteria（具体条件）\u003C\u002Fstrong>：品类、价格、库存……一个维度一个类；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>组合条件（AndCriteria \u002F OrCriteria \u002F NotCriteria）\u003C\u002Fstrong>：与、或、非三个组合器。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>精髓在最后半句：\u003Cstrong>组合器自己也实现 Criteria 接口\u003C\u002Fstrong>。正因为它自己也是个\"筛子\"，And 里才能嵌 Or、Or 里嵌 Not，最后拼出一棵条件树——这个味道是不是很熟悉？And\u002FOr 套娃出来的结构，已经隐隐是第九篇组合模式里那棵\"树\"了。\u003C\u002Fp>\n\u003Cp>顺带交代户口问题：它不在 GoF 名单里，分类归属也乱——多数资料（含本系列目录篇）把它排在结构型里讲，也有人说它行为型味道更浓。编外选手，户口本乱很正常，记住它\"筛集合、拼条件\"的本事就行。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、过滤器模式的优缺点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-3\">优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>条件复用\u003C\u002Fstrong>：一个条件类写一次，哪里需要哪里 new，不用到处复制 if。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>组合自由\u003C\u002Fstrong>：与、或、非加嵌套，任意复杂的筛选逻辑都是积木拼接，表达能力从\"一串 AND\"升级为任意布尔表达式。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>新增条件不改老代码\u003C\u002Fstrong>：加\"品牌\"筛选 = 新建一个 BrandCriteria 完事，反面教材里\"改签名、改方法体、全调用方跟着改\"的事故现场不再重演——开闭原则。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>条件成了\"一等公民\"\u003C\u002Fstrong>：条件可以当参数传、当返回值返、存进集合、运行时动态拼装——这套操作有个学名，叫\u003Cstrong>行为参数化\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-4\">缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>类数量膨胀\u003C\u002Fstrong>：每个条件一个类，为三行 if 建一个类，简单场景纯属杀鸡用牛刀。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Java 8 之后存在感下降\u003C\u002Fstrong>：Stream + Predicate 把这套玩法内建进了 JDK，一个 lambda 顶一个类，第五节专门展开。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>组合正确性全靠人肉\u003C\u002Fstrong>：积木拼错了编译器不吭声，复杂组合的可读性全靠变量命名硬撑。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-5\">三、过滤器模式的应用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK（Java 8+）\u003C\u002Fstrong>：这个模式最提气的地方——它被官方收编了。\u003Ccode>java.util.function.Predicate\u003C\u002Fcode> 就是 Criteria 接口的 JDK 版，\u003Ccode>and() \u002F or() \u002F negate()\u003C\u002Fcode> 三个默认方法就是三个组合器，\u003Ccode>Stream.filter()\u003C\u002Fcode> 就是 meetCriteria。某种意义上，全世界的 Java 工程师每天都在用它。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>MyBatis-Plus\u003C\u002Fstrong>：\u003Ccode>QueryWrapper.eq(\"category\", \"数码\").lt(\"price\", 4000)\u003C\u002Fcode> 链式拼条件，或条件用 \u003Ccode>or()\u003C\u002Fcode> \u002F \u003Ccode>nested()\u003C\u002Fcode> 接——把\"条件对象\"拼装好交给 SQL，思路同款。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>风控与规则引擎\u003C\u002Fstrong>：\"（单笔 &gt; 5000 且 夜间交易）或 命中黑名单\"——规则本身就是条件的与或非组合，很多规则引擎的核心就是一棵可配置的条件树。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>后台列表页\u003C\u002Fstrong>：多条件搜索把前端传来的参数动态拼成组合条件再查库，是它最常见的日常形态。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：要按多个条件筛一批对象，条件会持续增加、组合方式五花八门（不止 AND）时。要是永远只有一个固定条件，一个 if 就够了，别请它出场。\u003C\u002Fp>\n\u003Cp>顺带排个雷：Spring 的 \u003Ccode>Filter\u003C\u002Fcode>、Servlet 的 \u003Ccode>FilterChain\u003C\u002Fcode> 是\u003Cstrong>拦截过滤器模式\u003C\u002Fstrong>（J2EE 模式，本系列㉛ 专讲）——那是\"请求过安检门\"，和这里\"筛对象集合\"是两回事，只是共用了 Filter 这个名字，别被带偏。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、过滤器模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 filter 包里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>design-pattern\u002Fsrc\u002Fmain\u002Fjava\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>└── filter                       \u002F\u002F 过滤器模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Product.java             \u002F\u002F 被筛选的商品\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── BadFilter.java           \u002F\u002F 反面教材：参数地狱\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Criteria.java            \u002F\u002F 筛选条件接口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── CategoryCriteria.java    \u002F\u002F 具体条件：按品类\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── PriceBelowCriteria.java  \u002F\u002F 具体条件：价格低于\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── InStockCriteria.java     \u002F\u002F 具体条件：有货\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── AndCriteria.java         \u002F\u002F 组合条件：与\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── OrCriteria.java          \u002F\u002F 组合条件：或\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── NotCriteria.java         \u002F\u002F 组合条件：非\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    └── FilterTest.java          \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：电商列表页，商品按品类、价格、库存筛选。先看用\"万能方法\"硬扛的下场，再看 Criteria 怎么救。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">1. 反面教材：参数地狱\u003C\u002Fh3>\n\u003Cp>最直觉的写法：一个静态方法，每个维度一个参数，\"不筛就传 null\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 反面教材：每加一个筛选维度，签名和所有调用方都得跟着改\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadFilter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">filter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">products\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">category\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Double \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">maxPrice\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Boolean \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">inStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> result \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Product p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> products) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (category \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> !\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">category.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getCategory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">())) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                continue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (maxPrice \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getPrice\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> maxPrice) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                continue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (inStock \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">isInStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> inStock) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                continue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            result.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> result;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>筛\"数码 且 价格低于 4000\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> result \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> BadFilter.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">filter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"数码\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">4000.0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 反面教材：参数地狱 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>小米手环（数码，249.0元，有货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>能跑，但三个雷已经埋下：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>\"传 null 表示不筛\"是隐式协议\u003C\u002Fstrong>——签名上看不出哪个参数能空、空了是什么语义，全靠口口相传；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>只会 AND\u003C\u002Fstrong>——想筛\"食品或图书\"？这个方法无能为力，只能另写一个方法，或者往 if 里继续堆逻辑；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>加维度全链路遭殃\u003C\u002Fstrong>——哪天要按品牌筛：签名加参数、方法体加 if、所有调用方跟着改、测试全部重跑——开闭原则碎了一地。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-8\">2. Criteria 版：条件做成积木\u003C\u002Fh3>\n\u003Cp>先立筛子接口，这是全模式的定海神针：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 筛选条件接口：具体条件和组合器都实现它\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Criteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">products\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体条件，一个维度一个类。按品类筛：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 具体条件：按品类筛\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Criteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String category;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">category\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.category \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> category;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">products\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> result \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Product p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> products) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (category.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getCategory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">())) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                result.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> result;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>PriceBelowCriteria\u003C\u002Fcode>（价格低于指定值）和 \u003Ccode>InStockCriteria\u003C\u002Fcode>（只留有货的）结构完全一样，只是 if 的条件不同，就不贴了。\u003C\u002Fp>\n\u003Cp>重头戏是三个组合器。\u003Cstrong>与\u003C\u002Fstrong>——把第一个条件筛完的结果，交给第二个条件接着筛：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 组合条件：两个条件都满足才留下\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AndCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Criteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Criteria first;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Criteria second;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AndCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Criteria \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">first\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Criteria \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">second\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.first \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> first;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.second \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> second;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">products\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> firstResult \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> first.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> second.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(firstResult);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>或\u003C\u002Fstrong>——两边各筛一遍，第二边的成果里去掉重复的并进来：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 组合条件：满足任一条件就留下\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Criteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Criteria first;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Criteria second;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Criteria \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">first\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Criteria \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">second\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.first \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> first;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.second \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> second;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">products\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> firstResult \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> first.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> secondResult \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> second.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Product p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> secondResult) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">firstResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">contains\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                firstResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> firstResult;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>非\u003C\u002Fstrong>——先算出要排除的，剩下的全留下：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 组合条件：反着筛，留下不满足条件的\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NotCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Criteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Criteria criteria;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NotCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Criteria \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">criteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.criteria \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> criteria;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">products\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> excluded \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> criteria.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> result \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Product p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> products) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">excluded.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">contains\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                result.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> result;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意三个组合器的构造参数：装的\u003Cstrong>不是商品，是 Criteria\u003C\u002Fstrong>。组合器自己实现了 Criteria，所以它也能被装进别的组合器——套娃合法，这就是\"积木\"的全部秘密。\u003C\u002Fp>\n\u003Cp>看测试代码。五个商品先备好，从单条件到组合到嵌套，层层递进：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> combine\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> products) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Criteria digital \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"数码\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Criteria cheap \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PriceBelowCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">4000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"--- 数码 且 价格&#x3C;4000 ---\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AndCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(digital, cheap).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"--- 食品 或 图书 ---\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"食品\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">), \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"图书\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"--- 没货的（非：有货） ---\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NotCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> InStockCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> nest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> products) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Criteria cheapDigital \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AndCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"数码\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">), \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PriceBelowCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">4000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"--- (数码 且 价格&#x3C;4000) 或 图书 ---\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(cheapDigital, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CategoryCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"图书\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">meetCriteria\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(products));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果（单条件部分略，完整输出见项目）：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 与、或、非组合 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>--- 数码 且 价格&#x3C;4000 ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>小米手环（数码，249.0元，有货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>--- 食品 或 图书 ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>薯片（食品，9.9元，有货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>三体（图书，59.0元，有货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>--- 没货的（非：有货） ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>iPhone 15（数码，4999.0元，缺货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>洗衣机（家电，2999.0元，缺货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>===== 条件当积木：套娃组合 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>--- (数码 且 价格&#x3C;4000) 或 图书 ---\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>小米手环（数码，249.0元，有货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>三体（图书，59.0元，有货）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对照反面教材逐条验收：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>反面教材唯一的组合\"数码 且 价格&lt;4000\"，Criteria 版用 \u003Ccode>AndCriteria(digital, cheap)\u003C\u002Fcode> 平替，还不用背 null 协议；\u003C\u002Fli>\n \u003Cli>\"食品或图书\"\"没货的\"这两个需求，BadFilter 直接投降，Criteria 版各一行拼完；\u003C\u002Fli>\n \u003Cli>套娃组合\"(数码 且 价格&lt;4000) 或 图书\"——AndCriteria 的产物被塞进 OrCriteria，因为组合器自己也是 Criteria，\u003Cstrong>积木的接口统一了，无限嵌套就是自然结果\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>哪天要按品牌筛：新建 BrandCriteria 一个类，老代码零改动——对照反面教材的\"签名、方法体、调用方三处改\"。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这套\"组合器也实现同一接口\"的设计，和桥接篇\"两棵树用组合相连\"、适配器篇\"组合优于继承\"一脉相承——接口统一 + 组合，是结构型模式反复出现的底牌。\u003C\u002Fp>\n\u003Ch2 id=\"toc-9\">五、Java 8 之后：Predicate 是官方收编版\u003C\u002Fh2>\n\u003Cp>写到这里必须回答一个灵魂拷问：都 2025 年了，谁还手写这套？同一套筛选，Stream 版长这样：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Predicate&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> digital \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"数码\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getCategory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Predicate&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> cheap \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getPrice\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 4000\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Predicate&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> book \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"图书\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(p.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getCategory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F (数码 且 价格&#x3C;4000) 或 图书\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> result \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> products.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">stream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">filter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(digital.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">and\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(cheap).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">or\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(book))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">collect\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Collectors.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toList\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>逐项对账，几乎一一对应：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>Criteria 手写版\u003C\u002Fth>\n   \u003Cth>Stream \u002F Predicate 版\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Criteria 接口\u003C\u002Ftd>\n   \u003Ctd>`Predicate` 函数式接口\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>CategoryCriteria(\"数码\")\u003C\u002Ftd>\n   \u003Ctd>`p -&gt; \"数码\".equals(p.getCategory())`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>AndCriteria \u002F OrCriteria \u002F NotCriteria\u003C\u002Ftd>\n   \u003Ctd>`and()` \u002F `or()` \u002F `negate()`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>meetCriteria(list)\u003C\u002Ftd>\n   \u003Ctd>`list.stream().filter(...).collect(...)`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>所以业务代码里的结论很直接：\u003Cstrong>能用 Stream 就用 Stream\u003C\u002Fstrong>，一个 lambda 顶一个类，还没有类膨胀问题。那手写版还剩什么用？三个场景：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>讲解与面试\u003C\u002Fstrong>——把模式拆开揉碎，才看得清 Predicate 为什么长这样、and\u002For\u002Fnegate 三个默认方法在设计上抄了谁的作业；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>老项目\u003C\u002Fstrong>——还在 Java 7 及更早的项目没有 lambda，手写 Criteria 是唯一选择；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>条件要\"带走\"的场景\u003C\u002Fstrong>——lambda 没法存进数据库、没法跨服务传输、没法给运营改（lambda 序列化是个公认的 fragile 玩法）；而规则引擎里的条件必须入库、回放、可视化编辑，所以它必须是真正的对象。手写 Criteria 本质上就是\"\u003Cstrong>可序列化、可配置的 Predicate\u003C\u002Fstrong>\"——这正是 Drools 这类规则引擎存在的理由之一。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：过滤器模式（Criteria）把每个筛选条件封装成实现同一接口的类，再配上与、或、非三个组合器，条件像积木一样自由拼接、合法套娃；反面教材\"参数地狱\"只会 AND、加维度全链路遭殃；它不在 GoF 名单里，却被 JDK 收编成了 Predicate + Stream——业务里优先用 Stream，条件需要入库配置时手写版依然是正解；另外记牢名字排雷：拦截过滤器是\"请求过安检门\"，㉛ 再讲。\u003C\u002Fp>\n\u003Cp>下一篇回到 GoF 正牌阵容，结构型第四位：\u003Cstrong>组合模式（Composite）\u003C\u002Fstrong>。这一篇 And 嵌 Or 拼出的条件树已经提前剧透了它的主题——把\"一对多\"组织成树形结构，让单片叶子和整棵树用同一个接口处理：文件夹与文件、菜单与子菜单、部门与员工，都是它的舞台。\u003C\u002Fp>",[111,114,117,121,124,127,130,133,136,139],{"id":112,"text":113,"level":106},"toc-1","一、什么是过滤器模式？",{"id":115,"text":116,"level":106},"toc-2","二、过滤器模式的优缺点",{"id":118,"text":119,"level":120},"toc-3","优点",3,{"id":122,"text":123,"level":120},"toc-4","缺点",{"id":125,"text":126,"level":106},"toc-5","三、过滤器模式的应用场景",{"id":128,"text":129,"level":106},"toc-6","四、过滤器模式的实现方式",{"id":131,"text":132,"level":120},"toc-7","1. 反面教材：参数地狱",{"id":134,"text":135,"level":120},"toc-8","2. Criteria 版：条件做成积木",{"id":137,"text":138,"level":106},"toc-9","五、Java 8 之后：Predicate 是官方收编版",{"id":140,"text":141,"level":106},"toc-10","写在最后"]