mid
单例模式
说说什么是单例设计模式,如何实现
1.单例模式定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
前提
- 该类不能被复制。
- 该类不能被公开的创造
那么对于C++来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。
2.单例模式实现方式
单例模式通常有两种模式,分别为懒汉式单例和饿汉式单例。两种模式实现方式分别如下
懒汉式设计模式实现方式(2种)
-
静态指针 + 用到时初始化
-
局部静态变量
饿汉式设计模式(2种)
-
直接定义静态对象
-
静态指针 + 类外初始化时new空间实现
3.具体解析
1.懒汉模式: 懒汉模式的特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。以下是懒汉模式实现方式
懒汉模式实现一:静态指针 + 用到时初始化
template<typename T>
class Singleton {
public:
static T& getInstance() {
if (!value_) {
value_ = new T();
}
return *value_;
}
private:
Singleton();
~Singleton();
static T* value_;
};
template<typename T>
T* Singleton<T>::value_ = nullptr;
在单线程环境中,上述代码可以正常工作。然而,在多线程环境中,这种实现方式是线程不安全的。下面是对多线程环境下可能发生的问题的分段解释:
-
假设线程A和线程B都要访问 getInstance 函数。线程A进入函数并检查 if 条件,由于是第一次进入,value 为空,if 条件成立,准备创建对象实例。
-
然而,线程A可能在创建对象实例之前被操作系统的调度器中断,并被挂起(睡眠),将控制权交给线程B。
-
线程B同样进入 if 条件,发现 value 仍然为 NULL,因为线程A还没有来得及构造它就被中断了。线程B完成对象的创建并成功返回。
-
之后,线程A被唤醒并继续执行 new 来再次创建对象。这样一来,两个线程都构建了自己的对象实例,破坏了单例模式的唯一性。
除了线程安全问题,该实现还存在内存泄漏的问题。通过 new 创建的对象始终没有被释放。
改进
template<typename T>
class Singleton {
public:
static T& getInstance() {
if (!value_) {
static CGarbo garbo; // 定义一个静态局部变量,利用静态局部变量的特性确保线程安全
value_ = new T();
}
return *value_;
}
private:
class CGarbo {
public:
~CGarbo() {
if (Singleton::value_) {
delete Singleton::value_;
}
}
};
Singleton();
~Singleton();
static T* value_;
};
template<typename T>
T* Singleton<T>::value_ = nullptr;
在程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
-
在单例类内部定义专有的嵌套类
-
在单例类内定义私有的专门用于释放的静态成员
-
利用程序在结束时析构全局变量的特性,选择最终的释放时机
懒汉模式实现二:局部静态变量 (略)
如果存在多个单例对象且这几个单例对象相互依赖,可能会出现程序崩溃的危险。原因:对编译器来说,静态成员变量的初始化顺序和析构顺序是一个未定义的行为
2.饿汉模式
单例类定义的时候就进行实例化。因为main函数执行之前,全局作用域的类成员静态变量m_Instance已经初始化,故没有多线程的问题。
饿汉模式实现一:直接定义静态对象
// .h文件
class Singleton {
public:
static Singleton& GetInstance();
private:
Singleton() {}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton m_Instance;
};
// CPP文件
Singleton Singleton::m_Instance; // 类外定义-不要忘记写
Singleton& Singleton::GetInstance() {
return m_Instance;
}
// 函数调用
Singleton& instance = Singleton::GetInstance();
缺点
在程序开始时,就创建类的实例,如果Singleton对象产生很昂贵,而本身有很少使用,这种方式单从资源利用效率的角度来讲,比懒汉式单例类稍差些。但从反应时间角度来讲,则比懒汉式单例类稍好些。
请说说工厂设计模式,如何实现,以及它的优点
1.工厂设计模式的定义
定义一个创建对象的接口,让子类决定实例化哪个类,而对象的创建统一交由工厂去生产,有良好的封装性,既做到了解耦
2.工厂设计模式分类
工厂模式属于创建型模式,大致可以分为三类,简单工厂模式、工厂方法模式、抽象工厂模式
1.简单工厂模式
它的主要特点是需要在工厂类中做判断,从而创造相应的产品。当增加新的产品时,就需要修改工厂类。
举例:有一家生产处理器核的厂家,它只有一个工厂,能够生产两种型号的处理器核。客户需要什么样的处理器核,一定要显示地告诉生产工厂。
点击查看代码
//程序实例(简单工厂模式)
enum CTYPE {COREA, COREB};
class SingleCore
{
public:
virtual void Show() = 0;
};
//单核A
class SingleCoreA: public SingleCore
{
public:
void Show() { cout<<"SingleCore A"<<endl; }
};
//单核B
class SingleCoreB: public SingleCore
{
public:
void Show() { cout<<"SingleCore B"<<endl; }
};
//唯一的工厂,可以生产两种型号的处理器核,在内部判断
class Factory
{
public:
SingleCore* CreateSingleCore(enum CTYPE ctype)
{
if(ctype == COREA) //工厂内部判断
return new SingleCoreA(); //生产核A
else if(ctype == COREB)
return new SingleCoreB(); //生产核B
else
return NULL;
}
};
优点: 简单工厂模式可以根据需求,动态生成使用者所需类的对象,而使用者不用去知道怎么创建对象,使得各个模块各司其职,降低了系统的耦合性。
缺点:就是要增加新的核类型时,就需要修改工厂类。这就违反了开放封闭原则:软件实体(类、模块、函数)可以扩展,但是不可修改。
2.工厂方法模式
所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。