跳到主要内容 设计模式在 C++ 中的实战应用(一):创建型模式 | 极客日志
C++ 算法
设计模式在 C++ 中的实战应用(一):创建型模式 深入讲解 C++ 中三种核心创建型设计模式的实现与应用。涵盖单例模式的基础懒汉式、线程安全双重检查锁定及饿汉式实现,重点分析多线程环境下的安全性问题。介绍工厂方法模式通过子类决定对象创建类型,实现客户端与具体产品的解耦,符合开闭原则。阐述抽象工厂模式用于创建相关或依赖的对象族,支持跨平台控件库等场景的产品族切换。结合日志管理器和图形绘制系统实战案例,提供完整的 C++ 代码示例与对比分析,帮助开发者根据实际需求选择合适的模式提升代码灵活性与可维护性。
第 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_ = nullptr ;
std::mutex LogManager::mutex_;
void ThreadFunc (int thread_id) {
LogManager* log = LogManager::GetInstance ();
log->Log ("线程" + std::to_string (thread_id) + "调用成功" );
}
int main () {
std::thread t1 (ThreadFunc, 1 ) ;
std::thread t2 (ThreadFunc, 2 ) ;
std::thread t3 (ThreadFunc, 3 ) ;
std::thread t4 (ThreadFunc, 4 ) ;
std::thread t5 (ThreadFunc, 5 ) ;
t1. join ();
t2. join ();
t3. join ();
t4. join ();
t5. join ();
return 0 ;
}
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_;
void ReadConfig (int thread_id) {
ConfigManager& config = ConfigManager::GetInstance ();
std::cout << "线程" << thread_id << "获取 timeout 配置:" << config.GetConfig ("timeout" ) << std::endl;
}
int main () {
std::cout << "程序启动,开始创建线程..." << std::endl;
std::thread t1 (ReadConfig, 1 ) ;
std::thread t2 (ReadConfig, 2 ) ;
t1. join ();
t2. join ();
return 0 ;
}
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" ;
case LogLevel::ERROR: return "ERROR" ;
default : return "UNKNOWN" ;
}
}
std::string GetTimestamp () {
std::time_t now = std::time (nullptr );
char buf[64 ];
std::strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S" , std::localtime (&now));
return buf;
}
public :
static LogManager* GetInstance () {
if (instance_ == nullptr ) {
std::lock_guard<std::mutex> lock (mutex_) ;
if (instance_ == nullptr ) {
instance_ = new LogManager ();
}
}
return instance_;
}
void Log (LogLevel level, const std::string& message) {
std::string timestamp = GetTimestamp ();
std::string level_str = LevelToString (level);
std::string log_msg = "[" + timestamp + "] [" + level_str + "] " + message + "\n" ;
std::cout << log_msg;
std::lock_guard<std::mutex> lock (mutex_) ;
if (log_file_.is_open ()) {
log_file_ << log_msg;
log_file_.flush ();
}
}
void Debug (const std::string& message) { Log (LogLevel::DEBUG, message); }
void Info (const std::string& message) { Log (LogLevel::INFO, message); }
void Warn (const std::string& message) { Log (LogLevel::WARN, message); }
void Error (const std::string& message) { Log (LogLevel::ERROR, message); }
~LogManager () {
if (log_file_.is_open ()) {
log_file_.close ();
}
}
LogManager (const LogManager&) = delete ;
LogManager& operator =(const LogManager&) = delete ;
};
LogManager* LogManager::instance_ = nullptr ;
std::mutex LogManager::mutex_;
int main () {
LogManager* logger = LogManager::GetInstance ();
logger->Debug ("初始化数据库连接池" );
logger->Info ("程序启动成功,版本 v1.0.0" );
logger->Warn ("内存使用率超过 80%" );
logger->Error ("数据库连接失败:无法连接到 127.0.0.1:3306" );
return 0 ;
}
[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;
}
std::string GetName () const override {
return "Rectangle" ;
}
};
class Triangle : public Shape {
public :
void Draw () const override {
std::cout << "绘制三角形:△" << std::endl;
}
std::string GetName () const override {
return "Triangle" ;
}
};
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 ;
}
⚠️ 注意:工厂方法模式中,一个具体工厂对应一个具体产品 。如果需要创建一组相关联的产品(如'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 :
void Render () const override {
std::cout << "渲染 Mac 风格文本框:[⎡⎤________________]" << std::endl;
}
};
class MacComboBox : public ComboBox {
public :
void Render () const override {
std::cout << "渲染 Mac 风格下拉框:[选项 1 ▾]" << 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 (); }
};
class MacWidgetFactory : public WidgetFactory {
public :
Button* CreateButton () const override { return new MacButton (); }
TextBox* CreateTextBox () const override { return new MacTextBox (); }
ComboBox* CreateComboBox () const override { return new MacComboBox (); }
};
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 LinuxTextBox (); }
ComboBox* CreateComboBox () const override { return new LinuxComboBox (); }
};
int main () {
LinuxWidgetFactory linux_factory;
RenderUI (linux_factory);
return 0 ;
}
开始渲染界面...
渲染 Linux 风格按钮:<确定>
渲染 Linux 风格文本框:[▭▭▭▭▭▭▭▭▭▭]
渲染 Linux 风格下拉框:[选项 1 ↓]
界面渲染完成!
⚠️ 注意:抽象工厂模式的缺点是扩展产品等级结构困难 。例如,若需在所有平台中新增'复选框(CheckBox)'产品(新增产品等级),则需要修改抽象工厂接口及所有具体工厂类,违反开闭原则。因此,抽象工厂模式适用于产品族相对稳定的场景。
21.6 三种创建型模式的对比与选择 模式 核心关注点 适用场景 优点 缺点 单例模式 单个对象的全局唯一性 全局管理类(日志、配置、连接池) 节省资源,全局统一访问 线程安全实现复杂,扩展性差 工厂方法模式 单个产品的创建解耦 单个产品的动态扩展(如新增图形、算法) 符合开闭原则,扩展灵活 产品族扩展时工厂类冗余 抽象工厂模式 产品族的创建解耦 多平台、多风格的产品族切换(如控件库、框架) 产品族切换方便,一致性强 扩展产品等级结构困难
需全局唯一对象 → 单例模式;
需独立扩展单个产品 → 工厂方法模式;
需一组相关产品配合使用且需切换产品族 → 抽象工厂模式。
21.7 实战练习
设计一个线程安全的数据库连接池(单例模式),支持连接的创建、获取、释放,限制最大连接数为 10。
使用工厂方法模式设计一个文件解析器系统,支持 TXT、JSON、XML 三种文件格式的解析,要求新增文件格式时无需修改原有代码。
使用抽象工厂模式设计一个智能家居控制系统,支持'小米'和'华为'两个产品族,每个产品族包含灯光、空调、窗帘三种设备,要求能够切换产品族并控制设备的开关操作。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online