Реализовать свойства 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() { }
}
Для меня это чище, потому что он не загромождает класс , и мне не нужно делать явные проверки существования, но поскольку я никогда не видел эту реализацию нигде, Мне интересно:
Есть ли что-то неправильное в использовании второй реализации над первым?
Ответы
Ответ 1
Вероятно, вы имеете в виду это с небольшой модификацией (в противном случае я получил синтаксическую ошибку):
<?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');
Это дает:
instance: (refcount = 2, is_ref = 1),
<Б > объект (<я > Синглтон) [<я > 1]
a: (refcount = 1, is_ref = 0),
<Б > объект (<я > Синглтон) [<я > 1]
instance: (refcount = 2, is_ref = 1),
<Б > объект (<я > Синглтон) [<я > 1]
b: (refcount = 1, is_ref = 0),
<Б > объект (<я > Синглтон) [<я > 1]
Таким образом, у него есть недостаток: при каждом вызове будет создан новый zval. Это не особенно серьезно, поэтому, если вы предпочитаете это, продолжайте.
Причина разделения zval заключается в том, что внутри getInstance
, $instance
является ссылкой (в смысле =&
и имеет счетчик ссылок 2 (один для символа внутри метода, другой для статическое хранилище). Поскольку getInstance
не возвращается по ссылке, zval должен быть отделен - для возврата создается новый счетчик ссылок с номером ссылки 1, а флаг ссылки очищается.
Ответ 2
Перейдите в свойство класса. Есть несколько преимуществ...
class Foo {
protected static $instance = null;
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new Foo();
}
return self::$instance;
}
}
Во-первых, проще выполнить автоматизированные тесты. Вы можете создать класс mock 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
}
}
Еще одно замечание: я бы не стал закрывать конструктор. Это сделает невозможным правильное распространение или тестирование. Вместо этого сделайте его защищенным, чтобы вы могли подклассом при необходимости и все еще работать с родительским...
Ответ 3
После некоторого воспроизведения лучшего метода, о котором я могу думать, это так:
Создайте файл с именем SingletonBase.php и включите его в корень из script!
Код
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;
}
}
Затем для любого класса, который вы хотите сделать singleton, просто добавьте этот небольшой единственный метод.
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');
}
и в конце вашего script вы можете выполнить некоторую dfebugging, если вам тоже нужно,
в конце вашего script вы можете просто сделать
foreach(SingletonBase::storage () as $name => $object)
{
if(method_exists("debugInfo",$object))
{
debug_object($name,$object,$object->debugInfo());
}
}
поэтому этот метод будет отличным для отладчика, чтобы получить доступ ко всем классам и состояниям объектов, которые были инициализированы
Ответ 4
Самое чистое решение - удалить синтаксическую логику из самого класса (потому что это не связано с работой самого класса).
Для интересной реализации см. это:
http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/