实现PHP单例:静态类属性还是静态方法变量?

时间:2010-07-13 13:24:01

标签: php design-patterns singleton

所以,我总是像这样实现一个单身:

class Singleton {
    private static $_instance = null;
    public static function getInstance() {
        if (self::$_instance === null) self::$_instance = new Singleton();
        return self::$_instance;
    }
    private function __construct() { }
}

然而,最近让我感到震惊的是我还可以使用成员方式的静态变量来实现它:

class Singleton {
    public static function getInstance() {
        //oops - can't assign expression here!
        static $instance = null; // = new Singleton();
        if ($instance === null) $instance = new Singleton();
        return $instance;
    }
    private function __construct() { }
}

对我来说,这更干净,因为它不会使类混乱,我不必进行任何明确的存在检查,但因为我从未在其他地方见过这种实现,我想知道:

使用第二个实现比第一个实现有什么问题吗?

4 个答案:

答案 0 :(得分:10)

使用类属性。有一些优点......

class Foo {
    protected static $instance = null;

    public static function instance() {
        if (is_null(self::$instance)) {
            self::$instance = new Foo();
        }
        return self::$instance;
    }
}

首先,执行自动化测试更容易。你可以创建一个模拟foo类来“替换”实例,这样依赖于foo的其他类将获得mock而不是原始的副本:

class MockFoo extends Foo {
    public static function initialize() {
        self::$instance = new MockFoo();
    }
    public static function deinitialize() {
        self::$instance = null;
    }
}

然后,在您的测试用例中(假设phpunit):

protected function setUp() {
    MockFoo::initialize();
}

protected function tearDown() {
    MockFoo::deinitialize();
}

这解决了对单身人士的共同抱怨,他们很难测试。

其次,它使您的代码更加灵活。如果您想在该类中运行时“替换”该功能,您需要做的就是将其子类化并替换self::$instance

第三,它允许您在其他静态函数中操作实例。对于单实例类(真正的单例)来说这不是一个大问题,因为你可以调用self::instance()。但是,如果您有多个“命名”副本(比如数据库连接或其他需要多个资源的资源,但如果它们已经存在则不想创建新副本),它会变脏,因为您需要跟踪的名字:

protected static $instances = array();

public static function instance($name) {
    if (!isset(self::$instances[$name])) {
        self::$instances[$name] = new Foo($name);
    }
    return self::$instances[$name];
}

public static function operateOnInstances() {
    foreach (self::$instances as $name => $instance) {
        //Do Something Here
    }
}

另外一点,我不会将构造函数设为私有。这将使得无法正确扩展或测试。相反,让它受到保护,以便您可以根据需要进行子类化,并仍然可以对父级进行操作...

答案 1 :(得分:6)

你可能意味着稍作修改(否则会出现语法错误):

<?php
class Singleton {
    public static function getInstance() {
        static $instance;
        if ($instance === null)
            $instance = new Singleton();
        xdebug_debug_zval('instance');
        return $instance;
    }
    private function __construct() { }
}
$a = Singleton::getInstance();
xdebug_debug_zval('a');
$b = Singleton::getInstance();
xdebug_debug_zval('b');

这给出了:

实例:(refcount = 2,is_ref = 1)对象的Singleton 的)的 1 的]

a:(refcount = 1,is_ref = 0)对象的Singleton 的)的 1 的]

实例:(refcount = 2,is_ref = 1)对象的Singleton 的)的 1 的]

b:(refcount = 1,is_ref = 0)对象的Singleton 的)的 1 的]

因此它的缺点是每次调用都会创建一个新的zval。这不是特别严重,所以如果您愿意,请继续。

强制zval分离的原因是getInstance内,$instance是一个引用(在=&意义上,它有引用计数2(一个用于内部符号)由于getInstance不是通过引用返回的,因此必须将zval分开 - 对于返回,将创建一个新的引用计数为1且引用标志为clear。 / p>

答案 2 :(得分:0)

在玩了一些我认为最好的方法之后就是这样:

创建一个名为SingletonBase.php的文件,并将其包含在脚本的根目录中!

代码是

abstract class SingletonBase
{
    private static $storage = array();

    public static function Singleton($class)
    {
        if(in_array($class,self::$storage))
        {
            return self::$storage[$class];
        }
        return self::$storage[$class] = new $class();
    }
    public static function storage()
    {
       return self::$storage;
    }
}

然后,对于任何想要制作单身的班级,只需添加这个小单一方法。

public static function Singleton()
{
    return SingletonBase::Singleton(get_class());
}

这是一个小例子:

include 'libraries/SingletonBase.resource.php';

class Database
{
    //Add that singleton function.
    public static function Singleton()
    {
        return SingletonBase::Singleton(get_class());
    }

    public function run()
    {
        echo 'running...';
    }
}

$Database = Database::Singleton();

$Database->run();

你可以在任何一个类中添加这个单例函数,每个类只创建一个实例。

你也可以做另一个想法

if(class_exists('Database'))
{
   $Database = SingletonBase::Singlton('Database');
}

如果你需要,你可以在你的脚本结束时做一些dfebugging,

在脚本的末尾,你可以做到

foreach(SingletonBase::storage () as $name => $object)
{
     if(method_exists("debugInfo",$object))
     {
         debug_object($name,$object,$object->debugInfo());
     }
}

所以这个方法非常适合调试器访问已经初始化的所有类和对象状态

答案 3 :(得分:0)

最干净的解决方案是从类本身中删除单例逻辑(因为它与类本身的作业无关)。

有趣的实现请看: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/