一、单例模式(Singleton Pattern)
单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在以下场景中非常有用:
当系统需要一个全局唯一的资源管理器时(如日志记录器、配置管理器等)。
当需要控制实例化过程时(如限制实例数量)。
单例模式可根据实现方式和线程安全性细分为多种类型。主要有以下几种:
1.1 饿汉式单例(Eager Singleton)- 不推荐
实现特点
类加载时就创建实例,线程安全。
缺点是如果实例很大,且不一定用到,会浪费资源。
代码示例
#include <memory>
class Singleton {
public:
// 全局访问点(返回引用)
static Singleton& GetInstance() noexcept {
return *instance_;
}
// 删除所有复制 / 移动能力
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
private:
Singleton() = default;
~Singleton() = default;
struct Deleter {
void operator()(Singleton* p) const noexcept { delete p; }
};
// 饿汉式:在全局静态存储期初始化
static std::unique_ptr<Singleton, Deleter> instance_;
};
// 程序启动时立即创建实例
std::unique_ptr<Singleton, Singleton::Deleter> Singleton::instance_{new Singleton};
使用方式
Singleton& s = Singleton::GetInstance();彻底禁止类对象被拷贝或移动
Singleton(const Singleton&) = delete; // 拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 拷贝赋值运算符
Singleton(Singleton&&) = delete; // 移动构造函数
Singleton& operator=(Singleton&&) = delete; // 移动赋值运算符任何试图进行 拷贝构造、拷贝赋值、移动构造、移动赋值 的代码都将编译失败。
Singleton s1 = Singleton::GetInstance(); // 触发拷贝构造
Singleton s2;
s2 = Singleton::GetInstance(); // 触发拷贝赋值
Singleton s3 = std::move(Singleton::GetInstance()); // 触发移动构造
Singleton s4;
s4 = std::move(Singleton::GetInstance()); // 触发移动赋值单例模式要求全局唯一实例。
如果允许拷贝或移动,就会出现多个实例,破坏唯一性。
通过把这四个特殊成员全部设为
= delete,编译器会在任何试图拷贝或移动对象时报错,从而保证“一个且仅有一个”实例。
私有构造函数 - 阻止任何人在类外 创建 第二份对象
如果构造函数是 public,任何人都能写:
Singleton s; // 直接栈上创建
auto s = new Singleton; // 直接堆上创建这就违背了“只有一个实例”的核心约束。
设为 private 后,这两句都会编译失败,唯一可构造的时机只剩下类自己的静态成员初始化:
static std::unique_ptr<Singleton, Singleton::Deleter> instance_{new Singleton}; // 只有这里能调构造函数私有析构函数 - 阻止任何人在类外 销毁 单例
把析构函数也设为 private,可以防止:
delete &Singleton::GetInstance(); // 外部强行 delete或者
Singleton s = Singleton::GetInstance(); // 栈上副本析构时会调析构函数一旦析构函数不可访问,这些代码统统编译不过,对象生命周期完全由类内部掌控。
强制统一入口
用户只能通过
GetInstance()拿到引用,既不能额外构造,也无法随意析构,
从而保证 “整个程序生命周期内只有一份实例,且由类自己管理”。
静态成员变量
static std::unique_ptr<Singleton, Deleter> instance_;非
static成员属于某个对象,static成员属于整个类,与任何对象无关,程序里只有一份实体。把
instance_设成static,是为了让它脱离任何具体对象,成为整个程序唯一的、在启动阶段就能准备好的单例实例。
全局初始化
std::unique_ptr<Singleton, Singleton::Deleter> Singleton::instance_{new Singleton};静态数据成员必须在类外“定义”且只能定义一次;
类内的
static std::unique_ptr<Singleton> instance_;只是声明,告诉编译器“有这么个变量”,但并未真正分配存储。
1.2 懒汉式单例(Lazy Singleton)- 推荐
实现特点
延迟到第一次调用
GetInstance()时才创建实例。C++11 起局部静态变量初始化自带线程安全,无需显式锁。
代码示例
#include <memory>
class Singleton {
public:
// 全局访问点(返回引用)
static Singleton& GetInstance() noexcept {
// C++11 起,函数局部静态变量初始化是线程安全的
static Singleton instance;
return instance;
}
// 删除所有复制 / 移动能力
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
该示例使用了“C++11 起局部静态变量初始化自带线程安全,无需显式锁”的特性,也称为 Meyers Singleton。若需要显式锁版本(兼容 C++03 或特殊需求),可用双检锁(DCLP)+ std::call_once/std::mutex,但现代 C++ 推荐上述局部静态变量写法,更简洁安全。
二、工厂模式(Factory Pattern)
工厂模式根据复杂度和扩展性分为三大类:
2.1 简单工厂模式(Simple Factory)
特点:
又称静态工厂方法,由一个工厂类根据参数决定创建哪个产品类的实例。
不属于GoF 23种设计模式,但实际应用广泛。
简单工厂适用于 产品种类不多且变化不频繁 的场景;如需更灵活的扩展,可升级为 工厂方法模式 或 抽象工厂模式。
代码示例:
#include <iostream>
#include <memory>
class Product {
public:
virtual void Show() const = 0; // 纯虚函数
virtual ~Product() = default; // 必须虚析构
};
class ProductA : public Product {
public:
void Show() const override { std::cout << "ProductA" << std::endl; }
};
class ProductB : public Product {
public:
void Show() const override { std::cout << "ProductB" << std::endl; }
};
class SimpleFactory {
public:
static std::unique_ptr<Product> CreateProduct(const std::string& type) {
if (type == "A") return std::make_unique<ProductA>();
if (type == "B") return std::make_unique<ProductB>();
return nullptr;
}
};
// 使用 SimpleFactory
int main() {
auto productA = SimpleFactory::CreateProduct("A");
productA->Show();
auto productB = SimpleFactory::CreateProduct("B");
productB->Show();
return 0;
}虚析构 - virtual ~Product() = default;
基类析构必须是虚函数,防止通过基类指针删除派生对象时发生未定义行为。
使用方
完全不关心对象创建细节,只依赖抽象接口
Show()。新增形状不会导致客户端重新编译。
2.2 工厂方法模式(Factory Method)
特点:
定义一个创建对象的接口,由子类决定实例化哪一个产品类。
新增产品时只需增加具体工厂类,符合开闭原则。
代码示例:
#include <iostream>
#include <memory>
// 1. 抽象产品:Shape
class Shape {
public:
virtual void draw() const = 0;
virtual ~Shape() = default;
};
// 2. 具体产品
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Draw Circle\n";
}
};
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Draw Rectangle\n";
}
};
// 3. 抽象工厂:提供工厂方法的接口
class ShapeFactory {
public:
virtual std::unique_ptr<Shape> createShape() const = 0;
virtual ~ShapeFactory() = default;
};
// 4. 具体工厂:每新增一种产品,就新增一个工厂子类
class CircleFactory : public ShapeFactory {
public:
std::unique_ptr<Shape> createShape() const override {
return std::make_unique<Circle>();
}
};
class RectangleFactory : public ShapeFactory {
public:
std::unique_ptr<Shape> createShape() const override {
return std::make_unique<Rectangle>();
}
};
// 5. 客户端:只依赖抽象工厂和抽象产品
void client(const ShapeFactory& factory) {
auto shape = factory.createShape(); // 多态创建
shape->draw();
}
// 6. 主函数:运行时切换工厂即可
int main() {
CircleFactory circleFactory;
RectangleFactory rectFactory;
client(circleFactory); // Draw Circle
client(rectFactory); // Draw Rectangle
return 0;
}
抽象产品 Shape
定义公共接口
draw(),所有具体形状必须实现。
具体产品 Circle / Rectangle
实现各自的绘制逻辑;新增产品时无需修改已有代码(符合开闭原则)。
抽象工厂 ShapeFactory
声明工厂方法
createShape(),返回抽象产品指针。工厂方法延迟到子类实现,因此叫 Factory Method。
具体工厂 CircleFactory / RectangleFactory
每个具体工厂负责生产一种具体产品。
新增产品时,只需再写一个
XXXFactory,不用改旧工厂或客户端。
客户端
只依赖抽象接口(
Shape和ShapeFactory),运行时通过多态切换不同工厂,从而创建不同产品。
2.3 抽象工厂模式(Abstract Factory)
特点:
提供一个接口,用于创建一系列相关或相互依赖的对象。
常用于产品族的场景,如UI控件的不同主题。
代码示例:
#include <iostream>
#include <memory>
/* ========== 抽象产品族 ========== */
// 按钮抽象
class Button {
public:
virtual void paint() const = 0;
virtual ~Button() = default;
};
// 复选框抽象
class CheckBox {
public:
virtual void paint() const = 0;
virtual ~CheckBox() = default;
};
/* ========== Windows 风格的具体产品 ========== */
class WinButton : public Button {
public:
void paint() const override {
std::cout << "Render a Windows-style button\n";
}
};
class WinCheckBox : public CheckBox {
public:
void paint() const override {
std::cout << "Render a Windows-style checkbox\n";
}
};
/* ========== macOS 风格的具体产品 ========== */
class MacButton : public Button {
public:
void paint() const override {
std::cout << "Render a macOS-style button\n";
}
};
class MacCheckBox : public CheckBox {
public:
void paint() const override {
std::cout << "Render a macOS-style checkbox\n";
}
};
/* ========== 抽象工厂 ========== */
class GUIFactory {
public:
virtual std::unique_ptr<Button> createButton() const = 0;
virtual std::unique_ptr<CheckBox> createCheckBox() const = 0;
virtual ~GUIFactory() = default;
};
/* ========== Windows 工厂 ========== */
class WinFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() const override {
return std::make_unique<WinButton>();
}
std::unique_ptr<CheckBox> createCheckBox() const override {
return std::make_unique<WinCheckBox>();
}
};
/* ========== macOS 工厂 ========== */
class MacFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() const override {
return std::make_unique<MacButton>();
}
std::unique_ptr<CheckBox> createCheckBox() const override {
return std::make_unique<MacCheckBox>();
}
};
/* ========== 客户端代码 ========== */
void renderUI(const GUIFactory& factory) {
auto button = factory.createButton();
auto checkbox = factory.createCheckBox();
button->paint();
checkbox->paint();
}
int main() {
std::unique_ptr<GUIFactory> factory;
#ifdef _WIN32
factory = std::make_unique<WinFactory>();
#else
factory = std::make_unique<MacFactory>();
#endif
renderUI(*factory);
return 0;
}抽象产品族
Button和CheckBox构成一个“产品族”。每增加一种控件(如
Slider),就要在抽象层增加接口,并在每个具体风格里实现。
具体产品
WinButton / WinCheckBox与MacButton / MacCheckBox分别实现 Windows 和 macOS 的绘制逻辑。新增 Linux 风格时,只需再加
LinuxButton / LinuxCheckBox,无需改动现有代码。
抽象工厂 GUIFactory
声明一系列 创建整个产品族 的接口:
createButton、createCheckBox。客户端只依赖此抽象接口,完全解耦具体类名。
具体工厂
WinFactory返回 Windows 风格产品;MacFactory返回 macOS 风格产品。新增 Linux 支持 → 新增
LinuxFactory,符合 开闭原则。
客户端
通过 运行时选择的工厂 统一创建一整套相关对象(按钮+复选框)。
避免在业务代码里出现任何
new WinButton、new MacCheckBox这样的具体类名。
三、总结与对比
开闭原则
开闭原则(Open-Closed Principle, OCP)是 SOLID 五大原则中的第二个,由 Bertrand Meyer 提出:
对扩展开放,对修改关闭。
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
通俗解释
开放:当需求变化时,可以通过 新增代码(继承、实现新接口、添加新类)来扩展系统功能。
关闭:不修改已有且已经测试通过的源代码(不动老代码),避免引入回归 Bug。
简单示例
违反 OCP 的写法(每次加图形都要改 drawAll)
enum Type { CIRCLE, RECTANGLE };
void drawAll(const std::vector<Shape*>& ss) {
for (auto* s : ss) {
switch (s->type) { // 每加一种图形都要改这里
case CIRCLE: ... break;
case RECTANGLE: ... break;
}
}
}符合 OCP 的写法(加图形只需新类)
class Shape {
public:
virtual void draw() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() const override { std::cout << "Circle\n"; }
};
class Rectangle : public Shape {
public:
void draw() const override { std::cout << "Rectangle\n"; }
};
void drawAll(const std::vector<std::unique_ptr<Shape>>& ss) {
for (const auto& s : ss) {
s->draw(); // 新增图形无需改这里
}
}简言之,加功能靠 加代码,不靠 改老代码。
评论区