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

Важно отметить, что 

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

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

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

Еще раз сформулируем суть 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?

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

P.s.

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

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

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

_19.10.2006

Алексей Гоголев_

Комментарии

comments powered by Disqus