设计模式-单例模式&工厂模式

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值