【C++/Qt 进阶】设计模式系列(7):过滤器模式——把“筛选条件“变成可组合的积木

系列文章:

  1. 单例模式:【C++/Qt 进阶】设计模式系列(1):单例模式------饿汉式内存泄漏真相、指针单例存在的意义、五种实现终极对比-CSDN博客
  2. 工厂模式与抽象工厂模式:【C++/Qt 进阶】设计模式系列(2):工厂模式------从游戏角色到披萨店-CSDN博客
  3. 建造者模式:【C++/Qt 进阶】设计模式系列(3): 建造者模式------把"复杂对象的组装"从业务代码里剥离出来-CSDN博客
  4. 原型模式:【C++/Qt 进阶】设计模式系列(4):原型模式------用"克隆"代替"重新构造"-CSDN博客
  5. 适配器模式:【C++/Qt 进阶】设计模式系列(5):适配器模式------给"不兼容的接口"贴一层翻译膜-CSDN博客
  6. 桥接模式【C++/Qt 进阶】设计模式系列(6):桥接模式------把"两个独立变化的维度"拆开_日志框架变更用到的适配器模式 桥接模式-CSDN博客
  7. 过滤器模式(本篇)

写在前面

⚠️ 前几篇聚焦于"如何创建对象 "(创建型)和"如何组织对象 "(结构型)。从本篇开始,我们正式进入行为型设计模式的领域------但过滤器模式是个例外:它在结构上更像"可组合的积木",是结构型与行为型的融合体。

上一讲我们用桥接模式拆解了两个独立变化的维度。但在真实项目中,还有一类高频需求反复出现:

"给我一份数据,我要按多个条件筛选,并且这些条件能自由组合。"

这正是 Qt 工程师常遇到的典型场景------日志或告警列表的多维筛选:

cpp 复制代码
// 反人类写法:所有条件硬编码在一个函数里
QList<LogRecord> filterLogs(const QList<LogRecord>& all) {
    QList<LogRecord> out;
    for (const auto& r : all) {
        if (r.level >= LogLevel::Warning           // 等级
            && r.timestamp > lastHour              // 时间
            && r.module == "Motor"                // 模块
            && r.message.contains("timeout")) {   // 关键字
            out.append(r);
        }
    }
    return out;
}

问题显而易见:

  1. 条件写死 ------用户想只看 Error 级别或仅限电机模块时,必须修改代码并重新编译;
  2. 无法灵活组合 ------每加一个条件就多一个 &&,代码迅速变成"条件意大利面";
  3. 不可复用------换数据源或筛选维度,逻辑需重写一遍。

菜鸟教程定义:将对象的筛选过程封装起来,允许使用不同的筛选标准动态地筛选对象。 (参考)
记忆口诀:"条件即积木"------每个筛选器是一块砖,AND/OR/NOT 就是拼接的胶水。


一、30 秒看懂过滤器模式

维度 策略模式 过滤器模式
封装内容 一个算法 一个判定条件
核心关注 "用哪种方式算" "这条数据是否符合"
是否支持组合 通常不组合 ✅ 支持 AND / OR / NOT 自由组合
典型应用 排序、支付方式 表格筛选、告警过滤、文件搜索

一句话总结:把"是否保留这条数据"的判断抽象为对象,让筛选条件像积木一样自由拼装。 在 C++ 中,每个筛选器只需实现一个 matches(const T&) const -> bool 方法,组合器通过逻辑运算将其串联。


二、UML 类图与角色

以下是基于 纯 C++/Qt 风格 的过滤器模式结构图,采用模板 + 智能指针的设计习惯。

text 复制代码
          ┌──────────────────────────────┐
          │  template <typename T>       │
          │  class IFilter {             │   ← 筛选接口(Filter)
          │  {                           │   只负责"单条数据"的判定
          │  public:                     │
          │    virtual ~IFilter() = default;
          │    virtual bool matches(     │   ← 核心方法
          │      const T& item) const = 0;
          │  };                          │
          └───────────────┬──────────────┘
                          │ 继承
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
┌────────────────┐  ┌────────────────┐  ┌────────────────┐
│ class LevelFilter : │ │ class TimeFilter : │ │ class KeywordFilter : │
│  public IFilter<Rec> │ │  public IFilter<Rec> │ │  public IFilter<Rec> │
│ { bool matches(...) const override; } │ │ { bool matches(...) const override; } │ │ { bool matches(...) const override; } │
└────────────────┘  └────────────────┘  └────────────────┘
                          ▲
                          │ 组合(shared_ptr)
          ┌───────────────┴──────────────┐
          │  template <typename T>       │
          │  class AndFilter :           │   ← 组合筛选器
          │     public IFilter<T>       │
          │  {                           │
          │  public:                     │
          │    AndFilter(                │
          │      std::shared_ptr<IFilter<T>> a,
          │      std::shared_ptr<IFilter<T>> b);
          │    bool matches(const T& i) const override;
          │  private:
          │    std::shared_ptr<IFilter<T>> m_a, m_b;
          │  };                          │
          └──────────────────────────────┘

三大核心角色

角色 职责 C++/Qt 中的对应实现
筛选接口(Filter) 定义"判定一条数据"的统一契约 template<typename T> class IFilter { virtual bool matches(const T&) const = 0; }
具体筛选器(Concrete Filter) 实现某一特定条件的判断逻辑 LevelFilter、TimeFilter、KeywordFilter 等
组合筛选器(Composite Filter) 将多个筛选器通过 AND/OR/NOT 组合起来 AndFilter、OrFilter、NotFilter

💡 推荐使用单条判定 bool matches(const T&) const:

  • 避免重复内存分配:多次筛选仅生成一个结果列表,而非嵌套分配;
  • 天然支持短路优化 :&& 和 || 可提前终止;
  • 兼容流式处理与迭代器场景。

使用 template<typename T> 设计,使一套 IFilter 可作用于任意类型数据,提升复用性。


三、基础代码实现(C++/Qt 版)

以下是一个面向实际开发的完整示例,模拟日志系统的多条件筛选功能。

cpp 复制代码
#include <QList>
#include <QString>
#include <memory>

// 日志记录结构体
struct LogRecord {
    int level;
    qint64 timestamp;
    QString module;
    QString message;

    // 构造函数便于测试
    LogRecord(int l, qint64 t, const QString& m, const QString& msg)
        : level(l), timestamp(t), module(m), message(msg) {}
};

// 筛选接口:所有筛选器必须实现此方法
template<typename T>
class IFilter {
public:
    virtual ~IFilter() = default;
    virtual bool matches(const T& item) const = 0;
};

// 具体筛选器:按等级筛选
class LevelFilter : public IFilter<LogRecord> {
private:
    int minLevel;
public:
    explicit LevelFilter(int minLevel) : minLevel(minLevel) {}

    bool matches(const LogRecord& record) const override {
        return record.level >= minLevel;
    }
};

// 按时间筛选(最近一小时)
class TimeFilter : public IFilter<LogRecord> {
private:
    qint64 cutoffTime;
public:
    explicit TimeFilter(qint64 cutoffTime) : cutoffTime(cutoffTime) {}

    bool matches(const LogRecord& record) const override {
        return record.timestamp > cutoffTime;
    }
};

// 按模块筛选
class ModuleFilter : public IFilter<LogRecord> {
private:
    QString moduleName;
public:
    explicit ModuleFilter(const QString& name) : moduleName(name) {}

    bool matches(const LogRecord& record) const override {
        return record.module == moduleName;
    }
};

// 按关键字筛选
class KeywordFilter : public IFilter<LogRecord> {
private:
    QString keyword;
public:
    explicit KeywordFilter(const QString& key) : keyword(key) {}

    bool matches(const LogRecord& record) const override {
        return record.message.contains(keyword);
    }
};

✅ 以上代码已具备高内聚、低耦合特性,各筛选器互不影响,可独立测试与复用。


四、组合过滤器:And / Or / Not

接下来构建组合逻辑,实现灵活的条件拼接。

cpp 复制代码
// AND 组合:所有条件都满足才通过
template<typename T>
class AndFilter : public IFilter<T> {
private:
    std::shared_ptr<IFilter<T>> left, right;
public:
    AndFilter(std::shared_ptr<IFilter<T>> l, std::shared_ptr<IFilter<T>> r)
        : left(l), right(r) {}

    bool matches(const T& item) const override {
        return left->matches(item) && right->matches(item);
    }
};

// OR 组合:任一条件满足即通过
template<typename T>
class OrFilter : public IFilter<T> {
private:
    std::shared_ptr<IFilter<T>> left, right;
public:
    OrFilter(std::shared_ptr<IFilter<T>> l, std::shared_ptr<IFilter<T>> r)
        : left(l), right(r) {}

    bool matches(const T& item) const override {
        return left->matches(item) || right->matches(item);
    }
};

// NOT 组合:取反
template<typename T>
class NotFilter : public IFilter<T> {
private:
    std::shared_ptr<IFilter<T>> inner;
public:
    explicit NotFilter(std::shared_ptr<IFilter<T>> i) : inner(i) {}

    bool matches(const T& item) const override {
        return !inner->matches(item);
    }
};

🧩 使用示例

cpp 复制代码
int main() {
    QList<LogRecord> logs = {
        LogRecord(1, 1000, "Motor", "timeout detected"),
        LogRecord(2, 2000, "Sensor", "high temp"),
        LogRecord(3, 3000, "Motor", "system error"),
        LogRecord(0, 500, "Network", "connection lost")
    };

    // 构建复杂筛选条件
    auto levelFilter = std::make_shared<LevelFilter>(LogLevel::Warning); // Warning 及以上
    auto timeFilter = std::make_shared<TimeFilter>(1500);               // 近期日志
    auto motorFilter = std::make_shared<ModuleFilter>("Motor");
    auto keywordFilter = std::make_shared<KeywordFilter>("timeout");

    // 组合:同时满足"电机模块"且"包含 timeout"
    auto andFilter = std::make_shared<AndFilter<LogRecord>>(
        motorFilter,
        keywordFilter
    );

    // 复杂组合:(电机模块 && 包含 timeout) OR (级别 ≥ Warning 且时间 > 1500)
    auto complexFilter = std::make_shared<OrFilter<LogRecord>>(
        andFilter,
        std::make_shared<AndFilter<LogRecord>>(
            levelFilter,
            timeFilter
        )
    );

    // 执行筛选
    QList<LogRecord> result;
    for (const auto& log : logs) {
        if (complexFilter->matches(log)) {
            result.append(log);
        }
    }

    // 输出结果
    for (const auto& r : result) {
        qDebug() << "Matched:" << r.module << r.message;
    }

    return 0;
}

✅ 输出:

复制代码
Matched: Motor "timeout detected"
Matched: Motor "system error"

五、Qt 自带的过滤器:QSortFilterProxyModel

在实际项目中,你无需手动实现过滤逻辑。Qt 提供了强大的内置工具:QSortFilterProxyModel。

优势:

  • 支持 排序 + 过滤 一体化;
  • 与 QTableView / QListView 等控件无缝集成;
  • 支持正则表达式、自定义模型索引匹配;
  • 可通过重写 filterAcceptsRow() 动态控制显示。

示例用法:

cpp 复制代码
class LogProxyModel : public QSortFilterProxyModel {
protected:
    bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override {
        auto index = sourceModel()->index(source_row, 0, source_parent);
        auto level = sourceModel()->data(index).toInt();
        auto module = sourceModel()->data(index, Qt::UserRole).toString();

        // 条件:级别 ≥ Warning 且模块为 "Motor"
        return level >= 2 && module == "Motor";
    }
};

✅ 优点:无需自己遍历数据,性能更高;支持懒加载、分页等高级特性。


六、通用模板场景

过滤器模式不仅适用于日志系统,还可广泛用于:

  • 文件搜索:按扩展名、大小、修改时间筛选;
  • 数据库查询:构建动态 WHERE 子句;
  • 配置管理:筛选满足特定策略的配置项;
  • 事件处理:只响应特定类型的事件;
  • 权限控制:根据角色、资源、操作类型决定是否放行。

✅ 通用原则:凡是"从集合中选出符合条件元素"的场景,皆可用过滤器模式。


七、优缺点分析

优点 缺点
✅ 条件可组合、可复用、可配置 ❌ 多层嵌套可能导致性能下降(尤其递归组合)
✅ 易于扩展新筛选器 ❌ 过度组合可能造成逻辑难以理解
✅ 符合开闭原则(OCP) ❌ 需要合理管理智能指针生命周期
✅ 适合复杂业务规则建模 ❌ 不适合简单的一次性筛选

✅ 建议:对复杂规则使用组合,简单规则直接写 if 判断。


八、与其他模式对比

模式 与过滤器模式的区别
策略模式 策略关注"如何计算",过滤器关注"是否通过";前者改变算法,后者改变筛选标准
责任链模式 责任链是"逐个处理",过滤器是"并行判断";责任链常用于流程审批,过滤器用于数据筛选
中介者模式 中介者协调多方交互,过滤器只是单一判断逻辑的封装,无状态协调职责

🔍 总结:过滤器 ≠ 策略 ≠ 责任链,它是专为"条件筛选"设计的轻量级组合结构。


九、最佳实践与避坑指南

✅ 最佳实践

  1. 优先使用组合而非继承 :通过 AndFilter/OrFilter 实现组合,避免无限嵌套;
  2. 保持筛选器单一职责:每个筛选器只做一件事;
  3. 利用模板泛化类型 :一套 IFilter 可通用于多种数据类型;
  4. 结合 Qt 框架使用 QSortFilterProxyModel:避免重复造轮子;
  5. 添加日志或调试信息:便于排查组合逻辑错误。

⚠️ 避坑指南

  • ❌ 不要在 matches() 中进行耗时操作(如网络请求、文件读写);
  • ❌ 避免深层嵌套组合(如 And(And(And(...)))),建议使用容器扁平化;
  • ❌ 不要用裸指针传递筛选器,务必使用 std::shared_ptr;
  • ❌ 忽视空指针检查,确保组合器内部安全访问;
  • ❌ 误将过滤器当作"数据转换器"------它只负责"是否保留"。

十、写在最后

过滤器模式看似简单,却是构建可配置、可组合、可维护的筛选逻辑的核心利器。

它不是纯粹的行为型模式,也不完全属于结构型,而是一种以"条件即对象"为核心思想的混合型设计范式。

当你面对"我要按多个条件筛选数据"的需求时,请记住:

不要写一堆 if (a && b && c),而要让每个条件成为一块积木,用逻辑运算拼出想要的结果。
📌 下一篇预告:策略模式与观察者模式------我们将探讨"如何让算法可切换"以及"如何实现事件驱动通知"。
🔔 关注我,持续更新高质量设计模式实战解析,带你从"会写代码"走向"懂架构"。


相关推荐
TDengine (老段)1 小时前
TDengine IDMP 十大亮点功能(一)
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
为思念酝酿的痛1 小时前
MySQL的基本查询
数据库·mysql
花间相见1 小时前
【计算基础|Redis01】【上手】安装连接、配置与通用命令
数据库·后端·面试
geovindu1 小时前
Pivoting and Unpivoting Data in SQL using postgreSQL 18
大数据·数据库·数据仓库·sql·postgresql·数据库开发·数据库架构
我命由我123451 小时前
6-3.Python 异常处理 - 异常处理进阶(else 块与 finally 块、异常传递、自定义异常)
开发语言·前端·数据库·后端·python·学习·java-ee
Elastic 中国社区官方博客2 小时前
Elastic 重大事件功能如何将 100 条日志样本转化为检测规则
大数据·运维·数据库·人工智能·elasticsearch·搜索引擎
adinnet20262 小时前
持续智能问数:多轮对话状态管理、下钻钻取与经营驾驶舱的演进
javascript·数据库·人工智能
系统集成-鹰翔2 小时前
ERP接口月度统计少了零单月份:SQLite分组与最近三个月均值排查
javascript·数据库·经验分享·后端