php如何实现单例模式

凡是讲到设计模式,无一例外的都会讲到单例模式,单例模式相对于其他设计模式来讲,要容易理解的多,但是要实现一个严格意义上的单例模式,很简单吗?

很多人可以轻松的写出如下php实现的单例模式:

<?php

class 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来阻止实例对象的方法失效了。下面来验证可行性。

为了方便验证,会在上例中加入一些属性及方法。


<?php

class 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() ;
    }
}


测试用例脚本:

<?php
require_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 140410609465280
Singleton 920373440721 I'm construct! process id is 30019 and thread id is 140410609465280
obj1 equal to obj3: 1
obj1 not equal obj2: 1
obj1: (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则是不同的对象。违反了单例模式。


考虑序列化

<?php
require_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 139844633716672
obj1 equal to obj3: 1
obj1 not equal obj2: 1
obj1: (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则是不同的对象。违反了单例模式。


考虑多线程

<?php
require_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 139824163313408
obj: (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 139824152233728
obj: (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 139824069605120
obj: (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 139824059115264
obj: (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 139824048625408
obj: (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 139824038135552
obj: (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 139824027645696
obj: (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 139824017155840
obj: (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 139823935387392
obj: (refcount=2, is_ref=0)=class Singleton { private $_serialize_id = (refcount=1, is_ref=0)=435530856252 }



目前可以想到以上三种可以破坏上述单例模式的情形,下面针对上述三个方面,试着探讨一些相应的解决方案。

针对反射

设置标志位,第一次调用构造函数时开启标志位,第二次调用构造函数时抛出异常。

<?php

class 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:44
Stack 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 44

Fatal error: Uncaught exception 'Exception' with message 'Not allowed serizlization' in /data1/study/php/singleton.php on line 44

Exception: Not allowed serizlization in /data1/study/php/singleton.php on line 44

Call 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

<?php

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






  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值