单例模式
1. 啥时候使用单例模式
保证系统中某一服务有一个统一的入口,如:一个系统中可以存在多个打印服务,但只能有一个正在工作的任务;一个系统中只能有一个计时工具或序号生成器。
如何保证一个类只有一个实例并且这个实例易于被访问?定义一个全局变量可以保证对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没与其它实例被创建,并且它可以提供一个访问该实例的方法。
2. 单例模式的定义
单例模式(Singleton Pattern):确保某一个类有且仅有一个实例,并且自行实例化向整个系统提供这个实例。
分析:某个类仅有一个实例,并且必须是自身创建这个实例,还必须自身向整个系统提供这个实例。
3. 该模式中包含的角色及其职责
1)、Singleton:单实例
对整个系统提供有且仅有一个实例,并且是自身创建这个实例。
不废话了,看下面代码!
4. 撸代码
PHP的单例模式相对于其他语言是比较简单的,因为它不需要考虑多线程问题,下面我们给出两种代码实例(PHP和C#)。
先看下在PHP下的单例模式:
/**
* 单实例角色:Singleton
* 防止类被继承
*/
final class Singleton
{
/**
* 定义一个私有的静态全局成员变量来保存该类的唯一实例
* @var Singleton
*/
private static $instance;
/**
* 通过静态方法来构造对象实例
*
*/
public static function getInstance()
{
//检查类是否已被实例化
if (null === static::$instance) {
static::$instance = new static();
}
return static::$instance;
}
/**
* 定义私有的构造函数
* 防止外部通过new关键字实例化对象
* 只允许自身实例化
*/
private function __construct()
{
}
/**
* 防止被复制或克隆
*/
private function __clone()
{
}
/**
* 防止反序列化创建该实例
*/
private function __wakeup()
{
}
}
/**
* 测试单例模式
*
*/
class TestSingleton
{
public function test(){
//获取并创建单例对象
$singleton = Singleton::getInstance();
}
}
通过上例代码看出,在PHP中创建单例模式,必须遵循一下几点:
1)、有一个私有的类对象成员变量
2)、构造函数必须是私有的,防止通过new关键字被实例化
3)、__clone()和__wakeup()必须为私有的,防止复制、克隆和反序列化
4)、必须提供一个可供外界访问的静态方法,并通过此静态方法实现自身实例化对象。
在PHP中实现单例模式相对简单,因为它不需要考虑多线程问题,但C#中就必须要靠线程安全,如果C#在多线程中访问单例模式就有可能被创建多个对象,所以不得不考虑线程安全问题,要确保在多线程下访问单例模式依然是被创建一个实例对象。
C#代码如下:
/// <summary>
/// 单实例角色:Singleton
/// </summary>
public sealed class Singleton
{
/// <summary>
/// 定义一个私有的静态全局成员变量来保存该类的唯一实例
/// </summary>
private static Singleton instance = null;
/// <summary>
/// 定义一个只读静态对象,且这个对象是在程序运行时创建
/// 必须为引用类型,确保访问同一地址
/// </summary>
private static readonly object syncObject = new object();
/// <summary>
/// 定义私有的构造函数
/// 防止外部通过new关键字实例化对象
/// 只允许自身实例化
/// </summary>
private Singleton() { }
/// <summary>
/// 通过静态方法来构造对象实例
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
//第一次判断:主要判断单例对象是否已被实例化
if (instance == null)
{
//锁住引用类型对象,进行同步操作
//必须为引用类型,因为引用类型的变量地址是同一内存地址
lock (syncObject)
{
//第二次判断:主要是防止可能延迟加载或缓存,造成创建多个实例
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
通过上例代码看出,在C#或含有线程操作的面向对象语言中,必须遵循一下几点:
1)、有一个私有的类对象成员变量
2)、有一个私有的静态只读引用类型的对象变量,并且该对象在程序运行时被创建。
3)、构造函数必须是私有的,防止通过new关键字被实例化
4)、必须提供一个可供外界访问的静态方法,并通过此静态方法实现自身实例化对象。
并且在此方法内必须进行两次检查对象实例是否被创建,防止可能延迟加载或缓存,造成创建多个实例。
5. 单例模式的优点
实例控制
单例模式会防止其他对象实例化其自身的单例对象的副本,从而确保所有对象都访问唯一实例。
灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
6. 单例模式的缺点
开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。
对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(c#),只有单例类能够导致实例被取消分配,因为它包含该实例的私有引用。
滥用单例带来的一些负面问题
如果为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
7. 使用场景
系统只需要一个实例对象,如系统要求提供一个唯一的入口,或者考虑资源消耗太大而只允许创建一个实例。