Week 2 Day 5:单例模式与工厂模式深度解析
核心要点速览
单例模式与工厂模式是创建型设计模式的核心代表,分别解决不同维度的对象创建问题:
单例模式的核心目标:
- 保证一个类只有一个实例
- 提供全局访问点
- 确保线程安全与资源正确管理
工厂模式的三层演进:
- 简单工厂:集中创建逻辑,通过参数区分产品类型
- 工厂方法:将创建延迟到子类,支持开闭原则
- 抽象工厂:创建产品家族,确保产品兼容性
现代C++实现特色:
- 使用Meyers单例替代传统懒汉/饿汉式
- 利用CRTP(奇特的递归模板模式)实现单例模板类
- 结合std::function与lambda表达式简化工厂实现
知识点精讲
1. 设计模式概述
概念定义:
设计模式是针对软件设计中常见问题的可复用解决方案模板,是经验丰富的开发者总结的最佳实践。
分类体系:
- 创建型模式:关注对象创建机制(单例、工厂、建造者等)
- 结构型模式:关注类与对象的组合方式(适配器、装饰器、代理等)
- 行为型模式:关注对象间的通信与职责分配(观察者、策略、命令等)
解决的问题:
- 提高代码复用性
- 增强系统可维护性
- 降低模块间耦合度
- 提升设计灵活性
2. 单例模式深度解析
核心原则:
确保一个类在任何时间点只有一个实例存在,并提供全局访问接口。
传统实现方式对比:
懒汉式单例(延迟初始化) :
class LazySingleton {
private:
static LazySingleton* instance;
static std::mutex mtx;
LazySingleton() = default;
~LazySingleton() = default;
public:
static LazySingleton* get_instance() {
std::lock_guard lock(mtx);
if (!instance) {
instance = new LazySingleton();
}
return instance;
}
// 删除拷贝操作
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
};
饿汉式单例(提前初始化) :
class EagerSingleton {
private:
static EagerSingleton instance;
EagerSingleton() = default;
~EagerSingleton() = default;
public:
static EagerSingleton& get_instance() {
return instance;
}
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;
};
// 静态成员初始化
EagerSingleton EagerSingleton::instance;
现代C++推荐:Meyers单例:
class MeyersSingleton {
private:
MeyersSingleton() = default;
~MeyersSingleton() = default;
public:
static MeyersSingleton& get_instance() {
static MeyersSingleton instance;
return instance;
}
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
};
Meyers单例的优势:
- 线程安全:C++11保证局部静态变量初始化线程安全
- 延迟初始化:首次调用时创建,避免不必要的资源占用
- 自动销毁:程序结束时自动调用析构函数,无内存泄漏风险
- 简洁优雅:代码量少,逻辑清晰
3. 单例模板类实现
通用单例模板设计:
template
class Singleton {
public:
// 禁止拷贝与赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 获取单例实例
static T& get_instance() {
static T instance;
return instance;
}
protected:
Singleton() = default;
~Singleton() = default;
};
// 使用示例
class Logger : public Singleton {
friend class Singleton;
private:
Logger() = default;
public:
void log(const std::string& message) {
std::cout << "[LOG] " << message << std::endl;
}
};
模板单例的关键特性:
- 使用CRTP实现静态多态
- 派生类构造函数必须私有化
- 通过friend声明允许模板基类访问私有构造函数
4. 工厂模式三层体系
简单工厂模式:
class Product {
public:
virtual ~Product() = default;
virtual void operation() = 0;
};
class ConcreteProductA : public Product {
public:
void operation() override {
std::cout << "Product A operation" << std::endl;
}
};
class ConcreteProductB : public Product {
public:
void operation() override {
std::cout << "Product B operation" << std::endl;
}
};
class SimpleFactory {
public:
static std::unique_ptr create_product(const std::string& type) {
if (type == "A") {
return std::make_unique();
} else if (type == "B") {
return std::make_unique();
}
throw std::invalid_argument("Unknown product type");
}
};
工厂方法模式:
class Creator {
public:
virtual ~Creator() = default;
virtual std::unique_ptr factory_method() = 0;
void some_operation() {
auto product = factory_method();
product->operation();
}
};
class ConcreteCreatorA : public Creator {
public:
std::unique_ptr factory_method() override {
return std::make_unique();
}
};
class ConcreteCreatorB : public Creator {
public:
std::unique_ptr factory_method() override {
return std::make_unique();
}
};
抽象工厂模式:
// 抽象产品A
class AbstractProductA {
public:
virtual ~AbstractProductA() = default;
virtual void operation_a() = 0;
};
// 抽象产品B
class AbstractProductB {
public:
virtual ~AbstractProductB() = default;
virtual void operation_b() = 0;
};
// 具体产品族1
class ProductA1 : public AbstractProductA {
public:
void operation_a() override {
std::cout << "Product A1 operation" << std::endl;
}
};
class ProductB1 : public AbstractProductB {
public:
void operation_b() override {
std::cout << "Product B1 operation" << std::endl;
}
};
// 抽象工厂
class AbstractFactory {
public:
virtual ~AbstractFactory() = default;
virtual std::unique_ptr create_product_a() = 0;
virtual std::unique_ptr create_product_b() = 0;
};
// 具体工厂1
class ConcreteFactory1 : public AbstractFactory {
public:
std::unique_ptr create_product_a() override {
return std::make_unique();
}
std::unique_ptr create_product_b() override {
return std::make_unique();
}
};
5. 设计模式的选择原则
适用场景判断:
| 模式 | 适用场景 | 关键特征 |
|---|---|---|
| 单例模式 | 全局配置、日志系统、线程池、缓存 | 唯一实例、全局访问 |
| 简单工厂 | 产品类型有限、创建逻辑简单 | 集中创建、参数区分 |
| 工厂方法 | 产品类型可扩展、创建逻辑复杂 | 子类决定、开闭原则 |
| 抽象工厂 | 产品家族、平台相关实现 | 产品兼容、接口统一 |
避免过度设计的原则:
- YAGNI原则:你不需要它(You Aren't Gonna Need It)
- KISS原则:保持简单愚蠢(Keep It Simple, Stupid)
- 最简可行方案:先用最简单方式实现,需要时再重构为模式
现代C++特性对模式的影响:
- 移动语义:简化资源管理,减少拷贝开销
- lambda表达式:替代小型策略对象,代码更紧凑
- 可变参数模板:支持灵活的产品创建接口
- 类型推导:减少模板模式中的类型冗余
关联知识地图
设计模式
创建型模式
结构型模式
行为型模式
单例模式
工厂模式
Meyers单例
模板单例
线程安全
简单工厂
工厂方法
抽象工厂
开闭原则
产品家族
设计原则
SOLID
DRY
KISS
现代C++
移动语义
智能指针
lambda
类型推导
知识关联要点:
- 单例模式与资源管理:RAII原则确保资源正确释放
- 工厂模式与开闭原则:支持扩展,避免修改
- 设计模式与SOLID原则:模式是原则的具体体现
- 现代C++特性与模式简化:语言特性减少模式实现复杂度
实战练习指引
关联今日实战:实现线程安全的单例模板类
核心要求:
- 实现通用的单例模板基类
- 保证线程安全性
- 支持移动语义与资源正确管理
- 提供简洁易用的接口
实现步骤分解:
步骤1:设计模板基类框架
template
class Singleton {
public:
// 禁止拷贝构造与赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 支持移动语义(可选)
Singleton(Singleton&&) = default;
Singleton& operator=(Singleton&&) = default;
// 全局访问接口
static Derived& get_instance() {
static Derived instance;
return instance;
}
protected:
Singleton() = default;
virtual ~Singleton() = default;
};
步骤2:实现具体单例类
cpp
class ApplicationConfig : public Singleton {
friend class Singleton;
private:
std::unordered_map config_map;
// 私有构造函数
ApplicationConfig() {
// 初始化配置
config_map["log_level"] = "info";
config_map["max_connections"] = "100";
}
public:
// 业务接口
std::string get_value(const std::string& key) const {
auto it = config_map.find(key);
return it != config_map.end() ? it->second : "";
}
void set_value(const std::string& key, const std::string& value) {
config_map[key] = value;
}
// 支持范围遍历(现代C++风格)
auto begin() const { return config_map.begin(); }
auto end() const { return config_map.end(); }
};
步骤3:添加线程安全增强
template
class ThreadSafeSingleton {
private:
static std::unique_ptr instance;
static std::once_flag init_flag;
public:
static Derived& get_instance() {
std::call_once(init_flag, []() {
instance = std::make_unique();
});
return *instance;
}
// 禁止拷贝
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
protected:
ThreadSafeSingleton() = default;
virtual ~ThreadSafeSingleton() = default;
};
// 静态成员初始化
template
std::unique_ptr ThreadSafeSingleton::instance = nullptr;
template
std::once_flag ThreadSafeSingleton::init_flag;
步骤4:编写全面测试用例
void test_singleton_patterns() {
// 测试Meyers单例
{
auto& config1 = ApplicationConfig::get_instance();
auto& config2 = ApplicationConfig::get_instance();
assert(&config1 == &config2);
assert(config1.get_value("log_level") == "info");
config1.set_value("timeout", "30");
assert(config2.get_value("timeout") == "30");
}
// 测试线程安全单例
{
class ResourceManager : public ThreadSafeSingleton {
friend class ThreadSafeSingleton;
private:
int resource_count = 0;
ResourceManager() = default;
public:
int allocate() { return ++resource_count; }
};
// 多线程测试(伪代码示意)
std::vector threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([]() {
auto& rm = ResourceManager::get_instance();
rm.allocate();
});
}
for (auto& t : threads) {
t.join();
}
}
}
关键实现要点:
- 构造函数私有化:确保外部无法直接创建实例
- friend声明:允许模板基类访问私有构造函数
- 局部静态变量:利用C++11线程安全特性
- 删除拷贝操作:防止意外复制破坏单例
- 虚析构函数:确保派生类正确析构
扩展挑战(可选) :
- 实现支持依赖注入的单例工厂
- 开发基于策略的单例生命周期管理器
- 创建支持热重载的单例配置系统
自我检测题
选择题(单选)
- 关于Meyers单例的优势,以下哪项描述是错误的?
A. 线程安全由C++11标准保证
B. 支持延迟初始化,减少启动开销
C. 需要手动管理内存释放
D. 代码简洁,逻辑清晰
- 工厂方法模式与抽象工厂模式的主要区别是什么?
A. 工厂方法创建单一产品,抽象工厂创建产品家族
B. 工厂方法使用继承,抽象工厂使用组合
C. 工厂方法更简单,抽象工厂更复杂
D. 工厂方法支持多态,抽象工厂不支持
- 在单例模板类设计中,为什么需要将派生类的构造函数声明为私有?
A. 提高编译速度
B. 防止外部直接实例化
C. 减少代码体积
D. 支持多线程访问
- 简单工厂模式违反的开闭原则主要体现在:
A. 增加新产品需要修改工厂类代码
B. 产品创建逻辑过于复杂
C. 不支持产品继承体系
D. 工厂类职责过多
- 现代C++中,哪种特性最有助于简化工厂模式的实现?
A. auto类型推导
B. lambda表达式
C. 移动语义
D. 可变参数模板
简答题
-
解释单例模式的"双重检查锁定"实现原理及其在现代C++中的替代方案。
-
比较工厂方法模式与抽象工厂模式的适用场景,各举一个实际应用例子。
-
在设计单例类时,如何平衡线程安全与性能开销?请结合具体实现说明。
-
为什么说简单工厂模式是"不完全"的设计模式?它有哪些局限性?
-
现代C++的移动语义如何影响单例模式与工厂模式的设计与实现?
参考答案要点
选择题答案:
- C(Meyers单例不需要手动管理内存释放)
- A(工厂方法创建单一产品,抽象工厂创建产品家族)
- B(防止外部直接实例化)
- A(增加新产品需要修改工厂类代码)
- B(lambda表达式可以简化小型工厂的实现)
简答题要点:
- 双重检查锁定通过两次检查instance是否为空,中间加锁,减少锁竞争。现代C++推荐使用Meyers单例(局部静态变量)或std::call_once。
- 工厂方法适用于产品类型单一但需要扩展的场景(如文档编辑器创建不同类型的文档);抽象工厂适用于需要创建兼容产品家族的场景(如跨平台UI组件创建)。
- 根据使用场景选择:高频访问场景使用Meyers单例(无锁),需要延迟初始化且线程安全使用std::call_once,避免过度优化。
- 简单工厂集中了所有创建逻辑,违反开闭原则,增加新产品需修改现有代码,不支持产品体系的扩展。
- 移动语义允许工厂返回unique_ptr而非裸指针,简化资源管理;单例中的资源转移更安全高效。
进阶学习指引
推荐资源
| 资源类型 | 具体名称 | 学习重点 |
|---|---|---|
| 经典书籍 | 《设计模式:可复用面向对象软件的基础》 | 23种经典模式原理解析 |
| 现代指南 | 《Modern C++ Design》 | 基于策略的设计、模板元编程 |
| 在线教程 | Refactoring Guru - Design Patterns | 图文并茂的模式讲解 |
| 实战项目 | GitHub - design-patterns-cpp | C++设计模式实现示例 |
后续学习路径
-
本周剩余主题:
- 周六:设计模式综合应用(简易日志系统)
- 周日:面向对象设计原则复习(SOLID原则)
-
下周预告:
- Week 3:STL标准库深度解析
- 序列容器、关联容器、迭代器与算法
-
长期深化方向:
- 并发编程中的设计模式应用
- 大型框架中的模式组合实践
- 领域特定设计模式探索
学习提示:设计模式的核心价值不在于记忆具体实现,而在于理解其解决的问题与设计思想。在实际项目中,应根据具体需求灵活运用,避免机械套用。
祝学习顺利!











