设计模式之单例模式–php实现单例模式 单例模式
是最常被提起的一个设计模式,他的意思是
一个类只有一个实例,所有人共用这同一个实例。不让外部创建类的实例,并且提供一个外部的全局访问点。
为什么要使用单例模式 单例模式有什么好处呢?
单例模式是为了全局唯一,如果你需要一个全局唯一的类那么就需要单例模式了。而且单例模式还可以节省资源,因为这个类只有一个对象。
比如你需要一个全局唯一的id生成器,如果创建了多个实例,那么生成的id可能就不是全局唯一了。
比如你需要一个全局配置信息,需要全局缓存信息,全局日志文件写入等等。
普通类 下面是一个普通的类的实现和使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Common { public $name ; public $age ; function __construct ($name , $age ) { $this ->name = $name ; $this ->age = $age ; } public function print ( ) { dump ('姓名:' .$this ->name); dump ('年龄:' .$this ->age); } } $obj1 = new Common ('张三' , '18' );dump ($obj1 );$obj2 = new Common ('张三' , '20' );dump ($obj1 );
可以看到普通类可以new
多个对象,并且每个对象都不一样,拥有自己的属性。
单例类 首先,单例类是一种特殊的类,单例类只允许new
一次。怎么实现只能new
一次呢,从上面的代码中可以看到,new
这个操作是在我们的业务中进行的,这是个无法掌控的操作。
我们要让new
操作在类中实现,由这个单例类来决定,来操作对象的实例化。而不是在业务中进行。
只有自己才能决定自己new
不new
,自己决定自己的人生,而不是交给别人,使得无法掌控。
从这个分析中可以知道,单例类要完成下面的操作:
第一点简单,实现一个方法new
自己,然后存起来返回就可以了。
第二点在php中怎么实现呢,不允许外部实例化可以通过将__construct
方法设置为private
权限。因为这个方法外部访问不到,所以外部就无法实例化了。
简单实现一下单例类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Singleton { public $name ; public $age ; private static $instance ; private function __construct ($name , $age ) { $this ->name = $name ; $this ->age = $age ; } public static function getInstance ($name , $age ) { if (empty (self ::$instance )) { self ::$instance = new self ($name , $age ); } return self ::$instance ; } public function print ( ) { dump ('姓名:' .$this ->name); dump ('年龄:' .$this ->age); } } $obj1 = Singleton ::getInstance ('张三' , '18' );dump ($obj1 );$obj2 = Singleton ::getInstance ('张三' , '20' );dump ($obj2 );
这是把上面的普通类改成了单例类,现在这个单例类还有些问题。
单例类一般不会出现这种需要各种参数的,需要参数也建议通过配置文件来搞定参数,而不是传参。如果有传参需求,需要的是不同的参数是不同的对象,同一个参数是单例对象,那么需要维护一个单例对象的数组 。为什么不提供修改参数的方法呢,因为提供这个方法太危险,全局使用同一个对象,如果某个地方不慎更改了属性,那么会导致其他地方出现未知bug。
并没有完全禁止外部实例化,php还有魔术方法__clone。
把__clone方法设置成private
根据上面的我们在修改一下这个单例类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Singleton { protected $name ; protected $age ; private static $instance ; private function __construct ( ) { $this ->name = config ('design.singleton.name' ); $this ->age = config ('design.singleton.age' ); } private function __clone ( ) { } public static function getInstance ( ) { if (empty (self ::$instance )) { self ::$instance = new self (); } return self ::$instance ; } public function print ( ) { dump ('姓名:' .$this ->name); dump ('年龄:' .$this ->age); } } $obj1 = Singleton ::getInstance ();dump ($obj1 );$obj2 = Singleton ::getInstance ();dump ($obj2 );
到现在,我们就完成了一个基于php的单例类,这个单例类支持延迟加载。如果不需要延迟加载,那么实现更简单。看下面的饿汉式单例类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Singleton { protected $name ; protected $age ; private static $instance = new self (); private function __construct ( ) { $this ->name = config ('design.singleton.name' ); $this ->age = config ('design.singleton.age' ); } private function __clone ( ) { } public static function getInstance ( ) { return self ::$instance ; } public function print ( ) { dump ('姓名:' .$this ->name); dump ('年龄:' .$this ->age); } }
我们支持延迟加载的其实还有些问题,那就是缺少锁,如果很多请求同时进来,那么后面的会覆盖前面的单例对象。如果再加上锁的话实现是下面这样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Singleton { protected $name ; protected $age ; private static $instance ; private function __construct ( ) { $this ->name = config ('design.singleton.name' ); $this ->age = config ('design.singleton.age' ); } private function __clone ( ) { } public static function getInstance ( ) { $lock = new lock; $lock ->lock (); if (empty (self ::$instance )) { self ::$instance = new self (); } $lock ->unlock (); return self ::$instance ; } public function print ( ) { dump ('姓名:' .$this ->name); dump ('年龄:' .$this ->age); } }
这样的话因为锁的原因会造成性能比较低,所以又有了下面的方式,叫做双重检测
。也就是拿锁之前先判断一下有没有实例化,如果有了就不需要锁了。为什么不直接在第一个判断里面加呢,因为锁加晚了没有意义了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Singleton { protected $name ; protected $age ; private static $instance ; private function __construct ( ) { $this ->name = config ('design.singleton.name' ); $this ->age = config ('design.singleton.age' ); } private function __clone ( ) { } public static function getInstance ( ) { if (empty (self ::$instance )) { $lock = new lock; $lock ->lock (); if (empty (self ::$instance )) { self ::$instance = new self (); } $lock ->unlock (); } return self ::$instance ; } public function print ( ) { dump ('姓名:' .$this ->name); dump ('年龄:' .$this ->age); } }
这就是整个单例模式。四种实现:
饿汉式 在类加载的时候实例化
懒汉式 支持延迟加载
带锁懒汉式 延迟加载的时候加锁
双重检测 两次判断
代码放在了我的github上面。