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;}






0 0
原创粉丝点击