Singleton и Late static binding

    Количество Singleton'ов в проекте зачастую прямо пропорционально его сложности и размеру. Естественно, что описывать закрытый конструктор, статическое свойство-объект и метод его получения для сколь-либо ощутимого количества классов немного утомительно, да и пожалуй неверно. Отсюда встаёт вопрос: как «вынести за скобки» реализацию Singleton'а?

    Вариантов тут на мой взгляд может быть несколько. Первый — пересмотреть архитектуру приложения. Возможно проще зарегистрировать все объекты, претендующие на Singleton, в некоторой коллекции, и обращаться к ним исключительно через неё. Это позволит избежать случайного создания двух копий объекта. Однако, с уверенностью утверждать, что в приложении в определённый момент выполнения существует только одна копия объекта, при таком раскладе нельзя, так как никто не может помешать создать объект напрямую, в обход коллекции.

    Второй способ заключается в реализации суперкласса Singleton'а, от которого будут наследоваться все классы-Singleton'ы. Однако в его реализации на PHP есть один подводный камень — позднее связывание (late/dynamic binding) в статических методах. Точнее — определение имени текущего класса в статическом методе getInstance.
    Стандартные средства вроде метода get_class тут применить нельзя, так как никакого объекта у нас ещё собственно нет. В голову приходит отражение, но быстро понимаешь, что тут оно ни причём, так как исходной точкой для него является имя объекта, которое мы собственно и пытаемся узнать.
    И тут нам на помощь приходит механизм по названием «позднее статическое связывание» (late static binding), доступный начиная с версии 5.3.0. Суть его заключается в возможности сослаться на вызываемый класс в контексте статического наследования. Другими словами, начиная с версии 5.3.0 мы можем использовать функцию get_called_class для получения имени вызываемого класса в рамках статического метода.

    Зная вышеописанное, нетрудно создать суперкласс для реализации шаблона Singleton.

    1. /**
    2. * Singleton pattern implementation
    3. */
    4. abstract class Singleton {
    5.  
    6.   /**
    7.    * Collection of instances
    8.    * @var array
    9.    */
    10.   private static $_aInstance = array();
    11.  
    12.   /**
    13.    * Private constructor
    14.    */
    15.   private function __construct(){}
    16.  
    17.   /**
    18.    * Get instance of class
    19.    */
    20.   public static function getInstance() {
    21.  
    22.     // Get name of current class
    23.     $sClassName = get_called_class();
    24.  
    25.     // Create new instance if necessary
    26.     if( !isset( self::$_aInstance[ $sClassName ] ) )
    27.       self::$_aInstance[ $sClassName ] = new $sClassName();
    28.     $oInstance = self::$_aInstance[ $sClassName ];
    29.     
    30.     return $oInstance;
    31.   }
    32.  
    33.   /**
    34.    * Private final clone method
    35.    */
    36.   final private function __clone(){}
    37. }
    * This source code was highlighted with Source Code Highlighter.


    Дополним код небольшим тестом

    1. class Example extends Singleton {}
    2. $oExample1 = Example::getInstance();
    3. $oExample2 = Example::getInstance();
    4. echo ( is_a( $oExample1, 'Example' ) && $oExample1 === $oExample2)
    5.   ? 'Same' : 'Different', "\n"
    * This source code was highlighted with Source Code Highlighter.


    результатом которого будет, как вы уже догадались, «Same».

    Стоит заметить, что существуют механизмы получения имени вызываемого класса в контексте статической функции в PHP версии до 5.3.0 с помощью анализа стека вызова методом debug_backtrace. Однако, на мой взгляд, данный подход ректален чуть более, чем полностью.

    P.S.
    Согласен, что наследование — не лучший механизм в данном случае. На мой взгляд, если бы PHP поддерживал аннотации и АОП, было бы намного удобней помечать классы аннотацией Singleton и реализовать аспект, осуществляющий инъекцию необходимого для реализации Singleton'a кода в помеченные классы. Однако, на сколько мне известно, ни аннотаций (как конструкции языка), ни АОП в PHP в ближайшее будущее не планируется.
    Поделиться публикацией

    Похожие публикации

    Комментарии 61
      –4
      Очень хорошая реализация, т.к. постоянное дублирование кода при наследовании, создании новых singleton'ов уже порядком надоело. Вместе с заплаткой для версий ниже 5.3.0 механизм получается универсальным.
        0
        наследование создано не для того, что бы избавляться от дублирования кода в одиночках.
        +1
        мне кажется вы спутали принципы наследования. Наследовать нужно только тогда когда ребенок является более специфической реализацией родителя. Например дверь — железная дверь. А у вас получается наследуем потому что все синглтоны, а что они реализуют вообще не важно.
          0
          Согласен. Я сказал в постскриптуне, что наследование тут не самый удачный инструмент, но другого пути реализации в PHP я не вижу. У вас есть идеи?
            –2
            я бы все таки использовал доступ к классам которые должны быть «синглтонами» через реестр. Zend_Registry как раз, например, для этого. Ибо в вашем случаем мне ничего не мешает сделать class MyClass extends Example и переопределить конструктор чтобы он мог создавать обьекты и тд и тп. Нет предела человеческой глупости :)
              +3
              Singleton + Registry = Factory
              Но вот по поводу глупости — я с Вами согласен.
            –1
            Отвечу за автора =). Тут всё нормально: singleton — singleton для конфига.
            +2
            Лучше фабрику (абстрактную, если нужно).
              0
              В шестой версии будет static и позднее связывание для статических методов. Вообще, думал, что это появилось уже в 5.3.
                0
                Ну как бы и написано, что появилось. И появилось собственно: )
                  0
                  так нельзя будет делать? static::$instance = new static();
                    0
                    Когда нельзя будет?
                      0
                      Когда доступно static::
                      В php 5.3+
                      Может быть там только методы так вызывать можно, но не конструктор?
                        0
                        Конструктор не проверял, но т.к. все делают через get_called_class видимо нельзя. Сам не проверял
              +6
              Если так надо, используйте IoC контейнеры, Registry и т.п.

              Кроме того, если честно, не очень понятно в какой ситуации данное решение может пригодится. Для написания самих синглтонов делается один макрос в IDE…

              Про использование OOP уже написали выше. Добавлю что private конструктор, который потом легко так переопределяется в классах-потомках, сильно удивляет. Точнее, путает.

              Да, и автоподстановка в IDE работать не будет в вашем случае. Ибо у метода getInstance() в документации не указать возвращаемый тип.

              ЗЫ: Ваши строчки про аннотации из какого языка взяты? Java? В этом случае эти строчки вызывают крайне много вопросов.
                0
                сейчас на меня набросятся люди с топорами, НО:

                class A
                {
                static $data;
                static Bla()
                {
                echo self::$data;
                }

                static Init($str)
                {
                static $inited = false;
                if(!$inited)
                self::$data = $str;
                }
                }

                A::Init('test');

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

                $a = new A;
                $a->method();

                или

                A::method();

                а собсна что вам нравится решайте сами
                  +3
                  Сдается мне Вы не понимаете паттерна синглтон — вся суть которого в том, что объект контролирует число своих инстансов самостоятельно.
                    +1
                    боюсь что «сути» этого чудо действия я не понимаю
                    особенно в свете приведённого в статье кода, который работает точно так же…
                      +4
                      это потому что код статьи кривой
                        0
                        ну, возможно и кривой и реализует частный случай
                        однако замечу что паттерн это возможность а не руководство к действию, и в многих применениях такой код бывает полезен
                  0
                  ИМХО, синглтоны не стоят того, чтобы из-за них так мучиться.
                  Ибо, как всем известно, в php5 нет пронстранств имен и синглтоны ни чем не лучше использования глобальных переменных. Введете 2 синглтона с одинаковыми именами и придется делать рефакторинг всего проекта (((
                  Так что смысл синглтонами делать только классы шаблона Register, а в них уже хранить ссылки на остальные синглтоны.
                  Подробнее в книге www.books.ru/shop/books/693675
                    +1
                    Поддерживаю. Если развить идею Registry со ссылками на объекты прямиком приходишь к паттерну Dependency Injection. А все эти singleton-ы это только минимальное решение проблемы времени жизни объектов.
                        +1
                        опередили :)
                          0
                          Это PHP 5.3
                          У моего хостера, к примеру, установлен 5.2.11 :'-((
                          Что же мне делать?
                          +2
                          если мне не изменяет память, то в php 5.3.* пространства имет таки имеются.
                          0
                          чтобы Singelton ваш был единственным используйте IoC(DI)
                            0
                            Буду очень, очень бладгодарен за пример инверсии контроля приминительно к данному случаю на PHP.
                              +1
                              Очень хорошо всё описано в документации компонента Dependency Injection. Очень рекомендую.
                            0
                            Если бы речь шла о Java + Spring, то проблема, на сколько мне известно, действительно решалась бы объявлением следующего baen'а:

                            <bean id="example" class="package.ExampleImpl" singleton="true">

                            </bean>

                            Но в данном случае речь о не о Java.
                            0
                            Вместо is_a() лучше использовать instanceof, ЕМНИП :)
                              0
                              с версии 5.3 опять разрешили :)
                              0
                              Не стоит делать конструктор приватным, т.к. его нельзя будет переопределить в дочерних классах. Исправьте на протектед.
                                0
                                туплю
                                0
                                Когда количество синглтонов в проекте расте пропорционально сложности и размеру, стоит пересмотреть подход к архитектуре, т.к. подобный способ ломает модульность кода и сильно мешает юнит тестированию.

                                По-хорошему, класс должен сам знать все свои депенденси, так что особой нужды в жутких количествах синглтонов нет.
                                  0
                                  ну кстати ни разу не правы

                                  модульность кода может достигаться за счёт использования указателей на объект
                                  а вот класс не всегда знает свои зависимости, допустим если один и тот же класс работает с разными базами данных
                                  тогда только 2 решения — или прокси на нужную базу или указатель на синглет работы с БД
                                    0
                                    >> модульность кода может достигаться за счёт использования указателей на объект
                                    не понял мысль

                                    >> а вот класс не всегда знает свои зависимости, допустим если один и тот же класс работает с разными базами данных
                                    >> тогда только 2 решения — или прокси на нужную базу или указатель на синглет работы с БД
                                    решение в духе ООП для такой ситуации — это интерфейс
                                    синглтон для этого не нужен. Интерфейсов в пхп для БД много — PEAR::DB, adodb…
                                      0
                                      про казатель не очень точно выразился, идея следующая:

                                      class DatabaseRecord
                                      {
                                      private $db;
                                      function __construct($database)
                                      {
                                      self::$db = $database;
                                      }

                                      fucntion read()
                                      {
                                      $db = self::$db;
                                      $db::doSomething();
                                      }
                                      }
                                        +1
                                        абсолютно согласен — никакой нужды в синглтонах в данном примере нет =)
                                  +1
                                  В PHP 5.3.0 для позднего статического связывания появился референс static:: который резолвится именно так, как надо.

                                  php.net/manual/en/language.oop5.late-static-bindings.php
                                    –1
                                    Да, но для получения имени вызываемого класса использовать его не получится.
                                      +1
                                      Для создания синглтона это и не требуется
                                        –1
                                        Уверены? Можно пример?
                                          +2
                                          class Sin
                                          {
                                          public $a = 1;

                                          static private $_instance;

                                          static function getInstance()
                                          {
                                          if (! self::$_instance ) {
                                          self::$_instance = new static; // тут только начиная с php 5.3.*
                                          }

                                          return self::$_instance;
                                          }
                                          }

                                          class B extends Sin
                                          {
                                          public $a = 2;
                                          }

                                          $s = B::getInstance();

                                          var_dump($s);
                                            0
                                            Опередили :)
                                              0
                                              О! А этот код «компилируется»? :)
                                              А то мы тут выше теоретические рассуждения разводим, работает это или нет — проверить негде.
                                                0
                                                Проверил — компилируется. И даже правильно работает ) Я почему-то ошибочно полагал, что через static можно обращаться только к методам/свойствам.
                                                0
                                                А почему вы пишете:
                                                if (! self::$_instance ) {
                                                self::$_instance = new static; // тут только начиная с php 5.3.*

                                                а не
                                                if (! static::$_instance ) {
                                                static::$_instance = new static();
                                                ?
                                                Или static:: работает только для методов, но не для полей? А скобочки после new static не нужны?
                                                  0
                                                  Оба способа монописуальны, так как при создании класса с помощью конструкции new в случае отсутствия параметров скобки можно опускать:

                                                  ini_set('display_errors', 1);
                                                  error_reporting(E_ALL | E_STRICT);

                                                  class Test {
                                                      function __construct() {
                                                          echo 'Test Constructor', "\n";
                                                      }
                                                  }

                                                  $oTest1 = new Test(); // Печатает 'Test Constructor'
                                                  $oTest2 = new Test; // Печатает 'Test Constructor'
                                                    0
                                                    Потому что $_instance приватное свойство класса Sin, и не доступно для потомков. Вообще можно объявить $_instance как protected, тогда можно будет использовать static::$_instance. Я просто хотел чтобы было как у автора (private свойство), он вроде тоже об этом упоминает.

                                                    > А скобочки после new static не нужны?
                                                    Как вам больше нравиться =) Классы можно создавать и так и так. Т.е:

                                                    class B {}
                                                    $a = new B;

                                                    тоже вполне допустимо.

                                                    > Или static:: работает только для методов, но не для полей?
                                                    Нет, работает и так и так, как я уже писал здесь self только из-за того что поле private.
                                                      0
                                                      Да, не заметил приватности инстанции.
                                                      А в случае self::$_instance не перепишется приватное поле родительского класса?

                                                      Хотя все эти вопросы чисто теоретические — мне вряд ли светит переход на php6 :)
                                                        0
                                                        > Хотя все эти вопросы чисто теоретические — мне вряд ли светит переход на php6 :)
                                                        Ну в общем то php6 тут и не нужен, хватит php 5.3.

                                                        > А в случае self::$_instance не перепишется приватное поле родительского класса?
                                                        Гм, я не уверен, что понял, что именно вы спрашиваете. Если объявить в классе B static private $_instance, то нет, ничего не перепишется. Обращаясь к B self::$_instance и к Sin self::$_instance вы получите. Т.е когда модификатор свойства private потомок не может напрямую получить доступ к нему.

                                                        class Sin
                                                        {
                                                            static private $_instance = 'Sin $_instance';
                                                         
                                                            public function test()
                                                            {
                                                                return self::$_instance;
                                                            }
                                                        }
                                                         
                                                        class B extends Sin
                                                        {
                                                         
                                                            static protected $_instance = 'B $_instance';
                                                         
                                                            public function test2()
                                                            {
                                                                return self::$_instance;
                                                            }
                                                        }
                                                         
                                                        $s = new B;
                                                         
                                                        var_dump($s->test()); // Sin $_instance
                                                        echo '<Br>';
                                                        var_dump($s->test2()); // B $_instance


                                                          0
                                                          В промежуточном переходе на php5.3 смысла я не вижу, мне интересней нативная поддержка юникода в php6 — часто сталкиваюсь с проблемами с восточноевропейскими кодировками.

                                                          Вопрос был в другом:
                                                          class Sin
                                                          {
                                                          public $a = 1;

                                                          static private $_instance;

                                                          static function getInstance()
                                                          {
                                                          if (! self::$_instance ) {
                                                          self::$_instance = new static; // тут только начиная с php 5.3.*
                                                          }

                                                          return self::$_instance;
                                                          }
                                                          }

                                                          class B extends Sin
                                                          {
                                                          public $a = 2;
                                                          }

                                                          $b = B::getInstance();
                                                          $sin = Sin::getInstance();

                                                          Мне кажется, что переменные $b и $sin будут имет одно и то же значение — инстанцию класса B, так как он создастся раньше, но сохранится в общей для них поле self::$_instance в классе Sin.
                                                            0
                                                            Вот, теперь все ясно. Отвечаю (раз уж начал, то до конца) =) Да вы правы, и действительно свойство $_instance будет одно на всех. Для того чтобы это избежать, нужно делать что-то вроде, того что привел автор. Ну или объявлять static protected $_instance в потомках (и в родительском классе), и так же использовать везде static:: вместо self::. Я думаю можно еще найти какие-то способы, но все они мне почему-то не очень нравятся, ибо помахивают каким-то не здравым шаманством.
                                                              0
                                                              Еще один Singleton. Крутой пример, он несколько по другому подходит к проблеме. Используется static «внутри» функции.
                                                            0
                                                            На сколько мне известно ввиду того, что некоторые нововведения, которые планировались в 6-ой версии, были бэкпортированы в 5-ую, сколь-либо скорого появления 6-ой версии ихо ожидать не стоит.
                                              0
                                              а мне нравится такой вариант:
                                              $example_obj= $core->new_example;

                                              где ядро самостоятельно находит файл 'inc/example.php', исполняет его получая имя класса, инстанцирует и кэширует результат.
                                                0
                                                Ох, уж эти префиксы в названиях переменных.
                                                  –1
                                                  Чем дальше, тем все больше убеждаюсь, что singleton не для web. Он больше мешает, чем приносит пользы.

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое