php如何实现单例模式
来源:互联网 发布:淘宝nala彩妆是正品吗 编辑:程序博客网 时间:2024/06/05 02:25
凡是讲到设计模式,无一例外的都会讲到单例模式,单例模式相对于其他设计模式来讲,要容易理解的多,但是要实现一个严格意义上的单例模式,很简单吗?
很多人可以轻松的写出如下php实现的单例模式:
<?phpclass Singleton { //保存类实例的静态成员变量 private static $_instance; //private 构造函数 private function __construct() { echo " I'm construct! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } private function __clone() { echo " I'm clone! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } //单例方法访问类实例 public static function getInstance() { if (!(self::$_instance instanceof self)) { self::$_instance = new self(); } return self::$_instance; }}
在该示例中,将构造方法设为private从而防止直接new一个对象;将__clone方法设为private,防止通过clone复制一个对象;需要该类对象"只能"通过调用Singleton::getInstance()方法的方式,而getInstance方法通过"饿汉模式"保证类变量$_instance只会被初始化一次,即Singleton类只能有一个对象。
这种实现方式看似没有问题,当我们试图 new Singleton()或者clone 一个对象时都会发生fatal error。那么,这种方式是否就能保证单例了?并不是。
考虑反射
构造方法被private了,是不是就无法实例化一个类了?来看ReflectionClass的一个方法
ReflectionClass::newInstanceWithoutConstructor — 创建一个新的类实例而不调用它的构造函数
也就是通过这个方法可以不经过构造方法就创建一个对象,上例中试图将构造方法private来阻止实例对象的方法失效了。下面来验证可行性。
为了方便验证,会在上例中加入一些属性及方法。
<?phpclass Singleton { //保存类实例的静态成员变量 private static $_instance; private $_serialize_id = 1234567890; //private 构造函数 private function __construct() { $this->setSerializeId(rand(1,1000000000000)); echo $this . " I'm construct! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } private function __clone() { echo $this . " I'm clone! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } /** * @return mixed */ public function getSerializeId() { return $this->_serialize_id; } /** * @param mixed $serialize_id */ public function setSerializeId($serialize_id) { $this->_serialize_id = $serialize_id; } //单例方法访问类实例 public static function getInstance() { if (!(self::$_instance instanceof self)) { self::$_instance = new self(); } return self::$_instance; } public function __toString() { return __CLASS__ . " " . $this->getSerializeId() ; }}
测试用例脚本:
<?phprequire_once 'singleton.php';//$obj1 and $obj3 is the same object$obj1 = Singleton::getInstance();$obj3 = Singleton::getInstance();//$obj2 is a new object$class = new ReflectionClass('Singleton');$obj2 = $class->newInstanceWithoutConstructor();$ctor = $class->getConstructor();$ctor->setAccessible(true);$ctor->invoke($obj2);echo "obj1 equal to obj3: " . ($obj1 === $obj3) . "\n";echo "obj1 not equal obj2: " . ($obj1 !== $obj2) . "\n";xdebug_debug_zval('obj1');xdebug_debug_zval('obj2');xdebug_debug_zval('obj3');
输出case:
Singleton 840562594589 I'm construct! process id is 30019 and thread id is 140410609465280Singleton 920373440721 I'm construct! process id is 30019 and thread id is 140410609465280obj1 equal to obj3: 1obj1 not equal obj2: 1obj1: (refcount=3, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=840562594589 }obj2: (refcount=1, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=920373440721 }obj3: (refcount=3, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=840562594589 }
可以看出$obj1和$obj3是同一个对象,而与$obj2则是不同的对象。违反了单例模式。
考虑序列化
<?phprequire_once 'singleton.php';//$obj1 and $obj3 is the same object$obj1 = Singleton::getInstance();$obj3 = Singleton::getInstance();//$obj2 is a new object$objSer = serialize($obj1);$obj2 = unserialize($objSer);echo "obj1 equal to obj3: " . ($obj1 === $obj3) . "\n";echo "obj1 not equal obj2: " . ($obj1 !== $obj2) . "\n";xdebug_debug_zval('obj1');xdebug_debug_zval('obj2');xdebug_debug_zval('obj3');
输出case:
Singleton 165926147718 I'm construct! process id is 6849 and thread id is 139844633716672obj1 equal to obj3: 1obj1 not equal obj2: 1obj1: (refcount=3, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=165926147718 }obj2: (refcount=1, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=165926147718 }obj3: (refcount=3, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=165926147718 }
可以看出$obj1和$obj3是同一个对象,而与$obj2则是不同的对象。违反了单例模式。
考虑多线程
<?phprequire_once 'singleton.php';class Mythread extends Thread { public function __construct($i) { $this->i = $i; } public function run() { $obj = Singleton::getInstance(); xdebug_debug_zval('obj'); } }for ( $i=1; $i<10; $i++) { $threads[$i]=new MyThread($i); $threads[$i]->start();}
输出case
Singleton 685692620930 I'm construct! process id is 27349 and thread id is 139824163313408obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=685692620930 }Singleton 578721798491 I'm construct! process id is 27349 and thread id is 139824152233728obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=578721798491 }Singleton 334907566198 I'm construct! process id is 27349 and thread id is 139824069605120obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=334907566198 }Singleton 940285742749 I'm construct! process id is 27349 and thread id is 139824059115264obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=940285742749 }Singleton 41907731444 I'm construct! process id is 27349 and thread id is 139824048625408obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=41907731444 }Singleton 492959984113 I'm construct! process id is 27349 and thread id is 139824038135552obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=492959984113 }Singleton 561926315539 I'm construct! process id is 27349 and thread id is 139824027645696obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=561926315539 }Singleton 829729639926 I'm construct! process id is 27349 and thread id is 139824017155840obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=829729639926 }Singleton 435530856252 I'm construct! process id is 27349 and thread id is 139823935387392obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=435530856252 }
目前可以想到以上三种可以破坏上述单例模式的情形,下面针对上述三个方面,试着探讨一些相应的解决方案。
针对反射
设置标志位,第一次调用构造函数时开启标志位,第二次调用构造函数时抛出异常。
<?phpclass Singleton { //保存类实例的静态成员变量 private static $_instance; private $_serialize_id = 1234567890; private static $_flag = false; //private 构造函数 private function __construct() { if ( self::$_flag ) { throw new Exception("I'm Singleton"); } else { self::$_flag = true; } $this->setSerializeId(rand(1,1000000000000)); echo $this . " I'm construct! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } private function __clone() { echo $this . " I'm clone! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } /** * @return mixed */ public function getSerializeId() { return $this->_serialize_id; } /** * @param mixed $serialize_id */ public function setSerializeId($serialize_id) { $this->_serialize_id = $serialize_id; } //单例方法访问类实例 public static function getInstance() { if (!(self::$_instance instanceof self)) { self::$_instance = new self(); } return self::$_instance; } public function __toString() { return __CLASS__ . " " . $this->getSerializeId() ; }}
针对序列化
由于在序列化之前会试图调用__sleep()方法,相应的,在重新构造对象之后,会调用__wakeup()方法。与__clone()方法不同,序列化的时候__sleep()方法只是序列化动作之前调用,将其设置为private并不会起作用,只是运行的时候会收到一个notice。可以试着在__sleep()方法抛出异常的方式来阻止序列化的达成。不过使用这种方式,如果没有捕获异常,或者没有异常处理函数,将导致程序异常退出,并不是很完美。
在Singleton类中增加__sleep()及__wakeup()方法,并执行测试case
Singleton 594976518769 I'm construct! process id is 27612 and thread id is 139941710354368
PHP Fatal error: Uncaught exception 'Exception' with message 'Not allowed serizlization' in /data1/study/php/singleton.php:44Stack trace:#0 [internal function]: Singleton->__sleep()#1 /data1/study/php/test2.php(11): serialize(Object(Singleton))#2 {main} thrown in /data1/study/php/singleton.php on line 44Fatal error: Uncaught exception 'Exception' with message 'Not allowed serizlization' in /data1/study/php/singleton.php on line 44Exception: Not allowed serizlization in /data1/study/php/singleton.php on line 44Call Stack: 0.0007 227224 1. {main}() /data1/study/php/test2.php:0 0.0010 244080 2. serialize(???) /data1/study/php/test2.php:11 0.0010 244448 3. Singleton->__sleep() /data1/study/php/test2.php:11
在这个测试case中,发现了另外一个问题,《php中$this的引用计数》
针对多线程
目前还没有想到针对多线程的解决方案。
单例模式与trait结合,可以实现一个单例模式的模板,关于php中trait的使用参见《php中的trait》
<?phptrait TSingleton { private $_serialize_id = 1234567890; private static $_flag = false ; //private 构造函数 private function __construct() { if ( self::$_flag ) { throw new Exception("I'm a Singleton"); } else { self::$_flag = true; } $this->setSerializeId(rand(1,1000000000000)); echo $this . " I'm construct! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } private function __clone() { echo $this . " I'm clone! process id is " . getmypid() . " and thread id is " . Thread::getCurrentThreadId() . "\n"; } /** * @return mixed */ public function getSerializeId() { return $this->_serialize_id; } /** * @param mixed $serialize_id */ public function setSerializeId($serialize_id) { $this->_serialize_id = $serialize_id; } //单例方法访问类实例 public static function getInstance() { static $instance ; if (!($instance instanceof self )) { $ref = new ReflectionClass( get_called_class() ); $ctor = $ref->getConstructor(); $ctor->setAccessible(true); $instance = $ref->newInstanceWithoutConstructor(); $ctor->invokeArgs($instance, func_get_args()); } return $instance; } public function __toString() { return __CLASS__ . " " . $this->getSerializeId() ; } public function __sleep() { // TODO: Implement __sleep() method. throw new Exception("I'm Singleton! Can't serialize"); } public function __wakeup() { // TODO: Implement __wakeup() method. throw new Exception("I'm Singleton! Can't unserialize"); }}class Singleton { use TSingleton;}
- php如何实现单例模式
- php实现单例模式
- PHP实现单例模式
- PHP实现单例模式
- PHP 单例模式实现
- 如何实现单例模式?
- 如何实现单例模式
- PHP实现单例模式(单态模式)
- php实现的单例模式
- 单例模式之php实现
- php中的单例模式实现
- php单例模式 数据类实现
- php、java实现单例模式
- PHP Trait实现Singleton单例模式
- php实现单例模式原理
- PHP设计模式笔记:使用PHP实现单例模式
- 【Python】Python如何实现单例模式?
- 如何实现C++单例模式?
- iptables Linux防火墙配置工具
- 大数据IMF传奇行动绝密课程第26课:Spark Runtime内幕揭秘
- 2016暑假练习——线段树
- C# 用数据库读取Excel出现“定义了过多字段”错误的解决方法
- 2016.8.11
- php如何实现单例模式
- cscope
- 逗号表达式
- [HDU5828] Rikka with Sequence [2016 Multi-University Training Contest 8(2016多校联合训练8) 1008]
- Thread 方法
- 总结——在CCS5.5.0中如何将代码烧写到DSP28335的Flash中
- Java 集合框架源码分析(四)——LinkedHashMap
- HTML引用资源相对路径
- poj 3185 高斯消元 枚举变元