所以,我总是像这样实现一个单身:
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() { }
}
对我来说,这更干净,因为它不会使类混乱,我不必进行任何明确的存在检查,但因为我从未在其他地方见过这种实现,我想知道:
使用第二个实现比第一个实现有什么问题吗?
答案 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/