GoF 23种经典的设计模式——单例模式

2024-01-14 09:46:31 浏览数 (2)

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

单例模式的优点包括:

  • 提供对唯一实例的全局访问点,方便在代码中的任何位置使用该实例。
  • 避免重复创建相同的对象,节省系统资源。
  • 全局控制类的唯一实例,确保数据一致性。

注意:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

下面是一个使用 C 实现的单例模式的示例代码,将构造函数和拷贝构造函数声明为私有,防止外部创建实例:

代码语言:javascript复制
class Singleton {
private:
    static Singleton* instance; // 静态成员变量,用于保存唯一的实例

    // 将构造函数和拷贝构造函数声明为私有,防止外部创建实例
    Singleton() {}
    Singleton(const Singleton&) {}

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

    void someBusinessLogic() {
        // 单例类的其他成员函数
    }
};

Singleton* Singleton::instance = nullptr; // 初始化静态变量

int main() {
    Singleton* singleton1 = Singleton::getInstance();
    Singleton* singleton2 = Singleton::getInstance();

    // 通过getInstance()获得的实例应该是相同的
    if (singleton1 == singleton2) {
        std::cout << "singleton1 and singleton2 are the same instance." << std::endl;
    }

    singleton1->someBusinessLogic();

    return 0;
}

在这个例子中,Singleton 类有一个私有的静态成员变量 instance,用于保存唯一的实例。构造函数和拷贝构造函数都是私有的,这样就防止了外部直接创建实例。公共的静态成员函数 getInstance() 负责创建实例(如果尚未创建)并返回该实例的指针。

注意:自 C 11 起,静态局部变量在多线程环境下是线程安全的(C 11 标准要求编译器在初始化静态局部变量时提供线程安全的初始化保证。使用互斥锁或其他同步机制来保证只有一个线程可以执行初始化操作)。因此,可以使用静态局部变量来实现单例模式,而无需显式使用互斥锁。否则,公共的静态成员函数 getInstance() 需要使用同步锁 synchronized 防止多线程同时进入造成 instance 被多次实例化。 class Singleton { private: static Singleton* instance; static std::mutex mutex; // 添加互斥锁 Singleton() {} Singleton(const Singleton&) {} public: static Singleton* getInstance() { std::lock_guard lock(mutex); // 加锁 if (instance == nullptr) { instance = new Singleton(); } return instance; } void someBusinessLogic() { // 单例类的其他成员函数 } }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::mutex; // 初始化互斥锁

main() 函数中,我们通过 Singleton::getInstance() 获得两个实例的指针。由于单例模式的特性,这两个指针应该是相等的,因为它们都指向相同的唯一实例。然后可以通过实例指针调用单例类的其他成员函数,如 someBusinessLogic()

在实际开发中,可以考虑使用单例模式的情况包括:

  1. 系统中只需要存在一个实例:当整个系统只需要一个实例来管理某个资源、配置或服务时,可以使用单例模式。例如,一个全局的日志记录器、数据库连接池或线程池等。
  2. 全局访问点:当需要在系统的多个组件或模块中共享某个对象实例时,可以使用单例模式提供一个全局的访问点。这样可以方便地在任何地方获取该实例,并确保实例的一致性。
  3. 资源共享和避免重复创建:当多个对象需要共享同一个资源,并且避免重复创建相同的对象时,可以使用单例模式。例如,在游戏开发中,多个对象可能需要共享一个纹理资源或音频资源。
  4. 控制实例数量:当需要限制某个类的实例数量只能为一个时,可以使用单例模式。例如,系统中只能有一个窗口管理器或文件系统对象。
  5. 延迟实例化:当对象的创建和初始化过程比较耗费资源或时间时,可以使用单例模式进行延迟实例化。即在首次访问该实例时才进行创建和初始化,以提高系统的性能和效率。

单例模式的几种实现方式:

懒汉式(Lazy Initialization):

  • 在懒汉式中,单例实例在首次使用时才被创建。
  • 在多线程环境下,需要考虑线程安全性,以避免多个线程同时创建多个实例。
  • 一种常见的线程安全的懒汉式实现方式是在 getInstance() 方法中使用双重检查锁定(Double-Checked Locking)和同步锁来确保只有一个线程创建实例。

饿汉式(Eager Initialization):

  • 在饿汉式中,单例实例在类加载时就被创建,并在整个生命周期中始终存在。
  • 饿汉式保证了在任何时候都只有一个实例存在,无需考虑线程安全性。
  • 饿汉式是一种简单直接的实现方式,但在某些情况下可能会导致资源浪费,因为实例在整个生命周期中都存在,即使没有被使用。

双重检查锁定(Double-Checked Locking):

双重检查锁定是一种在懒汉式中使用的优化技术,用于在多线程环境下减少同步锁的开销。

在双重检查锁定中,首先检查实例是否已经被创建,如果没有,则使用同步锁对代码块进行加锁,然后再次检查实例是否已经被创建。

这种方式可以减少同步锁的开销,只有在实例未创建时才进行同步,已经创建的实例直接返回,避免了每次调用都需要进行同步的开销。

代码语言:javascript复制
#include 

class Singleton {
private:
 static Singleton* instance;
 static std::mutex mutex; // 用于线程安全

 Singleton() {}

public:
 static Singleton* getInstance() {
     if (instance == nullptr) { // 第一次检查,避免不必要的加锁
         std::lock_guard lock(mutex); // 加锁
         if (instance == nullptr) { // 第二次检查,确保只有一个线程创建实例
             instance = new Singleton();
         }
     }
     return instance;
 }
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex; // 初始化互斥锁

静态内部类(Static Inner Class):

静态内部类是一种利用类的静态属性来实现延迟初始化的方式。

在静态内部类中,单例实例在首次使用内部类时才被创建,利用了类加载的特性实现线程安全的延迟初始化。

这种方式既保证了线程安全性,又避免了同步锁的开销,是一种常见的单例模式实现方式。

代码语言:javascript复制
class Singleton {
private:
 /* 略 */

public:
 static Singleton& getInstance() {
     static Singleton instance; // 静态局部变量,在首次使用时初始化
     return instance;
 }
};

------本页内容已结束,喜欢请分享------


0 人点赞