单例模式
意图
《设计模式》对单例模式意图的描述如下:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
从意图来看,单例模式的两个核心为「保证一个类仅有一个实例」以及 「提供一个访问它的全局访问点」。
为什么要保证一个类仅有一个实例?有时候是为了控制某些共享资源,例如一个系统仅有一个打印机、文件系统和窗口管理器。
如何保证一个类仅有一个实例?首先,通过构造函数来获取实例肯定是不行的,因为构造函数每次都会生成新的实例对象。其次,单单提供一个全局变量来访问实例也不够,因为不能保证获取的是唯一实例。
因此,更好的解决方式就是让类自身来负责保存它的唯一实例,并提供一个访问该实例的方法。这样的话,对于客户端而言,每次获取的都是同一个实例。不过,类既负责了实例的保存,也负责了实例的访问,违反了 单一职责 原则。
实现
单例模式的实现可以分为两种,在类中直接实现单例模式,或者通过继承的方式来实现单例模式。
在类中实现单例模式
第一种方法就是我们在类中直接实现单例模式,这也是最常用的方法
<?php
final class Singleton
{
// 保存实例
private static $instance;
// 第一次获取时创建
public static function getInstance(): Singleton
{
if (null === static::$instance) {
static::$instance = new static();
}
return static::$instance;
}
// 防止通过构造函数实例化
private function __construct(){}
// 防止被克隆
private function __clone(){}
// 防止被反序列化
private function __wakeup(){}
}
通过继承实现单例模式
另外一种方式就是定义一个全局的单例类,子类只需要继承该类就可以实现单例模式。
<?php
class Singleton
{
// 保存一个或多个实例
private static $instances = [];
protected function __construct(){}
protected function __clone(){}
public function __wakeup()
{
throw new \Exception("不能反系列化单例");
}
public static function getInstance(): Singleton
{
$subclass = static::class;
if (!isset(self::$instances[$subclass])) {
self::$instances[$subclass] = new static();
}
return self::$instances[$subclass];
}
}
// 测试
class Foo extends Singleton
{
}
Foo::getInstance() === Foo::getInstance(); // true
总结
当你要控制某些共享资源时,可使用单例模式。但是单例模式也存在几点不足
- 单例模式同时负责类的保存与访问,违反了「单一职责」原则,因此,单例模式被认为是反模式。
- 在多线程环境中,需要对单例模式进行特殊处理,防止多个线程同时创建单例对象。
- 由于单例模式不能被实例化,将导致单元测试难以进行。