3.1 单例模式
1.概念
用类来实现单例。由于某种需要,要保证一个类在程序的生命周期中只有一个实例,并且提供该实例的全局访问方法。
2.结构三要素
1)私有的静态对象属性private static instance,它的类型就是当前类的对象,静态的特性决定了程序一启动就要初始化完成当前对象的构造。
2)私有的构造函数(保证在程序中,无法通过new的方式来创建对象实例,只能用于类内部创建该单例对象)
3)公有的,静态的(类可以直接访问,因为无法创建对象,所以不能用对象来访问非静态的成员函数。),访问该实例对象的方法public static Singleton getInstance(){}
3.实现——饿汉式(代码见vs)
饿汉式就是应用程序刚启动时,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。以空间换时间。
优点:写法简单,在多线程下能保证单例实例的唯一性,运行效率高。
缺点:在外部未使用到该类时,类的实例已经创建,若类实例的创建比较消耗系统资源,并且外部一直没有调用该实例,造成资源浪费。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
//饿汉式
class Singleton
{
private:
static Singleton* instance;//1)私有的静态对象属性
Singleton() {};//2)私有的构造函数
public:
static Singleton* getInstance()//3)公有的,静态的访问该单例的方法
{
return instance;
}
};
//实例化类的静态成员,要在类外初始化
Singleton* Singleton::instance = new Singleton();//程序一启动,单例马上就创建出来了,并且在整个程序生命周期,只有一份
void test01()
{
//测试一下,单例是否生效
Singleton* ins=Singleton::getInstance();
Singleton* ins2=Singleton::getInstance();
//接下来通过查看ins和ins2的地址,就可以判断是否为同一个对象
cout << ins << "=" << ins2 << endl;
}
4.实现——懒汉式(代码见vs)
懒汉式就是应用程序刚启动时,并不创建实例,当外部调用该类的实例或者该类实例方法时,才创建该类的实例。是以时间换空间。
优点:实例在被使用时创建,可以节省系统资源,体现延迟加载的思想。
缺点:系统刚启动且未被外部调用时,实例没有创建,如果同时有多个线程调用实例方法getInstance,可能会产生多个实例。在多线程下,不能保证单例实例的唯一性,需使用同步,同步会导致多线程下争夺锁资源,降低运行效率。
//懒汉式
class Singleton_Lan
{
private:
static Singleton_Lan* instance;
Singleton_Lan() {};
public:
static Singleton_Lan* getInstance()
{
//把单例的创建时机放到这里,有人调用这个函数的时候,就要创建了
if (instance==nullptr)//加个判断,当单例没有被创建的时候,才去创建它
{
instance = new Singleton_Lan();//这里创建出单例
}
return instance;
}
};
void test02()
{
//测试一下,单例是否生效
Singleton_Lan* ins = Singleton_Lan::getInstance();
Singleton_Lan* ins2 = Singleton_Lan::getInstance();
//接下来通过查看ins和ins2的地址,就可以判断是否为同一个对象
cout << ins << "=" << ins2 << endl;
}
Singleton_Lan* Singleton_Lan::instance = nullptr;//懒汉式,程序启动的时候不创建单例,而是设置为空
5.验证懒汉模式下——多线程不安全问题(代码见vs)
//多线程下,懒汉模式不安全,无法保证单例的实现
class Singleton_Duo
{
private:
static Singleton_Duo* instance;
Singleton_Duo() { this_thread::sleep_for(chrono::milliseconds(500)); };//构造的时候加延时,这个时候就可以发现问题
public:
static Singleton_Duo* getInstance()
{
if (instance==nullptr)//这个判断,在多线程下是不可靠的
{
instance = new Singleton_Duo();
}
return instance;
}
};
Singleton_Duo* Singleton_Duo::instance = nullptr;
//准备线程绑定的函数
void threadFunc()
{
Singleton_Duo* ins=Singleton_Duo::getInstance();
cout << this_thread::get_id() << ":" << ins << endl;
}
void test03()
{
thread t3[3];
for (int i = 0; i < 3; i++)
{
t3[i] = thread(threadFunc);
}
for (int i = 0; i < 3; i++)
{
t3[i].join();
}
}
//如果换成饿汉式单例,多线程下也是安全的
void threadFunc_e()
{
Singleton* ins = Singleton::getInstance();
cout << this_thread::get_id() << ":" << ins << endl;
}
void test04()
{
thread t3[3];
for (int i = 0; i < 3; i++)
{
t3[i] = thread(threadFunc_e);
}
for (int i = 0; i < 3; i++)
{
t3[i].join();
}
}
6.懒汉式——加锁模式(代码见vs)
//通过加锁,解决多线程下懒汉式单例的问题
mutex mut;//锁对象
class Singleton_Duo_Suo
{
private:
static Singleton_Duo_Suo* instance;
Singleton_Duo_Suo() { this_thread::sleep_for(chrono::milliseconds(500)); }
public:
static Singleton_Duo_Suo* getInstance()
{
mut.lock();//加锁
if (instance==nullptr)
{
instance = new Singleton_Duo_Suo();
}
mut.unlock();//解锁
return instance;
}
};
Singleton_Duo_Suo* Singleton_Duo_Suo::instance = nullptr;
//加锁后的懒汉式,多线程绑定的函数
void threadFunc_suo()
{
Singleton_Duo_Suo* ins = Singleton_Duo_Suo::getInstance();
cout << this_thread::get_id() << ":" << ins << endl;
}
void test05()
{
thread t3[3];
for (int i = 0; i < 3; i++)
{
t3[i] = thread(threadFunc_suo);
}
for (int i = 0; i < 3; i++)
{
t3[i].join();
}
}
7.单例释放问题
一般单例模式不用考虑内存释放问题,因为单个对象占用的空间不大。如果手动释放,会影响调用这个对象的资源,并且bug很难找。
如果非要手动释放,可以用这两种方式实现:
1)调用delete释放内存
2)单例中提供静态方法用于释放内存
8.饿汉式——局部静态变量实现单例(代码见vs)
使用局部静态变量,非常强大的方法,完全实现了单例的特性,而且代码量更少,就是把上面的第一要素的对象属性放在getInstance函数中去创建。它的作用域就是getInstance函数中。
不用担心单例销毁的问题,静态局部变量离开作用域后,不会马上被销毁,仍然驻留在内存中,只是无法被访问,当再次调用getInstance函数的时候,就能重新访问这个变量。只有当程序结束时,该变量才会被销毁,从内存中释放,此时,单例对象也被回收了。静态变量跟程序的生命周期一致。
不用担心多线程不安全问题,因为在编译器层面,本身就是支持静态局部变量的线程安全的,所以使用静态局部变量来保存单例,也是安全的。
优点:解决线程不安全问题;解决单例析构问题;代码简洁易懂。
//局部静态变量实现单例,可以保证多线程安全
class Singleton_Jubu
{
private:
Singleton_Jubu(){}
public:
static Singleton_Jubu* getInstance()
{
//定义一个局部静态变量来指向单例对象
static Singleton_Jubu* instance_jubu = new Singleton_Jubu();
return instance_jubu;
}
};
void test06()
{
Singleton_Jubu* ins_jubu = Singleton_Jubu::getInstance();
Singleton_Jubu* ins2_jubu = Singleton_Jubu::getInstance();
cout << ins_jubu << "=" << ins2_jubu << endl;
}
//多线程下,验证局部静态变量单例的安全性
class Singleton_Jubu_Duo
{
private:
Singleton_Jubu_Duo() { this_thread::sleep_for(chrono::milliseconds(500)); }
public:
static Singleton_Jubu_Duo* getInstance()
{
static Singleton_Jubu_Duo* instance_jubu = new Singleton_Jubu_Duo();
return instance_jubu;
}
};
void threadFunc_jubu()
{
Singleton_Jubu_Duo* ins = Singleton_Jubu_Duo::getInstance();
cout << this_thread::get_id() << ":" << ins << endl;
}
void test07()
{
thread t3[3];
for (int i = 0; i < 3; i++)
{
t3[i] = thread(threadFunc_jubu);
}
for (int i = 0; i < 3; i++)
{
t3[i].join();
}
}
9.单例总结
优点
1. 在内存中只有一个对象,节省内存空间。
2. 避免频繁的创建销毁对象,可以提高性能。
缺点
1. 没有接口,扩展困难。
2. 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
应用场景
1. 要求生成唯一序列号的环境。
2. 在整个项目中需要一个共享访问点或共享数据。
3. 创建一个对象需要消耗的资源过多。
4. 需要定义大量的静态常量和静态方法。如math类
注意事项
在高并发多线程情况下,注意普通单例模式的线程同步问题。
3.2 工厂模式
1.概念
在开发过程中完成某个任务时,都是通过调用实例对象的方法来实现的,需要一个准备动作(实例化对象)。如果一个类在实例化对象时操作很复杂(需要读配置文件,获取参数),可以用工厂模式,让用户和类分离,不要让用户操心对象如何创建的问题。该模式用于封装和管理对象的创建,是一种创建型模式。
工厂模式有三种:
简单工厂模式:工厂根据用户参数可以生产任意产品(产品即对象),工厂只有一层,没有抽象层,产品有抽象层和具体层。
工厂方法模式:工厂增加一个抽象层,派生出具体的工厂,具体的工厂只能生产具体的某个产品,客户不需要传参,只需要使用不同的工厂就能生产对应的产品。
抽象工厂模式:工厂和产品都有抽象层和具体层,最大的改变是具体层的每个工厂,都可以生产全部的产品,这些产品根据由哪个工厂生产的划分为不同的产品族,一个工厂的产品都属于一个产品族。
2.简单工厂模式(代码见vs)
简单工厂模式也被称为静态工厂模式;消费和生产完全分开,客户端只需要知道自己需要什么产品,如何来使用产品就可以了,具体的产品生产任务由具体的工厂类来实现。工厂类根据传进来的参数生产具体的产品供消费者使用。
#include <iostream>
using namespace std;
//简单工厂
//父类,水果类,抽象类
class AbsFruit
{
public:
virtual void showName() = 0;//纯虚函数,接口
};
//具体产品类,苹果
class Apple :public AbsFruit
{
public:
virtual void showName() { cout << "我是苹果" << endl; }
};
//具体产品类,桃子
class Peach :public AbsFruit
{
public:
virtual void showName() { cout << "我是桃子" << endl; }
};
//具体产品类,香蕉
class Banana :public AbsFruit
{
public:
virtual void showName() { cout << "我是香蕉" << endl; }
};
//简单工厂类
class SimpleFactory
{
public:
AbsFruit* createFruit(string str)//根据参数决定生产什么产品
{
if (str=="apple")
{
return new Apple();
}
else if (str=="peach")
{
return new Peach();
}
else if (str=="banana")
{
return new Banana();
}
}
};
void test01()
{
SimpleFactory sf = SimpleFactory();
AbsFruit* a=sf.createFruit("apple");
a->showName();
AbsFruit* b = sf.createFruit("banana");
b->showName();
delete a;
delete b;
}
简单工厂模式的构成:
具体的工厂角色:Factory;在工厂中根据参数不同生产出不同的产品;
抽象的产品角色:Product;在抽象产品类中声明接口,在具体的产品类中实现;
具体的产品角色:ProductA 和ProdcutB;在具体的类中实现抽象产品类的接口,也可以实现自己的业务逻辑。
简单工厂模式的总结
优点
工厂类包含必要的逻辑判断,可以根据用户需求动态实例化相关的类。
客户端可以免除直接创建产品对象的职责,去除了与具体产品的依赖。工厂和产品的职责区分明确。
缺点
增加产品,需要修改工厂类,扩展困难,不符合开闭原则。
客户创建实例对象时需要传入参数,需要记住参数的格式,比较麻烦。
应用场景
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
3.工厂方法模式(也可以称为复杂工厂)(代码见vs)
简单工厂方法违背了对扩展开放的原则,添加产品需要修改工厂类,并且高度依赖用户输入的参数。工厂方法模式是在简单工厂模式的基础上,工厂添加了一个抽象层。将工厂共同的动作抽象出来,作为抽象类,而具体的行为由子类去实现,让子类去决定生产什么样的产品。
//工厂方法模式
//抽象层工厂
class AbsFactory
{
public:
virtual AbsFruit* createFruit() = 0;
};
//苹果工厂
class AppleFactory :public AbsFactory
{
public:
virtual AbsFruit* createFruit() { return new Apple(); }
};
//桃子工厂
class PeachFactory :public AbsFactory
{
public:
virtual AbsFruit* createFruit() { return new Peach(); }
};
//香蕉工厂
class BananaFactory :public AbsFactory
{
public:
virtual AbsFruit* createFruit() { return new Banana(); }
};
void test02()
{
//想要什么产品,就需要什么工厂
AbsFactory* apple_fac = new AppleFactory();//创建一个苹果工厂
AbsFruit* apple=apple_fac->createFruit();//生产苹果
apple->showName();
AbsFactory* banana_fac = new BananaFactory();
AbsFruit* banana = banana_fac->createFruit();
banana->showName();
delete apple_fac;
delete banana_fac;
delete apple;
delete banana;
}
工厂方法模式总结
工厂方法模式 = 简单工厂模式 + 开闭原则
优点
继承了简单工厂模式的优点。
系统的可扩展性变得更好,符合开闭原则。
降低了用户的使用难度,不用考虑简单工厂的传参问题。
缺点
系统中的类是成对增加,增加了系统的复杂度和理解度。
应用场景
在设计的初期,就考虑到产品在后期会进行扩展的情况下,可以使用工厂方法模式。
4.抽象工厂模式(代码见vs)
//抽象工厂
//抽象苹果
class AbsApple
{
public:
virtual void showName() = 0;//纯虚函数,接口
};
//中国苹果
class ChinaApple :public AbsApple
{
public:
virtual void showName() { cout << "中国苹果" << endl; }
};
//日本苹果
class JapApple :public AbsApple
{
public:
virtual void showName() { cout << "日本苹果" << endl; }
};
//抽象桃子
class AbsPeach
{
public:
virtual void showName() = 0;//纯虚函数,接口
};
//中国桃子
class ChinaPeach :public AbsPeach
{
public:
virtual void showName() { cout << "中国桃子" << endl; }
};
//日本桃子
class JapPeach :public AbsPeach
{
public:
virtual void showName() { cout << "日本桃子" << endl; }
};
//抽象工厂
class AbsFactory_Abs
{
public:
virtual AbsApple* createApple() = 0;
virtual AbsPeach* createPeach() = 0;
};
//中国工厂
class ChinaFactory :public AbsFactory_Abs
{
virtual AbsApple* createApple() { return new ChinaApple(); }
virtual AbsPeach* createPeach() { return new ChinaPeach(); }
};
//日本工厂
class JapFacotry :public AbsFactory_Abs
{
virtual AbsApple* createApple() { return new JapApple(); }
virtual AbsPeach* createPeach() { return new JapPeach(); }
};
void test03()
{
//抽象工厂,每个工厂都能生产所有的产品
AbsFactory_Abs* chinaFac = new ChinaFactory();//中国工厂
AbsApple* china_apple = chinaFac->createApple();//生产中国苹果
AbsPeach* china_peach = chinaFac->createPeach();//生产中国桃子
china_apple->showName();//使用产品
china_peach->showName();//使用产品
delete chinaFac;
delete china_apple;
delete china_peach;
AbsFactory_Abs* japFac = new JapFacotry();//日本工厂
AbsApple* jap_apple = japFac->createApple();//生产日本苹果
AbsPeach* jap_peach = japFac->createPeach();//生产日本桃子
jap_apple->showName();//使用产品
jap_peach->showName();//使用产品
delete japFac;
delete jap_apple;
delete jap_peach;
}
抽象工厂总结
优点
1、抽象工厂模式隔离了具体产品的生产,使得客户并不需要知道什么产品被创建,一个工厂可以创建各种产品。
2、当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。可以理解为系统给用户提供了很多套相似的产品,每一套产品属于一个产品族,大体功能类似,但有一些细微的差别,比如同样的软件功能,但由于不同国家地区的法律法规和风俗不同,需要做出调整。
3、增加新的工厂族和产品族很方便,只需要添加新的工厂族即可,无须修改已有系统,符合“开闭原则”。
缺点
增加新的产品等级很复杂,比如增加一个橘子产品,需要修改抽象工厂和所有的具体工厂类,违反了开闭原则。
应用场景
适用于产品种类多的场合,主要用于创建一组相关的产品(属于同一个产品族),为它们提供创建的接口。注意事项:产品族易扩展,产品等级难扩展。
5.工厂模式总结
简单工厂模式:
产品有抽象层和具体层,工厂没有抽象层。工厂用来生产同一等级结构中的具体任意产品。对于增加新的产品,就要修改工厂类(需要根据参数决定生产什么产品)。符合单一职责原则。不符合开闭原则。客户传参麻烦。
工厂方法模式:
产品有抽象层和具体层,工厂也有抽象层和具体层,具体工厂用来生产固定产品。支持增加任意产品,但是增加一个产品就要增加一对(产品类和工厂类)。新增产品时不需要更改已有的工厂。符合单一职责原则、符合开闭原则。但是引入了复杂性。
抽象工厂模式 :
工厂和产品都有抽象层,工厂可以生产各种产品,不仅仅是固定的产品,这些产品都来自于同一个产品族。增加工厂族很容易,产品族容易扩展。但是增加新产品很难,需要修改已有的各个工厂族以及抽象工厂。符合单一职责原则,部分符合开闭原则,降低了复杂性。
6.练习(工厂方法模式)
#include <iostream>
using namespace std;
//抽象鼠标类
class Mouse
{
public:
virtual void showName() = 0;
};
//dellmouse
class DellMouse :public Mouse
{
public:
virtual void showName() { cout << "dellMouse" << endl; }
};
//hpMouse
class HpMouse :public Mouse
{
virtual void showName() { cout << "hpMouse" << endl; }
};
//抽象工厂
class MouseFactory
{
public:
virtual Mouse* createdellmouse() = 0;
};
//dellfactory
class DellFactory :public MouseFactory
{
public:
virtual DellMouse* createdellmouse() { return new DellMouse; }
};
//hpfactory
class HpFactory :public MouseFactory
{
public:
virtual HpMouse* createhpmouse() { return new HpMouse; }
};
void test1()
{
MouseFactory* dellmouse = new DellFactory();
Mouse* dell = dellmouse->createdellmouse();
dell->showName();
delete dellmouse;
delete dell;
}