设计模式在重构中的应用:单例、工厂、观察者、策略模式在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捕获野指针
策略模式 算法自由切换 导出格式、排序算法 策略类太多时管理混乱

知识体系结构图

设计模式在Qt重构中的应用 单例模式 工厂模式 观察者模式 策略模式 日志管理器 全局配置 数据库连接池 动态创建UI组件 跨平台适配 插件系统 信号槽机制 事件分发 数据绑定 导出格式切换 排序算法 校验规则 核心目标:降低耦合、提高可维护性、便于扩展

这张图把四种模式的核心应用场景都列出来了。你想想看,在实际项目中,这些模式往往不是孤立使用的。比如工厂模式里可以嵌套单例,策略模式可以用观察者模式来通知策略切换。

最后说一句:设计模式不是银弹。我见过有人为了用模式而用模式,把简单问题搞复杂了。记住,重构的目标是让代码更好维护,不是炫技。

可可精品资料库