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

问题描述 投票:0回答:4

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

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() { }
}

对我来说,这更干净,因为它不会让类变得混乱,而且我不必进行任何显式的存在检查,但因为我从未在其他地方见过这个实现,所以我想知道:

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

php design-patterns singleton
4个回答
12
投票

选择类属性。有几个优点...

class Foo {
    protected static $instance = null;

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

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

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

另一点是,我不会将构造函数设为私有。这将导致无法正确扩展或测试。相反,将其设置为受保护,以便您可以在需要时进行子类化并仍然对父级进行操作...


7
投票

您的意思可能是稍微修改一下(否则我会遇到语法错误):

<?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), 对象单例)[1]

a: (refcount=1, is_ref=0), 对象单例)[1]

实例:(refcount=2, is_ref=1), 对象单例)[1]

b: (refcount=1, is_ref=0), 对象单例)[1]

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

强制 zval 分离的原因是,在

getInstance
内部,
$instance
是一个引用(在
=&
的意义上,它的引用计数为 2(一个用于方法内的符号,另一个用于静态存储) ). 由于
getInstance
不通过引用返回,因此必须将 zval 分开——为了返回,将创建一个新的引用计数为 1 且引用标志清除的 zval。


1
投票

经过一番尝试后,我能想到的最好方法是这样的:

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

代码是

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

    public static function Singleton($class)
    {
        if(isset(self::$storage[$class]))
        {
            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();

您可以在您拥有的任何类中添加此单例函数,它只会为每个类创建 1 个实例。

还有一个你也可以做的想法

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

在脚本的最后,如果您也需要的话,您可以进行一些调试,

在脚本的最后你可以这样做

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

因此,此方法非常适合调试器访问已初始化的所有类和对象状态


0
投票

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

有关有趣的实现,请参见: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a- Different-approach/

© www.soinside.com 2019 - 2024. All rights reserved.