目 录CONTENT

文章目录

C++设计模式:单例模式与工厂模式

TalentQ
2025-07-17 / 0 评论 / 0 点赞 / 28 阅读 / 0 字

一、单例模式(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不用改旧工厂或客户端

客户端

  • 只依赖抽象接口(ShapeShapeFactory),运行时通过多态切换不同工厂,从而创建不同产品。

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;
}

抽象产品族

  • ButtonCheckBox 构成一个“产品族”。

  • 每增加一种控件(如 Slider),就要在抽象层增加接口,并在每个具体风格里实现。

具体产品

  • WinButton / WinCheckBoxMacButton / MacCheckBox 分别实现 Windows 和 macOS 的绘制逻辑。

  • 新增 Linux 风格时,只需再加 LinuxButton / LinuxCheckBox,无需改动现有代码。

抽象工厂 GUIFactory

  • 声明一系列 创建整个产品族 的接口:createButtoncreateCheckBox

  • 客户端只依赖此抽象接口,完全解耦具体类名。

具体工厂

  • WinFactory 返回 Windows 风格产品;MacFactory 返回 macOS 风格产品。

  • 新增 Linux 支持 → 新增 LinuxFactory,符合 开闭原则

客户端

  • 通过 运行时选择的工厂 统一创建一整套相关对象(按钮+复选框)。

  • 避免在业务代码里出现任何 new WinButtonnew MacCheckBox 这样的具体类名。

三、总结与对比

单例模式

优点

缺点

线程安全性

饿汉式

实现简单,线程安全

资源浪费

安全

懒汉式(Meyers 单例)

延迟加载,节省资源

简洁,线程安全

C++11前不安全

安全(C++11后)

维度

简单工厂(Simple Factory)

工厂方法(Factory Method)

抽象工厂(Abstract Factory)

核心思想

一个“万能工厂”类,内部用 if/elseswitch 判断创建哪种产品

把“创建哪个对象”的决定权延迟到子类工厂

把“一族相关对象”的创建封装到一个工厂族

结构角色

1 个工厂 + N 个产品

1 个抽象工厂接口 + N 个具体工厂 + N 个产品

1 个抽象工厂族 + 每个系列一个具体工厂 + 多个产品族

新增产品

改工厂源码 → 违背开闭原则

新增一个工厂子类即可 → 符合开闭原则

新增一个系列工厂即可 → 符合开闭原则

复杂度

最低

中等

最高

典型场景

产品种类少且稳定

产品种类中等,需要灵活扩展

跨平台/多主题/多系列 需要一次性创建整套相关对象

代码规模

最小

中等

最大

示例

根据字符串创建 Shape

每个 ShapeFactory 创建一种 Shape

创建 WinButton+WinCheckBoxMacButton+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();          // 新增图形无需改这里
    }
}

简言之,加功能靠 加代码,不靠 改老代码。

0

评论区