第 21 章 设计模式在 C++ 中的实战应用(一):创建型模式
21.1 学习目标与重点
- 掌握创建型模式的核心思想:封装对象创建过程,解耦对象创建与使用逻辑,提升代码灵活性和可维护性。
- 深入理解单例模式、工厂方法模式、抽象工厂模式的设计原理、适用场景及 C++ 实现细节。
- 能够根据实际开发需求,合理选择创建型模式解决对象创建相关的工程问题。
- 重点:单例模式的线程安全实现、工厂模式的层级扩展、抽象工厂模式的产品族设计。
21.2 创建型模式概述
💡 设计模式是软件开发中反复出现的问题的成熟解决方案,而创建型模式是设计模式的三大分类之一(另外两类为结构型模式、行为型模式),其核心关注点是如何创建对象。
在 C++ 开发中,直接使用 new 关键字创建对象看似简单,但在复杂场景下会存在诸多问题:
- 对象创建逻辑复杂(如需要初始化多个参数、依赖其他对象、选择不同子类实现)时,会导致创建代码冗余且分散;
- 客户端与具体类强耦合,更换对象类型时需要修改大量客户端代码;
- 无法灵活控制对象的创建时机、生命周期(如全局唯一对象、池化对象)。
创建型模式通过封装对象创建细节,将创建逻辑与使用逻辑分离,让客户端无需关注'如何创建对象',只需专注'如何使用对象',从而提升代码的可扩展性、可维护性和复用性。
本章重点讲解 3 种最常用的创建型模式:单例模式、工厂方法模式、抽象工厂模式,后续章节将继续介绍建造者模式和原型模式。
21.3 单例模式(Singleton Pattern)
21.3.1 核心思想与适用场景
💡 单例模式的核心是:确保一个类在整个程序运行期间只有一个实例对象,并提供一个全局访问点。
适用场景:
- 系统中需要一个全局管理类(如配置管理器、日志管理器、数据库连接池);
- 对象创建成本高(如占用大量内存、依赖复杂资源),无需多个实例;
- 需严格控制对象实例数量,避免资源竞争或状态不一致。
设计要点:
- 私有构造函数(禁止外部通过
new 创建对象);
- 私有静态成员变量(存储唯一实例);
- 公有静态成员方法(提供全局访问点,负责创建/返回实例)。
21.3.2 C++ 实现方式(从基础到线程安全)
1. 基础懒汉式(非线程安全)
懒汉式:延迟初始化,即第一次调用时才创建实例。
#include <iostream>
class LogManager {
private:
LogManager() {
std::cout << "LogManager 实例创建成功" << std::endl;
}
static LogManager* instance_;
public:
static LogManager* GetInstance() {
if (instance_ == nullptr) {
instance_ = new LogManager();
}
return instance_;
}
void Log(const std::string& message) {
std::cout << "[Log] " << message << std::endl;
}
LogManager(const LogManager&) = delete;
LogManager& operator=(const LogManager&) = delete;
};
LogManager* LogManager::instance_ = nullptr;
int main() {
LogManager* log1 = LogManager::GetInstance();
LogManager* log2 = LogManager::GetInstance();
std::cout << "log1 地址:" << log1 << std::endl;
std::cout << "log2 地址:" << log2 << std::endl;
log1->Log("程序启动成功");
log2->Log("用户登录");
return 0;
}
运行结果:
LogManager 实例创建成功
log1 地址:0x7f8a9b000b20
log2 地址:0x7f8a9b000b20
[Log] 程序启动成功
[Log] 用户登录
⚠️ 注意:基础懒汉式在多线程环境下存在线程安全问题。当多个线程同时调用 GetInstance() 时,可能会执行多次 new 操作,创建多个实例(违反单例原则)。
2. 线程安全的懒汉式(加锁实现)
通过互斥锁(std::mutex)保护实例创建过程,确保同一时间只有一个线程能执行创建逻辑。
#include <iostream>
#include <mutex>
#include <thread>
class LogManager {
private:
LogManager() {
std::cout << "LogManager 实例创建成功" << std::endl;
}
static LogManager* instance_;
static std::mutex mutex_;
public:
static LogManager* GetInstance() {
if (instance_ == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
instance_ = new LogManager();
}
}
return instance_;
}
void Log(const std::string& message) {
std::cout << "[Log] " << message << std::endl;
}
LogManager(const LogManager&) = delete;
LogManager& operator=(const LogManager&) = delete;
};
LogManager* LogManager::instance_ = ;
std::mutex LogManager::mutex_;
{
LogManager* log = LogManager::();
log->( + std::(thread_id) + );
}
{
;
;
;
;
;
t();
t();
t();
t();
t();
;
}
运行结果:
LogManager 实例创建成功
[Log] 线程 1 调用成功
[Log] 线程 3 调用成功
[Log] 线程 2 调用成功
[Log] 线程 5 调用成功
[Log] 线程 4 调用成功
💡 技巧:双重检查锁定(DCLP)是线程安全懒汉式的优化方案。第一次无锁检查避免了每次调用都加锁的性能开销,第二次加锁检查确保了线程安全,是兼顾性能和安全性的常用实现。
3. 饿汉式(线程安全,推荐简单场景)
饿汉式:程序启动时(静态变量初始化阶段)就创建实例,无需加锁,天然线程安全。
#include <iostream>
#include <thread>
#include <unordered_map>
class ConfigManager {
private:
ConfigManager() {
std::cout << "ConfigManager 实例创建,加载配置文件..." << std::endl;
config_map_["timeout"] = "30s";
config_map_["max_conn"] = "1000";
}
static ConfigManager instance_;
std::unordered_map<std::string, std::string> config_map_;
public:
static ConfigManager& GetInstance() {
return instance_;
}
std::string GetConfig(const std::string& key) {
auto it = config_map_.find(key);
return it != config_map_.end() ? it->second : "default";
}
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
};
ConfigManager ConfigManager::instance_;
{
ConfigManager& config = ConfigManager::();
std::cout << << thread_id << << config.() << std::endl;
}
{
std::cout << << std::endl;
;
;
t();
t();
;
}
运行结果:
ConfigManager 实例创建,加载配置文件...
程序启动,开始创建线程...
线程 1 获取 timeout 配置:30s
线程 2 获取 timeout 配置:30s
单例模式实现对比
| 实现方式 | 线程安全 | 初始化时机 | 优点 | 缺点 |
|---|
| 基础懒汉式 | ❌ 否 | 第一次调用时 | 延迟初始化,节省内存 | 多线程下不安全 |
| 加锁懒汉式(DCLP) | ✅ 是 | 第一次调用时 | 延迟初始化 + 线程安全 | 实现稍复杂,有微小锁开销 |
| 饿汉式 | ✅ 是 | 程序启动时 | 实现简单,无锁开销 | 提前占用内存,不支持延迟 |
✅ 结论:
- 简单场景(无多线程或对象占用内存小):优先使用饿汉式,实现简单且安全;
- 复杂场景(多线程 + 对象创建成本高):使用加锁懒汉式(DCLP),兼顾延迟初始化和线程安全。
21.3.3 实战案例:C++ 日志管理器(单例模式应用)
以下是一个完整的日志管理器实现,结合单例模式 + 文件输出,支持日志级别、时间戳记录:
#include <iostream>
#include <fstream>
#include <mutex>
#include <ctime>
#include <string>
enum class LogLevel {
DEBUG,
INFO,
WARN,
ERROR
};
class LogManager {
private:
LogManager() {
std::time_t now = std::time(nullptr);
std::string filename = "log_" + std::to_string(now) + ".txt";
log_file_.open(filename, std::ios::out | std::ios::app);
if (!log_file_.is_open()) {
std::cerr << "日志文件打开失败!" << std::endl;
}
}
static LogManager* instance_;
static std::mutex mutex_;
std::ofstream log_file_;
std::string LevelToString(LogLevel level) {
switch (level) {
case LogLevel::DEBUG: return "DEBUG";
case LogLevel::INFO: return "INFO";
case LogLevel::WARN: return "WARN";
LogLevel::ERROR: ;
: ;
}
}
{
std:: now = std::();
buf[];
std::(buf, (buf), , std::(&now));
buf;
}
:
{
(instance_ == ) {
;
(instance_ == ) {
instance_ = ();
}
}
instance_;
}
{
std::string timestamp = ();
std::string level_str = (level);
std::string log_msg = + timestamp + + level_str + + message + ;
std::cout << log_msg;
;
(log_file_.()) {
log_file_ << log_msg;
log_file_.();
}
}
{ (LogLevel::DEBUG, message); }
{ (LogLevel::INFO, message); }
{ (LogLevel::WARN, message); }
{ (LogLevel::ERROR, message); }
~() {
(log_file_.()) {
log_file_.();
}
}
( LogManager&) = ;
LogManager& =( LogManager&) = ;
};
LogManager* LogManager::instance_ = ;
std::mutex LogManager::mutex_;
{
LogManager* logger = LogManager::();
logger->();
logger->();
logger->();
logger->();
;
}
运行结果(控制台):
[2024-05-20 15:30:45] [DEBUG] 初始化数据库连接池
[2024-05-20 15:30:45] [INFO] 程序启动成功,版本 v1.0.0
[2024-05-20 15:30:45] [WARN] 内存使用率超过 80%
[2024-05-20 15:30:45] [ERROR] 数据库连接失败:无法连接到 127.0.0.1:3306
日志文件(log_1716207045.txt) 中会同步记录上述内容,实现了全局唯一的日志管理功能。
21.4 工厂方法模式(Factory Method Pattern)
21.4.1 核心思想与适用场景
💡 工厂方法模式的核心是:定义一个创建对象的接口(工厂接口),但由子类决定要创建哪个类的对象。即'工厂父类负责定义创建对象的公共接口,工厂子类负责生成具体对象'。
解决的问题:
直接 new 具体类导致客户端与具体产品强耦合。例如:
Shape* shape = new Circle();
适用场景:
- 客户端不需要知道具体产品的类名,只需知道对应的工厂;
- 系统中有多个产品家族,且产品之间存在共性(可抽象为基类);
- 需动态扩展产品类型(新增产品时,只需新增对应的工厂子类,无需修改原有代码)。
角色划分:
- 抽象产品(Product):所有具体产品的基类(纯虚类);
- 具体产品(ConcreteProduct):抽象产品的实现类;
- 抽象工厂(Factory):定义创建产品的接口(纯虚方法);
- 具体工厂(ConcreteFactory):实现抽象工厂接口,创建具体产品。
21.4.2 C++ 实现示例:图形绘制工厂
假设我们需要开发一个图形绘制系统,支持圆形、矩形、三角形绘制,且未来可能扩展更多图形。使用工厂方法模式解耦客户端与具体图形类:
1. 定义抽象产品与具体产品
#include <iostream>
#include <string>
class Shape {
public:
virtual ~Shape() {}
virtual void Draw() const = 0;
virtual std::string GetName() const = 0;
};
class Circle : public Shape {
public:
void Draw() const override {
std::cout << "绘制圆形:○" << std::endl;
}
std::string GetName() const override {
return "Circle";
}
};
class Rectangle : public Shape {
public:
void Draw() const override {
std::cout << << std::endl;
}
{
;
}
};
: Shape {
:
{
std::cout << << std::endl;
}
{
;
}
};
2. 定义抽象工厂与具体工厂
class ShapeFactory {
public:
virtual ~ShapeFactory() {}
virtual Shape* CreateShape() const = 0;
};
class CircleFactory : public ShapeFactory {
public:
Shape* CreateShape() const override {
return new Circle();
}
};
class RectangleFactory : public ShapeFactory {
public:
Shape* CreateShape() const override {
return new Rectangle();
}
};
class TriangleFactory : public ShapeFactory {
public:
Shape* CreateShape() const override {
return new Triangle();
}
};
3. 客户端使用
void DrawShape(const ShapeFactory& factory) {
Shape* shape = factory.CreateShape();
std::cout << "正在绘制图形:" << shape->GetName() << std::endl;
shape->Draw();
delete shape;
}
int main() {
CircleFactory circle_factory;
DrawShape(circle_factory);
RectangleFactory rect_factory;
DrawShape(rect_factory);
TriangleFactory triangle_factory;
DrawShape(triangle_factory);
return 0;
}
运行结果:
正在绘制图形:Circle
绘制圆形:○
正在绘制图形:Rectangle
绘制矩形:□
正在绘制图形:Triangle
绘制三角形:△
21.4.3 扩展产品:新增图形(符合开闭原则)
假设需要新增'菱形(Diamond)'图形,只需新增具体产品和具体工厂,无需修改原有代码(符合开闭原则:对扩展开放,对修改关闭):
class Diamond : public Shape {
public:
void Draw() const override {
std::cout << "绘制菱形:◇" << std::endl;
}
std::string GetName() const override {
return "Diamond";
}
};
class DiamondFactory : public ShapeFactory {
public:
Shape* CreateShape() const override {
return new Diamond();
}
};
int main() {
DiamondFactory diamond_factory;
DrawShape(diamond_factory);
return 0;
}
新增运行结果:
正在绘制图形:Diamond
绘制菱形:◇
⚠️ 注意:工厂方法模式中,一个具体工厂对应一个具体产品。如果需要创建一组相关联的产品(如'Windows 风格控件'包含按钮、文本框、下拉框),工厂方法模式会产生大量工厂类,此时应使用抽象工厂模式。
21.5 抽象工厂模式(Abstract Factory Pattern)
21.5.1 核心思想与适用场景
💡 抽象工厂模式的核心是:提供一个接口,用于创建一组相关或相互依赖的对象,而无需指定它们的具体类。
与工厂方法模式的区别:
- 工厂方法模式:关注'单个产品'的创建,一个工厂对应一个产品;
- 抽象工厂模式:关注'产品族'的创建,一个工厂对应一组相关产品。
产品族与产品等级结构:
- 产品族:同一工厂生产的、功能相关的一组产品(如'Windows 风格控件'包含按钮、文本框、下拉框);
- 产品等级结构:同一类产品的不同实现(如'按钮'包含 Windows 按钮、Mac 按钮、Linux 按钮)。
适用场景:
- 系统需要使用多个产品族,且产品族内的产品相互依赖或配合使用;
- 系统需要切换产品族(如从 Windows 风格切换到 Mac 风格),且切换时无需修改客户端代码;
- 客户端不关心产品的创建细节,只关心产品族的选择。
角色划分:
- 抽象产品(AbstractProduct):产品族中每个产品的基类(如按钮基类、文本框基类);
- 具体产品(ConcreteProduct):抽象产品的具体实现(如 Windows 按钮、Mac 文本框);
- 抽象工厂(AbstractFactory):定义创建产品族中所有产品的接口(如创建按钮、创建文本框的方法);
- 具体工厂(ConcreteFactory):实现抽象工厂接口,创建某一产品族的所有产品(如 Windows 控件工厂创建 Windows 按钮、Windows 文本框)。
21.5.2 C++ 实现示例:跨平台控件库
假设我们需要开发一个跨平台控件库,支持 Windows 和 Mac 两个平台,每个平台包含按钮(Button)、文本框(TextBox)、下拉框(ComboBox)三个相关控件(产品族)。使用抽象工厂模式实现平台切换:
1. 定义抽象产品(产品族中的各个产品基类)
#include <iostream>
#include <string>
class Button {
public:
virtual ~Button() {}
virtual void Render() const = 0;
};
class TextBox {
public:
virtual ~TextBox() {}
virtual void Render() const = 0;
};
class ComboBox {
public:
virtual ~ComboBox() {}
virtual void Render() const = 0;
};
2. 定义具体产品(各平台的产品实现)
class WindowsButton : public Button {
public:
void Render() const override {
std::cout << "渲染 Windows 风格按钮:[确定]" << std::endl;
}
};
class WindowsTextBox : public TextBox {
public:
void Render() const override {
std::cout << "渲染 Windows 风格文本框:[________________]" << std::endl;
}
};
class WindowsComboBox : public ComboBox {
public:
void Render() const override {
std::cout << "渲染 Windows 风格下拉框:[选项 1 ▼]" << std::endl;
}
};
class MacButton : public Button {
public:
void Render() const override {
std::cout << "渲染 Mac 风格按钮:● 确定 ●" << std::endl;
}
};
class MacTextBox : public TextBox {
public:
{
std::cout << << std::endl;
}
};
: ComboBox {
:
{
std::cout << << std::endl;
}
};
3. 定义抽象工厂与具体工厂(产品族工厂)
class WidgetFactory {
public:
virtual ~WidgetFactory() {}
virtual Button* CreateButton() const = 0;
virtual TextBox* CreateTextBox() const = 0;
virtual ComboBox* CreateComboBox() const = 0;
};
class WindowsWidgetFactory : public WidgetFactory {
public:
Button* CreateButton() const override { return new WindowsButton(); }
TextBox* CreateTextBox() const override { return new WindowsTextBox(); }
ComboBox* CreateComboBox() const override { return new WindowsComboBox(); }
};
: WidgetFactory {
:
{ (); }
{ (); }
{ (); }
};
4. 客户端使用(切换产品族)
void RenderUI(const WidgetFactory& factory) {
std::cout << "开始渲染界面..." << std::endl;
Button* button = factory.CreateButton();
TextBox* textbox = factory.CreateTextBox();
ComboBox* combobox = factory.CreateComboBox();
button->Render();
textbox->Render();
combobox->Render();
delete button;
delete textbox;
delete combobox;
std::cout << "界面渲染完成!" << std::endl << std::endl;
}
int main() {
WindowsWidgetFactory windows_factory;
RenderUI(windows_factory);
MacWidgetFactory mac_factory;
RenderUI(mac_factory);
return 0;
}
运行结果:
开始渲染界面...
渲染 Windows 风格按钮:[确定]
渲染 Windows 风格文本框:[________________]
渲染 Windows 风格下拉框:[选项 1 ▼]
界面渲染完成!
开始渲染界面...
渲染 Mac 风格按钮:● 确定 ●
渲染 Mac 风格文本框:[⎡⎤________________]
渲染 Mac 风格下拉框:[选项 1 ▾]
界面渲染完成!
21.5.3 扩展产品族:新增 Linux 平台(符合开闭原则)
若需新增 Linux 平台控件(产品族),只需新增具体产品和具体工厂,无需修改客户端和原有工厂代码:
class LinuxButton : public Button {
public:
void Render() const override {
std::cout << "渲染 Linux 风格按钮:<确定>" << std::endl;
}
};
class LinuxTextBox : public TextBox {
public:
void Render() const override {
std::cout << "渲染 Linux 风格文本框:[▭▭▭▭▭▭▭▭▭▭]" << std::endl;
}
};
class LinuxComboBox : public ComboBox {
public:
void Render() const override {
std::cout << "渲染 Linux 风格下拉框:[选项 1 ↓]" << std::endl;
}
};
class LinuxWidgetFactory : public WidgetFactory {
public:
Button* CreateButton() const override { return new LinuxButton(); }
TextBox* CreateTextBox() const override { return new (); }
{ (); }
};
{
LinuxWidgetFactory linux_factory;
(linux_factory);
;
}
新增运行结果:
开始渲染界面...
渲染 Linux 风格按钮:<确定>
渲染 Linux 风格文本框:[▭▭▭▭▭▭▭▭▭▭]
渲染 Linux 风格下拉框:[选项 1 ↓]
界面渲染完成!
⚠️ 注意:抽象工厂模式的缺点是扩展产品等级结构困难。例如,若需在所有平台中新增'复选框(CheckBox)'产品(新增产品等级),则需要修改抽象工厂接口及所有具体工厂类,违反开闭原则。因此,抽象工厂模式适用于产品族相对稳定的场景。
21.6 三种创建型模式的对比与选择
| 模式 | 核心关注点 | 适用场景 | 优点 | 缺点 |
|---|
| 单例模式 | 单个对象的全局唯一性 | 全局管理类(日志、配置、连接池) | 节省资源,全局统一访问 | 线程安全实现复杂,扩展性差 |
| 工厂方法模式 | 单个产品的创建解耦 | 单个产品的动态扩展(如新增图形、算法) | 符合开闭原则,扩展灵活 | 产品族扩展时工厂类冗余 |
| 抽象工厂模式 | 产品族的创建解耦 | 多平台、多风格的产品族切换(如控件库、框架) | 产品族切换方便,一致性强 | 扩展产品等级结构困难 |
✅ 选择建议:
- 需全局唯一对象 → 单例模式;
- 需独立扩展单个产品 → 工厂方法模式;
- 需一组相关产品配合使用且需切换产品族 → 抽象工厂模式。
21.7 实战练习
- 设计一个线程安全的数据库连接池(单例模式),支持连接的创建、获取、释放,限制最大连接数为 10。
- 使用工厂方法模式设计一个文件解析器系统,支持 TXT、JSON、XML 三种文件格式的解析,要求新增文件格式时无需修改原有代码。
- 使用抽象工厂模式设计一个智能家居控制系统,支持'小米'和'华为'两个产品族,每个产品族包含灯光、空调、窗帘三种设备,要求能够切换产品族并控制设备的开关操作。