单例模式

本文详细解析了面向对象设计的八大原则,包括依赖倒置、开放封闭、单一职责等,以及OOP设计模式的五项原则。深入探讨了Singleton单例模式的定义、动机、懒汉模式与饿汉模式的区别,以及在多线程环境下的线程安全实现。

一、面向对象设计原则(一共八种)

面向对象设计原则(1)

依赖倒置原则(DIP)

·高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。

·抽象(稳定)不应该依赖于变化),实现细节应该依赖于抽象(稳定)。 

面向对象设计原则(2)

开放封闭原则(OCP)

·对扩展开放,对更改封闭。

·类模块应该是可扩展的,但是不可修改。

面向对象设计原则(3)

单一职责原则(SRP)

·一个类应该仅有一个引起它变化的原因。

·变化的方向隐含着类的责任。

面向对象设计原则(4)

Liskov (里氏)替换原则(LSP)

·子类必须能够替换它们的基类(IS-A)。

·继承表达类型抽象。

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

面向对象设计原则(5)

接口隔离原则(ISP)

·不应该强迫客户程序依赖它们不用的方法。

·接口应该小而完备。

面向对象设计原则(6)

优先使用对象组合,而不是类继承

·类继承通常为“白箱复用”,对象组合通常为“黑箱复用”

·继承在某种程度上破坏了封装性,子类父类耦合度高。

·而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

面向对象设计原则(7)

封装变化点

·使用封装来创建对象之间的分界层,让设计者可以在分界的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。


面向对象设计原则(8)


针对接口编程,而不是针对实现编程

·不将变量类型声明为某个特定的具体类,而是声明为某个接口。

·客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。

·减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”

的类型设计方案。

二、OOP(面向对象编程)的设计模式的五项原则

1、单一职责原则

单一职责有2个含义,一个是避免相同的职责分散到不同的类中,另一个是避免一个类承担太多职责。减少类的耦合,提高类的复用性。

2、接口隔离原则

不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪。

该原则观点如下:
1)一个类对另外一个类的依赖性应当是建立在最小的接口上

2)客户端程序不应该依赖它不需要的接口方法。

3、开放-封闭原则

open模块的行为必须是开放的、支持扩展的,而不是僵化的。

closed在对模块的功能进行扩展时,不应该影响或大规模影响已有的程序模块。一句话概括:一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。

核心思想就是对抽象编程,而不对具体编程。

4、替换原则

子类型必须能够替换掉他们的父类型、并出现在父类能够出现的任何地方。

主要针对继承的设计原则

1)父类的方法都要在子类中实现或者重写,并且派生类只实现其抽象类中生命的方法,而不应当给出多余的,方法定义或实现。

2)在客户端程序中只应该使用父类对象而不应当直接使用子类对象,这样可以实现运行期间绑定。

5、依赖倒置原则

上层模块不应该依赖于下层模块,他们共同依赖于一个抽象,即:父类不能依赖子类,他们都要依赖抽象类。

抽象不能依赖于具体,具体应该要依赖于抽象。

三、Singleton单件模式

定义

保证一个类仅有一个实例,并提供一个该类的全局访问点。

动机

在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个一个实例,才能确保它们的逻辑正确性、以及良好的效率。

如何能够绕过常规的构造器,提供一种机制来保证一个类只有一个实例?

这应该是类设计者的责任,而不是使用者的责任。

懒汉模式和饿汉模式

1.两者建立单例对象的时间不同。“懒汉式”是在你真正用到的时候才去建这个单例对象,“饿汉式”是在不管用不用得上,一开始就建立这个单例对象。

2.饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的,因为有可能多个线程同时创建对象。

饿汉实现

饿汉模式在类定义时就创建了单例,seems hungry。使用时通过公共接口直接获取已创建好的这个单例对象,不存在并发创建问题,因此饿汉模式是线程安全的。

#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<algorithm>
#include<memory>
using namespace std;
template <class T>
class singleton
{
protected:    singleton() {};
private:
	singleton(const singleton&) {
		cout << "Create a singleton" << endl;
	};
	singleton& operator=(const singleton&) {};
	static T* m_instance;
public:
	static T* GetInstance();
};

template <class T>
T* singleton<T>::GetInstance()
{
	if (m_instance == nullptr)
	{
		m_instance = new T();
	}
	return m_instance;
}

template <class T>
T* singleton<T>::m_instance = GetInstance();//饿汉模式在类定义处构建单例
int main(void)
{
	cout << "Enter main" << endl;
	int *test1 = singleton<int>::GetInstance();	//通过公共接口获取单例对象
	int *test2 = singleton<int>::GetInstance();	//通过公共接口获取单例对象
	if (test1 == test2)	//判断获取到的单例是否是同一个,从而判断单例对象是否唯一
	{
		std::cout << "This is a real Singleton!" << std::endl;
	}
	else
	{
		std::cout << "Fake Singleton?" << std::endl;
	}
	system("pause");
	return 0;
}

懒汉实现

不加锁版本,多线程不安全。

template <class T>
class singleton
{
protected:    singleton() {};
private:
	singleton(const singleton&) {};
	singleton& operator=(const singleton&) {};
	static T* m_instance;
public:
	static T* GetInstance();
};


template <class T>
T* singleton<T>::GetInstance()
{
	if (m_instance == nullptr)
	{
		m_instance = new T();
	}
	return m_instance;
}

template <class T>
T* singleton<T>::m_instance = nullptr;

 懒汉模式下,在定义m_instance变量时先等于NULL,在调用GetInstance()方法时,在判断是否要赋值。这种模式,并非是线程安全的,因为多个线程同时调用GetInstance()方法,就可能导致有产生多个实例。要实现线程安全,就必须加锁。

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

template <class T>
class singleton
{
protected:    singleton() {};
private:
	singleton(const singleton&) {};
	singleton& operator=(const singleton&) {};
	static T* m_instance;
public:
	static T* GetInstance();
};

template <class T>
T* singleton<T>::GetInstance()
{
	pthread_mutex_lock(&mutex);
	if (m_instance == nullptr)
	{
		m_instance = new T();
	}
	pthread_mutex_unlock(&mutex);
	return m_instance;
}

template <class T>
T* singleton<T>::m_instance = nullptr;

 GetInstance()方法,每次进来都要加锁,会影响效率。然而这并不是必须的,于是又对GetInstance()方法进行改进,这也就是所谓的“双检锁”机制。

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

template <class T>
class singleton
{
protected:    singleton() {};
private:
	singleton(const singleton&) {};
	singleton& operator=(const singleton&) {};
	static T* m_instance;
public:
	static T* GetInstance();
};

template <class T>
T* singleton<T>::GetInstance()
{
	if (m_instance == nullptr)
	{
		pthread_mutex_lock(&mutex);
		if (m_instance == nullptr)
		{
			m_instance = new T();
		}
		pthread_mutex_unlock(&mutex);
	}
	return m_instance;
}

template <class T>
T* singleton<T>::m_instance = nullptr;

应用场景

 举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

总结:

1.需要生成唯一序列的环境

2.需要频繁实例化然后销毁的对象。

3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 

4.方便资源相互通信的环境


优点

1.实现了对唯一实例访问的可控

2.对于一些需要频繁创建和销毁的对象来说可以提高系统的性能。

缺点

1. 不适用于变化频繁的对象
2.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值