系列文章:
- 单例模式:【C++/Qt 进阶】设计模式系列(1):单例模式------饿汉式内存泄漏真相、指针单例存在的意义、五种实现终极对比-CSDN博客
- 工厂模式与抽象工厂模式:【C++/Qt 进阶】设计模式系列(2):工厂模式------从游戏角色到披萨店-CSDN博客
- 建造者模式:【C++/Qt 进阶】设计模式系列(3): 建造者模式------把"复杂对象的组装"从业务代码里剥离出来-CSDN博客
- 原型模式:【C++/Qt 进阶】设计模式系列(4):原型模式------用"克隆"代替"重新构造"-CSDN博客
- 适配器模式:【C++/Qt 进阶】设计模式系列(5):适配器模式------给"不兼容的接口"贴一层翻译膜-CSDN博客
- 桥接模式【C++/Qt 进阶】设计模式系列(6):桥接模式------把"两个独立变化的维度"拆开_日志框架变更用到的适配器模式 桥接模式-CSDN博客
- 过滤器模式(本篇)
写在前面
⚠️ 前几篇聚焦于"如何创建对象 "(创建型)和"如何组织对象 "(结构型)。从本篇开始,我们正式进入行为型设计模式的领域------但过滤器模式是个例外:它在结构上更像"可组合的积木",是结构型与行为型的融合体。
上一讲我们用桥接模式拆解了两个独立变化的维度。但在真实项目中,还有一类高频需求反复出现:
"给我一份数据,我要按多个条件筛选,并且这些条件能自由组合。"
这正是 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;
}
问题显而易见:
- 条件写死 ------用户想只看
Error级别或仅限电机模块时,必须修改代码并重新编译; - 无法灵活组合 ------每加一个条件就多一个
&&,代码迅速变成"条件意大利面"; - 不可复用------换数据源或筛选维度,逻辑需重写一遍。
菜鸟教程定义:将对象的筛选过程封装起来,允许使用不同的筛选标准动态地筛选对象。 (参考)
记忆口诀:"条件即积木"------每个筛选器是一块砖,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判断。
八、与其他模式对比
| 模式 | 与过滤器模式的区别 |
|---|---|
| 策略模式 | 策略关注"如何计算",过滤器关注"是否通过";前者改变算法,后者改变筛选标准 |
| 责任链模式 | 责任链是"逐个处理",过滤器是"并行判断";责任链常用于流程审批,过滤器用于数据筛选 |
| 中介者模式 | 中介者协调多方交互,过滤器只是单一判断逻辑的封装,无状态协调职责 |
🔍 总结:过滤器 ≠ 策略 ≠ 责任链,它是专为"条件筛选"设计的轻量级组合结构。
九、最佳实践与避坑指南
✅ 最佳实践
- 优先使用组合而非继承 :通过
AndFilter/OrFilter实现组合,避免无限嵌套; - 保持筛选器单一职责:每个筛选器只做一件事;
- 利用模板泛化类型 :一套
IFilter可通用于多种数据类型; - 结合 Qt 框架使用
QSortFilterProxyModel:避免重复造轮子; - 添加日志或调试信息:便于排查组合逻辑错误。
⚠️ 避坑指南
- ❌ 不要在
matches()中进行耗时操作(如网络请求、文件读写); - ❌ 避免深层嵌套组合(如
And(And(And(...)))),建议使用容器扁平化; - ❌ 不要用裸指针传递筛选器,务必使用
std::shared_ptr; - ❌ 忽视空指针检查,确保组合器内部安全访问;
- ❌ 误将过滤器当作"数据转换器"------它只负责"是否保留"。
十、写在最后
过滤器模式看似简单,却是构建可配置、可组合、可维护的筛选逻辑的核心利器。
它不是纯粹的行为型模式,也不完全属于结构型,而是一种以"条件即对象"为核心思想的混合型设计范式。
当你面对"我要按多个条件筛选数据"的需求时,请记住:
不要写一堆
if (a && b && c),而要让每个条件成为一块积木,用逻辑运算拼出想要的结果。
📌 下一篇预告:策略模式与观察者模式------我们将探讨"如何让算法可切换"以及"如何实现事件驱动通知"。
🔔 关注我,持续更新高质量设计模式实战解析,带你从"会写代码"走向"懂架构"。