Реализовать синглтон PHP: статические свойства класса или переменные статического метода? - PullRequest
12 голосов
/ 13 июля 2010

Итак, я всегда реализовывал синглтон так:

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

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

Что-то не так с использованием второй реализации над первой?

Ответы [ 4 ]

10 голосов
/ 13 июля 2010

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

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

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

6 голосов
/ 13 июля 2010

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

<?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 ]

экземпляр: (refcount = 2, is_ref = 1), объект ( Singleton ) [ 1 ]

b: (refcount = 1, is_ref = 0) , object ( Singleton ) [ 1 ]

Таким образом, у него есть недостаток: новый zval будет создаваться при каждом вызове.Это не особенно серьезно, поэтому, если вы предпочитаете это, продолжайте.

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

0 голосов
/ 21 июля 2010

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

Для интересной реализации см. Это: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/

0 голосов
/ 13 июля 2010

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

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

и в конце вашего скрипта вы можете сделать некоторую отладку, если вам это тоже нужно,

в конце вашего скрипта вы можете просто сделать

foreach(SingletonBase::storage () as $name => $object)
{
     if(method_exists("debugInfo",$object))
     {
         debug_object($name,$object,$object->debugInfo());
     }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...