Pull to refresh

Singleton и Late static binding

PHP
Количество 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. * Collection of instances
  7. * @var array
  8. */
  9. private static $_aInstance = array();
  10. /**
  11. * Private constructor
  12. */
  13. private function __construct(){}
  14. /**
  15. * Get instance of class
  16. */
  17. public static function getInstance() {
  18. // Get name of current class
  19. $sClassName = get_called_class();
  20. // Create new instance if necessary
  21. if( !isset( self::$_aInstance[ $sClassName ] ) )
  22. self::$_aInstance[ $sClassName ] = new $sClassName();
  23. $oInstance = self::$_aInstance[ $sClassName ];
  24. return $oInstance;
  25. }
  26. /**
  27. * Private final clone method
  28. */
  29. final private function __clone(){}
  30. }
* 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 в ближайшее будущее не планируется.
Tags:phpsingletonstaticlate static binding
Hubs: PHP
Total votes 58: ↑43 and ↓15+28
Views7.6K

Popular right now