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

Aus*_*yde 13 php singleton design-patterns

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

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

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

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() { }
}
Run Code Online (Sandbox Code Playgroud)

对我来说,这是更清洁,因为它不会使类混乱,我不需要做任何明确的存在检查,但因为我从来没有在其他任何地方见过这个实现,我想知道:

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

irc*_*ell 10

去一个类属性.有一些优点......

class Foo {
    protected static $instance = null;

    public static function instance() {
        if (is_null(self::$instance)) {
            self::$instance = new Foo();
        }
        return self::$instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

首先,执行自动化测试更容易.你可以创建一个模拟Foo类为"替换"的情况下,使依赖于foo其他类将得到模拟,而不是原件及复印件:

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

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

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

protected function tearDown() {
    MockFoo::deinitialize();
}
Run Code Online (Sandbox Code Playgroud)

这解决了他们难以测试的单身人士的共同抱怨.

其次,它使您的代码更加灵活.如果您想在该类中运行时"替换"该功能,您需要做的就是将其子类化并替换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
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Art*_*cto 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');
Run Code Online (Sandbox Code Playgroud)

这给出了:

实例:(refcount = 2,is_ref = 1), object(Singleton)[ 1 ]

a :( refcount = 1,is_ref = 0), object(Singleton)[ 1 ]

实例:(refcount = 2,is_ref = 1), object(Singleton)[ 1 ]

b:(refcount = 1,is_ref = 0), object(Singleton)[ 1 ]

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

强制zval分离的原因是inside getInstance,$instance是一个引用(在某种意义上=&,它有引用计数2(一个用于方法内部的符号,另一个用于静态存储).由于getInstance不通过引用返回, zval必须分开 - 对于返回,将创建一个新的参考计数1并且参考标志清除.