Singleton Pattern — один из наиболее известных шаблонов проектирования. Почти в любой объектно-ориентированной программе обычно существует один-два объекта, которые инициализируются в начале, и используются на всем протяжении работы приложения. Таким объектом может быть, logger — объект для ведения log-ов (log в переводе с англ. — журнал). Так в случае с log-ами, нам нет нужды каждый раз создавать объект класса logger, лишь для того, чтобы произвести запись в журнал. Достаточно создать один экземпляр класса, в начале работы программы, и пользоваться им. Такие классы (как например logger) будем называть Singleton классами.

Важно отметить, что Singleton Pattern также решает еще одну наболевшую задачу. Часто бывает так, что мы передаем в функцию (метод) параметр лишь для того, чтоб эта функция передала этот параметр еще дальше. Или же несколько частей программы постоянно нуждаются в «свежей» информации о состоянии объекта, который меняется по ходу работы; тогда снова и снова приходиться передавать объект как параметр.

Конечно, если такая ситуация возникает пару раз за всю работу приложения, можно не обращать на это внимания. Но, если один и тот же объект бестолково «футболяется» между всеми модулями и функциями программы, может возникнуть страшная путаница и, как следствие, ошибки. В таких ситуациях следует использовать Singleton класс. Вместо того, чтоб передавать по цепочке объект из функции в функцию, Singleton Pattern обеспечивает возможность, каждой части программы, получить по ссылке единственный для всей программы объект.

Вот какие преимущества дает Singleton Pattern:

  1. Мы используем ссылки на один и тот же экземпляр класса в разных частях проекта.
  2. Таким образом не нужно создавать новый объект каждый раз когда мы хотим воспользоваться каким-то методом — просто пользуемся ссылкой.
  3. Теперь не нужно передавать объект как аргумент, чтоб все части программы были «в курсе» о текущем состоянии объекта. Все ссылки указывают на один объект.

Еще раз сформулируем суть Singleton Pattern: Убедиться, что существует только один экземпляр класса и обеспечить доступ к нему из любой части программы.

Недостаток «глобального» похода

Итак, мы хотим получить только одного представителя класса и использовать его везде, где нам заблагорассудится. Казалось бы, логично ввести глобальную переменную и не загружать себе голову всякими pattern-ами. Но тут есть подвох.
Представим, что мы решили использовать глобальную переменную, содержащую singleton объект. Все нужные нам свойства есть — переменная одна и доступна отовсюду. В чем же тогда проблема? Проблема в том, что глобальная переменная непредсказуемая: у нас нет гарантии, что, на протяжении работы приложения, в ней содержится тот же объект, который мы инициализировали в начале.

У этой проблемы есть решение, но оно кривое и страшненькое. На мой взгляд классический подход лучше (на то он и классический).

Как создать singleton класс

Очень просто. В классе необходимо создать статический метод getInstance(), который возвращает единственный экземпляр класса. Если этот метод вызывается первый раз, то экземпляр класса создается, сохраняется в приватном (private), статическом (static) поле, и возвращает свеже-созданный объект. При повторном использовании метода, возвращается содержимое поля (т.е. экземпляр класса).
В следующем примере реализовано все вышесказанное.

<?php
// PHP5
class Logger {
static private $instance = NULL;
  static function getInstance()
  {
    if (self::$instance == NULL)
    {
      self::$instance = new Logger();
    }
    return self::$instance;
  }
private function __construct()
{
}
private function __clone()
{
}
}
//пусть далее размещены методы необходимые разработчику.
?>

Метод getInstance() — суть Singleton pattern. С помощью getInstance() мы можем получить доступ к объекту класса logger откуда угодно. В тоже время, методы construct() и clone(), объявленные как приватные (private), обеспечивают единственность экземпляра класса logger. Удален источник возможных ошибок — разработчик не может случайно создать еще один объект такого же типа (Такой же принцип действует и при построении value object — убрать, спрятать все методы, с помощью которых, объекты смогут <размножаться>). Следовательно, getInstance()— единственный способ <достучаться> к представителю класса.

Что же вышло в итоге

Итак, нам удалось создать singleton класс. Мы обеспечили единственность экземпляра класса и организовали удобный доступ к нему. Что же мы выиграли, используя singleton pattern?

  • Не нужно опасаться, что наш единственный объект «подменится» по ходу работы приложения. Он надежно защищен от случайностей.
  • Из любой части программы можно легко получить объект и пользоваться им. (и вовсе не нужно рассылать его, как параметр, всем функциям или создавать каждый раз заново, чтоб использовать какой-то метод).

P.s.

Паттерн Singleton следует применять очень хорошо подумав, т.к. в некоторых случаях его можно рассматривать как Антипаттерн. Речь идет о том, что Singleton часто используют просто как глобальную переменную, и в этом случае к недостаткам глобальной переменной добавляется еще и проблема существования только одного экземпляра класса.

Поэтому следует четко понимать для чего вы создаете Singleton — для глобального доступа (для чего, вообще говоря, он не предназначался), для реализации единственного экземпляра класса, либо для того и другого вместе. Если у вас слишком много Singleton'ов в приложении, возможно, есть смысл пересмотреть его дизайн.

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

Комментарии