Реализуйте одиночный элемент PHP: статические свойства класса или переменные статического метода?

Так, я всегда реализовывал одиночный элемент как так:

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

Мне это более чисто, потому что это не создает помехи классу, и я не должен делать никакой явной проверки существования, но потому что я никогда не видел эту реализацию больше нигде, я задаюсь вопросом:

Есть ли что-то не так с использованием второй реализации по первому?

13
задан Austin Hyde 13 July 2010 в 13:37
поделиться

4 ответа

Вероятно, вы имеете в виду это с небольшим изменением (иначе я получил синтаксическую ошибку):

<?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), 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 в том, что внутри getInstance, $instance является ссылкой (в смысле =&, и имеет счетчик ссылок 2 (одна для символа внутри метода, другая для статического хранилища). Поскольку getInstance не возвращается по ссылке, zval должен быть разделен -- для возврата создается новый с количеством ссылок 1 и очищенным флагом ссылки.

6
ответ дан 2 December 2019 в 00:02
поделиться

После некоторой игры лучший метод, который я могу придумать, выглядит следующим образом:

Создайте файл с именем 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();

И вы можете просто добавить эту одноэлементную функцию в любой имеющийся у вас класс, и она создаст только 1 экземпляр для каждого класса.

Еще одна идея, которую вы также можете сделать

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

, чтобы этот метод отлично подходит для отладчика, чтобы получить доступ ко всем классам и состояниям объектов, которые были инициализированы

0
ответ дан 2 December 2019 в 00:02
поделиться

Выбирайте классную собственность. Есть несколько преимуществ ...

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

Еще одно замечание: я бы не стал делать конструктор закрытым. Это сделает невозможным правильное расширение или тестирование. Вместо этого сделайте его защищенным, чтобы при необходимости можно было подклассифицировать и по-прежнему работать с родительским элементом ...

10
ответ дан 2 December 2019 в 00:02
поделиться

Самым чистым решением является удаление логики синглтона из самого класса (потому что это что-то не связано с работой самого класса).

Интересную реализацию см. Здесь: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/

0
ответ дан 2 December 2019 в 00:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: