Подтвердить что ты не робот

Реализовать свойства 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() { }
}

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

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

4b9b3361

Ответ 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());
     }
}

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