设计模式在重构中的应用:单例、工厂、观察者、策略模式在Qt中的实践
说实话,很多Qt开发者写了几年代码,还是把界面逻辑和业务逻辑揉成一团。项目一大了,改个按钮回调都得翻半天文件。我个人习惯是,在重构阶段引入设计模式,把代码拆成可维护的模块。今天咱们就聊聊四种最常用的模式,在Qt里怎么落地。
单例模式:全局唯一的“管家”
单例模式,说白了就是一个类在整个程序里只能有一个实例。比如日志管理器、全局配置、数据库连接池,这些场景用单例再合适不过。
我在项目中遇到过一个问题:多个窗口同时写日志,结果日志文件被锁死了。后来改成单例的日志管理器,所有写操作都走同一个实例,问题就解决了。
Qt中的典型实现:
class Logger {
public:
static Logger& instance() {
static Logger log;
return log;
}
void log(const QString& msg) {
// 写日志逻辑
}
private:
Logger() {}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
};
嗯,这里要注意:Qt的元对象系统(MOC)和单例结合时,别把构造函数声明为私有的同时还在Q_OBJECT宏里搞事情。我曾经踩过这个坑,编译报错半天没找到原因。
小技巧:如果你需要线程安全的单例,C++11的静态局部变量初始化已经是线程安全的了,不用额外加锁。
工厂模式:解耦对象的创建
工厂模式,说白了就是把“创建对象”这件事封装起来。你想想看,如果代码里到处都是new QPushButton、new QLabel,哪天要换一套皮肤,不得改到吐血?
我建议用工厂模式来管理UI组件的创建。比如根据配置文件动态生成不同类型的控件。
class WidgetFactory {
public:
static QWidget* createWidget(const QString& type) {
if (type == "button") {
return new QPushButton("按钮");
} else if (type == "label") {
return new QLabel("标签");
} else if (type == "slider") {
return new QSlider(Qt::Horizontal);
}
return nullptr;
}
};
这样做的好处是:新增一种控件类型,只需要在工厂里加一个分支,调用方完全不用改。我在重构一个老项目时,用工厂模式替换了原来散落在各处的new语句,代码量减少了30%。
避坑指南:我曾经在工厂里直接返回裸指针,结果内存泄漏查了一整天。建议返回QSharedPointer或者让调用方负责delete。
观察者模式:Qt的信号槽就是它
观察者模式,说白了就是“发布-订阅”。一个对象状态变了,通知所有关注它的对象。Qt的信号槽机制,本质上就是观察者模式的一种实现。
举个例子:一个温度传感器,温度变化时要更新多个UI显示。用信号槽就很简单:
class TemperatureSensor : public QObject {
Q_OBJECT
public:
void setTemperature(double t) {
if (t != m_temp) {
m_temp = t;
emit temperatureChanged(t);
}
}
signals:
void temperatureChanged(double temp);
private:
double m_temp = 0.0;
};
// 在UI中连接
QLabel* label = new QLabel("温度显示");
TemperatureSensor* sensor = new TemperatureSensor;
connect(sensor, &TemperatureSensor::temperatureChanged,
label, [=](double t) {
label->setText(QString("当前温度: %1°C").arg(t));
});
我个人习惯用Lambda表达式连接信号槽,代码更紧凑。但要注意:Lambda里捕获了this指针的话,记得在对象析构时断开连接,否则会野指针崩溃。
核心要点:观察者模式让发送者和接收者完全解耦。你可以在运行时动态添加或移除观察者,而不影响其他部分。
策略模式:算法自由切换
策略模式,说白了就是把算法封装成独立的类,让它们可以互相替换。比如一个数据导出功能,支持导出为CSV、JSON、XML三种格式。用策略模式就非常优雅。
class ExportStrategy {
public:
virtual ~ExportStrategy() {}
virtual void exportData(const QVariantList& data) = 0;
};
class CsvExport : public ExportStrategy {
public:
void exportData(const QVariantList& data) override {
// 导出为CSV
}
};
class JsonExport : public ExportStrategy {
public:
void exportData(const QVariantList& data) override {
// 导出为JSON
}
};
// 使用
ExportStrategy* strategy = new CsvExport();
strategy->exportData(myData);
我在项目中遇到过需求变更:客户一开始只要CSV导出,后来又要加JSON。如果当初用if-else硬编码,改起来就麻烦了。用了策略模式,加一个类就行,原有代码一行不动。
建议:策略模式配合工厂模式使用效果更佳。工厂负责创建策略对象,调用方只需要知道策略名称。
四种模式的对比与选择
| 模式 | 核心目的 | Qt中的典型场景 | 我踩过的坑 |
|---|---|---|---|
| 单例模式 | 全局唯一实例 | 日志、配置、数据库连接 | 多线程下忘记加锁 |
| 工厂模式 | 解耦对象创建 | 动态创建UI组件 | 裸指针导致内存泄漏 |
| 观察者模式 | 一对多通知 | 信号槽、事件系统 | Lambda捕获野指针 |
| 策略模式 | 算法自由切换 | 导出格式、排序算法 | 策略类太多时管理混乱 |
知识体系结构图
这张图把四种模式的核心应用场景都列出来了。你想想看,在实际项目中,这些模式往往不是孤立使用的。比如工厂模式里可以嵌套单例,策略模式可以用观察者模式来通知策略切换。
最后说一句:设计模式不是银弹。我见过有人为了用模式而用模式,把简单问题搞复杂了。记住,重构的目标是让代码更好维护,不是炫技。