跳到主要内容C++ 设计模式实践:工厂模式与单例模式详解 | 极客日志C++算法
C++ 设计模式实践:工厂模式与单例模式详解
C++ 设计模式实践,涵盖工厂模式(简单工厂、工厂方法、抽象工厂)、单例模式(饿汉式、懒汉式及 Meyers 写法)和策略模式。结合 Qt 项目场景,提供现代 C++(C++17/20)代码示例,包括动态加载插件系统实现、跨平台配置及常见陷阱避坑指南。旨在帮助开发者在组件创建、全局管理及行为切换中灵活应用设计模式,提升代码扩展性与可维护性。
鲜活24 浏览 17.1 本章学习目标与重点
- 掌握工厂模式三层次的核心思想、适用场景及 C++ 实现细节
- 理解单例模式的饿汉式/懒汉式实现及线程安全方案
- 能够在实际项目中(如组件创建、配置管理)灵活选择模式
- 规避常见陷阱(过度设计、线程安全隐患、资源泄漏等)
17.2 设计模式基础认知
设计模式不是'代码模板',而是**'变化点隔离 + 职责单一 + 接口依赖抽象'**的设计思想集合。
Qt 项目中最常使用设计模式的场景(真实案例):
| 场景 | 常用模式 | Qt 典型实现 | 典型类/组件 |
|---|
| 动态创建控件/组件 | 工厂模式 | QPluginLoader、自定义工厂 | QAbstractItemModel、QWidget 子类 |
| 全局配置/日志/连接池 | 单例模式 | 局部静态变量 + QSettings | QSettings、QApplication |
| 不同支付/渲染/日志方式 | 策略模式 | 信号槽 + 接口类 | QAbstractSocket、QStyle |
| 撤销/重做操作 | 命令模式 | QUndoStack | QUndoCommand |
| 动态扩展功能 | 插件/抽象工厂 | QPluginLoader + Q_DECLARE_INTERFACE | Qt 插件系统 |
17.3 工厂模式:对象创建的标准化解决方案
17.3.1 简单工厂模式(Simple Factory)——最常用入门级方案
适用场景(Qt 项目中常见):
- 产品种类少(3–8 种)
- 创建逻辑简单
- 不需要频繁新增产品
Qt 真实案例:创建不同类型的传感器(电压、电流、温度)
代码实现(现代 C++ 写法):
#pragma once
#include <memory>
#include <string>
#include <stdexcept>
class Sensor {
public:
virtual ~Sensor() = default;
= ;
= ;
};
: Sensor {
:
{ + (() % ) / ; }
{ ; }
};
: Sensor {
:
{ + (() % ) / ; }
{ ; }
};
: Sensor {
:
{ + (() % ); }
{ ; }
};
{
:
{
(type == ) std::<VoltageSensor>();
(type == ) std::<CurrentSensor>();
(type == ) std::<TemperatureSensor>();
std::( + type);
}
};
virtual
double
readValue
()
const
0
virtual std::string type() const
0
class
VoltageSensor
public
public
double readValue() const override
return
3.3
rand
10
10.0
std::string type() const override
return
"电压"
class
CurrentSensor
public
public
double readValue() const override
return
0.5
rand
5
10.0
std::string type() const override
return
"电流"
class
TemperatureSensor
public
public
double readValue() const override
return
25.0
rand
20
std::string type() const override
return
"温度"
class
SensorFactory
public
static std::unique_ptr<Sensor> create(const std::string& type)
if
"voltage"
return
make_unique
if
"current"
return
make_unique
if
"temperature"
return
make_unique
throw
invalid_argument
"不支持的传感器类型:"
auto sensor = SensorFactory::create("temperature");
ui->label->setText(QString("%1: %2 °C").arg(sensor->type().c_str()).arg(sensor->readValue()));
| 优点 | 缺点 |
|---|
| 代码简单、易上手 | 新增传感器类型需修改工厂类(违反开闭原则) |
| 集中管理创建逻辑 | 工厂类职责膨胀 |
17.3.2 工厂方法模式(Factory Method)——最推荐的扩展方案
核心思想:每个产品对应一个专属工厂,新增产品时只加一对(产品类 + 工厂类),不改现有代码。
Qt 项目真实案例:不同平台 UI 组件工厂(Windows / Mac / Linux)
#pragma once
#include <memory>
class Button {
public:
virtual ~Button() = default;
virtual void render() const = 0;
};
class WindowsButton : public Button {
public:
void render() const override { qDebug() << "Windows 风格按钮"; }
};
class MacButton : public Button {
public:
void render() const override { qDebug() << "Mac 风格圆角按钮"; }
};
class UIFactory {
public:
virtual std::unique_ptr<Button> createButton() const = 0;
virtual ~UIFactory() = default;
};
class WindowsUIFactory : public UIFactory {
public:
std::unique_ptr<Button> createButton() const override {
return std::make_unique<WindowsButton>();
}
};
class MacUIFactory : public UIFactory {
public:
std::unique_ptr<Button> createButton() const override {
return std::make_unique<MacButton>();
}
};
std::unique_ptr<UIFactory> factory;
if (platform == "windows") {
factory = std::make_unique<WindowsUIFactory>();
} else {
factory = std::make_unique<MacUIFactory>();
}
auto btn = factory->createButton();
btn->render();
优点:完全符合开闭原则,新增平台只需新增工厂类,无需改动现有代码。
17.3.3 抽象工厂模式(Abstract Factory)——多产品族解决方案
适用场景:需要同时创建一组相关对象(如 UI 组件族:按钮 + 文本框 + 窗口)
class AbstractUI {
public:
virtual std::unique_ptr<Button> createButton() const = 0;
virtual std::unique_ptr<TextBox> createTextBox() const = 0;
virtual ~AbstractUI() = default;
};
class WindowsUI : public AbstractUI {
public:
std::unique_ptr<Button> createButton() const override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<TextBox> createTextBox() const override {
return std::make_unique<WindowsTextBox>();
}
};
17.4 单例模式:全局唯一实例
推荐写法(C++11 后最优):局部静态变量(Meyers Singleton)
class Logger {
private:
Logger() { qDebug() << "Logger 初始化"; }
~Logger() { qDebug() << "Logger 销毁"; }
public:
static Logger& instance() {
static Logger inst;
return inst;
}
void info(const QString& msg) {
qDebug() << "[INFO]" << msg;
}
};
Logger::instance().info("系统启动");
- 线程安全(C++11 标准保证)
- 延迟初始化(首次调用时创建)
- 无需手动 delete(静态变量自动析构)
- 代码最简洁
| 写法 | 线程安全 | 延迟初始化 | 代码复杂度 | 推荐指数 |
|---|
| 饿汉式 | 是 | 否 | 低 | ★★☆☆☆ |
| 局部静态(Meyers) | 是 | 是 | 极低 | ★★★★★ |
| 双检锁(DCL) | 有隐患 | 是 | 高 | 不推荐 |
| std::call_once | 是 | 是 | 中 | ★★★☆☆ |
17.5 完整 Qt 项目示例:多模式结合的日志系统
项目名称:QtDesignPatternsDemo
- Logger(单例)
- LogStrategy(策略模式接口)
- ConsoleStrategy / FileStrategy(具体策略)
- LoggerFactory(简单工厂创建策略)
Logger::instance().setStrategy(LoggerFactory::create("file"));
Logger::instance().log("INFO", "系统启动成功");
总结与推荐路线
| 项目阶段 | 推荐模式组合 | 为什么推荐 |
|---|
| 小型工具 | 单例 + 简单工厂 | 实现最快 |
| 中型桌面应用 | 工厂方法 + 策略 + 单例 | 扩展性好,代码清晰 |
| 大型框架/插件 | 抽象工厂 + 插件 + 单例 | 支持跨平台、动态加载 |
| 高性能后台 | 局部静态单例 + CRTP 静态多态 | 零运行时开销 |
Qt 插件系统核心原理
Qt 插件本质上是动态链接库(.dll / .so / .dylib),但必须满足以下条件:
- 插件内部必须有一个继承自 QObject 的类
- 该类必须使用
Q_PLUGIN_METADATA(IID "...") 宏声明一个唯一的接口标识符(IID)
- 主程序通过
QPluginLoader::instance() 获取插件对象
- 使用
qobject_cast<YourInterface*>(plugin) 转换为自定义接口类型
#define MyPluginInterface_iid "org.example.MyPluginInterface/1.0"
Q_DECLARE_INTERFACE(MyPluginInterface, MyPluginInterface_iid)
Q_PLUGIN_METADATA(IID MyPluginInterface_iid)
完整可运行的现代插件系统实现(推荐方式)
项目结构(最清晰的组织方式)
QtPluginDemo/
├── main/ 主程序
│ ├── main.cpp
│ ├── mainwindow.h/cpp/ui
│ └── CMakeLists.txt
├── plugins/ 插件目录(可独立编译)
│ ├── plugin_core/ 插件核心接口
│ │ ├── plugininterface.h
│ │ └── CMakeLists.txt
│ ├── plugin_echo/ 示例插件 1:回显插件
│ │ ├── echo_plugin.h/cpp
│ │ └── CMakeLists.txt
│ └── plugin_calculator/ 示例插件 2:计算器插件
│ ├── calc_plugin.h/cpp
│ └── CMakeLists.txt
└── CMakeLists.txt 根 CMake(可选)
1. 插件接口(plugininterface.h)
#pragma once
#include <QtPlugin>
#include <QString>
#include <QWidget>
class PluginInterface {
public:
virtual ~PluginInterface() = default;
virtual QString name() const = 0;
virtual QString description() const = 0;
virtual QString version() const = 0;
virtual QWidget* createWidget(QWidget *parent = nullptr) = 0;
virtual void initialize() {}
};
#define PluginInterface_iid "org.example.PluginInterface/1.0"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
2. 示例插件:回显插件(echo_plugin.h & .cpp)
#pragma once
#include "plugininterface.h"
class EchoPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_PLUGIN_METADATA(IID PluginInterface_iid)
Q_INTERFACES(PluginInterface)
public:
QString name() const override { return "回显插件"; }
QString description() const override { return "简单输入输出回显演示"; }
QString version() const override { return "1.0.0"; }
QWidget* createWidget(QWidget *parent = nullptr) override;
void initialize() override;
};
#include "echo_plugin.h"
#include <QVBoxLayout>
#include <QLineEdit>
#include <QTextEdit>
#include <QPushButton>
#include <QDebug>
QWidget* EchoPlugin::createWidget(QWidget *parent) {
QWidget *w = new QWidget(parent);
QVBoxLayout *layout = new QVBoxLayout(w);
QLineEdit *input = new QLineEdit(w);
input->setPlaceholderText("输入内容,按回车回显");
QTextEdit *output = new QTextEdit(w);
output->setReadOnly(true);
QPushButton *clearBtn = new QPushButton("清空", w);
layout->addWidget(input);
layout->addWidget(output);
layout->addWidget(clearBtn);
connect(input, &QLineEdit::returnPressed, this, [=]() {
QString text = input->text();
if (!text.isEmpty()) {
output->append(">> " + text);
input->clear();
}
});
connect(clearBtn, &QPushButton::clicked, output, &QTextEdit::clear);
return w;
}
void EchoPlugin::initialize() {
qDebug() << "回显插件初始化完成";
}
3. 主程序加载与调用(mainwindow.cpp 核心部分)
#include <QPluginLoader>
#include <QDir>
#include <QDebug>
#include "plugininterface.h"
void MainWindow::loadPlugins() {
QDir pluginsDir(qApp->applicationDirPath());
#if defined(Q_OS_WIN)
pluginsDir.cd("plugins");
#elif defined(Q_OS_MAC)
pluginsDir.cdUp();
pluginsDir.cd("Plugins");
#else
pluginsDir.cd("plugins");
#endif
const QStringList files = pluginsDir.entryList(QDir::Files);
for (const QString &fileName : files) {
if (!QLibrary::isLibrary(fileName)) continue;
QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = loader.instance();
if (!plugin) continue;
if (auto* myPlugin = qobject_cast<PluginInterface*>(plugin)) {
qDebug() << "加载插件成功:" << myPlugin->name() << "版本:" << myPlugin->version();
QWidget *w = myPlugin->createWidget();
if (w) ui->tabWidget->addTab(w, myPlugin->name());
myPlugin->initialize();
} else {
qDebug() << "插件" << fileName << "未实现 PluginInterface 接口";
}
}
}
CMake 配置(推荐方式)
cmake_minimum_required(VERSION 3.16)
project(QtPluginDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
# 主程序
add_executable(QtPluginDemo main.cpp mainwindow.cpp)
target_link_libraries(QtPluginDemo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
# 插件接口(静态库,供插件和主程序链接)
add_library(plugin_interface INTERFACE)
target_include_directories(plugin_interface INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/plugin_core)
# 插件:echo_plugin
add_library(echo_plugin MODULE plugins/plugin_echo/echo_plugin.cpp)
target_link_libraries(echo_plugin PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets plugin_interface)
set_target_properties(echo_plugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins")
常见问题排查清单(VS / Creator 都适用)
| 问题 | 原因 | 解决方法 |
|---|
| QPluginLoader::instance() 返回 nullptr | 插件未正确编译或路径不对 | 确认插件输出到 plugins 目录,检查文件后缀(.dll / .so) |
| qobject_cast<PluginInterface*> 失败 | 未定义 IID 或 Q_DECLARE_INTERFACE | 必须在接口头文件中定义 IID 并使用 Q_DECLARE_INTERFACE |
| 插件加载成功但无输出 | 插件未调用 initialize() | 主动调用插件的初始化函数 |
| Windows 报'无法加载插件' | 缺少 Qt 运行时 DLL | 把 Qt 的 bin 目录加入 PATH,或使用 windeployqt 工具部署 |
| Linux 插件加载失败 | 缺少 .so 依赖 | 使用 ldd plugins/libxxx.so 检查依赖,安装缺失库 |
策略模式详解
1. 引言
策略模式(Strategy Pattern)是行为型设计模式的一种,它定义了一系列算法(策略),将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端,从而实现'算法与客户端的解耦'。
在实际开发中,当一个类需要根据不同条件选择不同行为时(如支付系统选择微信/支付宝/银行卡),直接用 if-else 会导致代码膨胀、难以扩展。策略模式通过多态接口解决这个问题,让行为像'插件'一样可插拔。
策略模式的核心价值:开闭原则(对扩展开放、对修改封闭),特别适合 Qt 项目中的动态行为切换(如渲染策略、事件处理策略)。
2. 策略模式的概念与核心思想
概念:
策略模式将可变的行为抽象为一个策略接口(Strategy),然后由上下文类(Context)持有策略对象的引用。客户端通过上下文类调用策略,从而实现行为动态切换。
- 上下文类不直接实现行为,而是委托给策略对象
- 策略对象通过多态(虚函数)提供具体实现
- 运行时可替换策略对象,实现行为变化
- 隔离变化:行为变化不影响上下文
- 复用算法:策略类可独立测试/复用
- 避免条件分支:取代 if-else / switch-case
3. 策略模式的结构与组成部分
- 抽象策略接口(Strategy):定义所有策略的公共接口(纯虚函数)
- 具体策略类(ConcreteStrategy):实现接口,提供算法变体
- 上下文类(Context):持有策略引用,委托执行
Context - strategy: Strategy*
+ executeStrategy()
Strategy (接口)
+ algorithmInterface() = 0
ConcreteStrategyA
+ algorithmInterface() { }
ConcreteStrategyB
+ algorithmInterface() { }
4. 策略模式的优点与缺点
- 灵活性高:运行时动态更换策略
- 代码复用:策略类可独立于上下文复用
- 符合开闭原则:新增策略无需改动上下文
- 测试友好:每个策略独立单元测试
- 避免分支膨胀:取代长 if-else 链
- 类数量增加:每个策略一个类
- 客户端需知道所有策略:选择策略时需了解变体
- 开销稍高:虚函数调用 + 对象创建
5. 策略模式的适用场景
- 算法变体多:支付方式(微信/支付宝/银行卡)
- 行为动态切换:渲染策略(2D/3D/OpenGL/Vulkan)
- 条件分支复杂:排序算法(快速/归并/插入/冒泡)
- Qt 项目典型:事件处理策略(如鼠标事件分派)、动画效果策略(如淡入/滑动/旋转)
不适用场景:行为固定、变体少时(简单 if-else 就够)。
6. C++ 中的策略模式实现示例(现代写法)
示例场景:排序策略(快速排序 vs 冒泡排序),上下文是数据处理器。
#pragma once
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual void sort(std::vector<int>& data) = 0;
virtual std::string name() const = 0;
};
#pragma once
#include "strategy.h"
#include <vector>
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::sort(data.begin(), data.end());
}
std::string name() const override { return "快速排序"; }
};
#pragma once
#include <memory>
#include "strategy.h"
class DataProcessor {
private:
std::unique_ptr<SortStrategy> strategy_;
public:
void setStrategy(std::unique_ptr<SortStrategy> s) {
strategy_ = std::move(s);
}
void process(std::vector<int>& data) {
if (strategy_) {
strategy_->sort(data);
}
}
};
#include "context.h"
#include "quick_sort.h"
#include <iostream>
int main() {
std::vector<int> data = {5, 3, 8, 1, 9};
DataProcessor proc;
proc.setStrategy(std::make_unique<QuickSort>());
proc.process(data);
std::cout << "排序后:";
for (int n : data) std::cout << n << " ";
std::cout << "\n";
return 0;
}
7. Qt 项目中的策略模式高级实践
Qt 典型场景:动态切换绘制策略(2D vs 3D)
#pragma once
#include <QObject>
#include <QPainter>
class DrawStrategy : public QObject {
Q_OBJECT
public:
virtual void draw(QPainter *painter, const QRect &rect) = 0;
};
#pragma once
#include "drawstrategy.h"
class TwoDStrategy : public DrawStrategy {
Q_OBJECT
public:
void draw(QPainter *painter, const QRect &rect) override {
painter->fillRect(rect, Qt::blue);
}
};
class Canvas : public QWidget {
std::unique_ptr<DrawStrategy> strategy_;
public:
void setStrategy(std::unique_ptr<DrawStrategy> s) {
strategy_ = std::move(s);
update();
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter painter(this);
if (strategy_) {
strategy_->draw(&painter, rect());
}
}
};
Canvas *canvas = new Canvas(this);
canvas->setStrategy(std::make_unique<TwoDStrategy>());
8. 常见陷阱与最佳实践
| 陷阱 | 后果 | 最佳实践 |
|---|
| 基类无虚析构 | 内存泄漏 | 所有接口类写 virtual ~Base() = default; |
| 忘记 override | 误写成隐藏而非覆盖 | C++11+ 强制写 override |
| 多态指针 delete 未用虚析构 | 未调用派生析构 | 用 std::unique_ptr 管理 |
| Qt 信号槽未写 Q_OBJECT | 信号失效 | 所有 QObject 子类写 Q_OBJECT |
| 策略类内存管理不当 | 泄漏或野指针 | 用 std::unique_ptr 持有 |
- 接口一律纯虚 + 虚析构
- 策略类轻量设计(无状态优先)
- Qt 项目中,策略类继承 QObject 支持信号
9. 完整示例项目:多态形状编辑器
#pragma once
#include <QObject>
#include <QPainter>
class ShapeStrategy : public QObject {
Q_OBJECT
public:
virtual void draw(QPainter *painter, const QRect &rect) = 0;
};
void MainWindow::on_switchStrategy_clicked() {
canvas->setStrategy(std::make_unique<CircleStrategy>());
}
总结
策略模式的核心是'行为封装 + 运行时替换',在 Qt 项目中常用于算法切换、事件处理、渲染等场景。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online