Количество 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.
Дополним код небольшим тестом
результатом которого будет, как вы уже догадались, «Same».
Стоит заметить, что существуют механизмы получения имени вызываемого класса в контексте статической функции в PHP версии до 5.3.0 с помощью анализа стека вызова методом debug_backtrace. Однако, на мой взгляд, данный подход ректален чуть более, чем полностью.
P.S.
Согласен, что наследование — не лучший механизм в данном случае. На мой взгляд, если бы PHP поддерживал аннотации и АОП, было бы намного удобней помечать классы аннотацией Singleton и реализовать аспект, осуществляющий инъекцию необходимого для реализации Singleton'a кода в помеченные классы. Однако, на сколько мне известно, ни аннотаций (как конструкции языка), ни АОП в PHP в ближайшее будущее не планируется.
Вариантов тут на мой взгляд может быть несколько. Первый — пересмотреть архитектуру приложения. Возможно проще зарегистрировать все объекты, претендующие на Singleton, в некоторой коллекции, и обращаться к ним исключительно через неё. Это позволит избежать случайного создания двух копий объекта. Однако, с уверенностью утверждать, что в приложении в определённый момент выполнения существует только одна копия объекта, при таком раскладе нельзя, так как никто не может помешать создать объект напрямую, в обход коллекции.
Второй способ заключается в реализации суперкласса Singleton'а, от которого будут наследоваться все классы-Singleton'ы. Однако в его реализации на PHP есть один подводный камень — позднее связывание (late/dynamic binding) в статических методах. Точнее — определение имени текущего класса в статическом методе getInstance.
Стандартные средства вроде метода get_class тут применить нельзя, так как никакого объекта у нас ещё собственно нет. В голову приходит отражение, но быстро понимаешь, что тут оно ни причём, так как исходной точкой для него является имя объекта, которое мы собственно и пытаемся узнать.
И тут нам на помощь приходит механизм по названием «позднее статическое связывание» (late static binding), доступный начиная с версии 5.3.0. Суть его заключается в возможности сослаться на вызываемый класс в контексте статического наследования. Другими словами, начиная с версии 5.3.0 мы можем использовать функцию get_called_class для получения имени вызываемого класса в рамках статического метода.
Зная вышеописанное, нетрудно создать суперкласс для реализации шаблона Singleton.
- /**
- * Singleton pattern implementation
- */
- abstract class Singleton {
-
- /**
- * Collection of instances
- * @var array
- */
- private static $_aInstance = array();
-
- /**
- * Private constructor
- */
- private function __construct(){}
-
- /**
- * Get instance of class
- */
- public static function getInstance() {
-
- // Get name of current class
- $sClassName = get_called_class();
-
- // Create new instance if necessary
- if( !isset( self::$_aInstance[ $sClassName ] ) )
- self::$_aInstance[ $sClassName ] = new $sClassName();
- $oInstance = self::$_aInstance[ $sClassName ];
-
- return $oInstance;
- }
-
- /**
- * Private final clone method
- */
- final private function __clone(){}
- }
* This source code was highlighted with Source Code Highlighter.
Дополним код небольшим тестом
- class Example extends Singleton {}
- $oExample1 = Example::getInstance();
- $oExample2 = Example::getInstance();
- echo ( is_a( $oExample1, 'Example' ) && $oExample1 === $oExample2)
- ? '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 в ближайшее будущее не планируется.