所以,我总是像这样实现单例:
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() { }
}
对我来说,这更干净,因为它不会让类变得混乱,而且我不必进行任何显式的存在检查,但因为我从未在其他地方见过这个实现,所以我想知道:
使用第二个实现比第一个实现有什么问题吗?
选择类属性。有几个优点...
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
}
}
另一点是,我不会将构造函数设为私有。这将导致无法正确扩展或测试。相反,将其设置为受保护,以便您可以在需要时进行子类化并仍然对父级进行操作...
您的意思可能是稍微修改一下(否则我会遇到语法错误):
<?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。
经过一番尝试后,我能想到的最好方法是这样的:
创建一个名为 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());
}
}
因此,此方法非常适合调试器访问已初始化的所有类和对象状态
最干净的解决方案是从类本身中删除单例逻辑(因为它与类本身的工作无关)。
有关有趣的实现,请参见: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a- Different-approach/