Данная статья ориентирована на тех, кто впервые столкнулся с понятием паттернов, услышал о Singleton’e, либо каким-то образом его сделал, но так ничего и не поняли. Welcome! Впервые с паттернами студенты JavaRush сталкиваются на 15 уровне, когда неожиданным образом кэп просит “закрепить” и реализовать паттерн Singleton с ленивой реализацией. У студентов, впервые услышавших про Singleton, мгновенно возникает куча вопросов: что вообще такое паттерн, зачем он нужен, какой еще Singleton и наконец, что еще за ленивая реализация. Начнем отвечать по-порядку: Паттерны и Singleton – для всех, кто впервые с ними столкнулся - 1

Что вообще такое паттерн

Отвечать на этот вопрос для лучшего понимания, полагаю, стоит с истории. Среди программистов есть такая знаменитая четверка авторов: Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес, которым пришла в голову интересная мысль.
Паттерны и Singleton – для всех, кто впервые с ними столкнулся - 2
Они заметили, что при написании программ им часто приходится решать приблизительно одни и те же задачи, и писать по структуре однотипный код. Поэтому они решили описать в виде паттернов типовые шаблоны, которые часто приходится использовать в объектно-ориентированном программировании. Книга вышла в 1995 году под названием «Приемы объектно-ориентированного проектирования. Паттерны проектирования» . Название книги оказалось слишком длинным, и ее просто стали называть «Книгой банды четырех». В первом издании было опубликовано 23 паттерна, после чего были открыты и десятки других. Так вот, отвечая на вопрос этого параграфа, — «Что же такое паттерны», подытожим буквально в нескольких словах:
Паттерн – это стандартизированное решение какой-либо часто встречающейся проблемы.
И Singleton – это всего-лишь один из таких паттернов.

Зачем нужны паттерны (шаблоны проектирования)

Программировать получается и без знания паттернов, убедиться в этом можно просто осознав тот факт, что к 15-му уровню на JavaRush вы написали сотни мини-программ, ничего не зная об их существовании. Это говорит о том, что паттерн – это своего рода инструмент, наличие которого и отличает мастера от любителя:
Паттерны и Singleton
В паттернах описывается, как правильно следует решать одну из типовых задач. Как следствие, знание паттернов экономит ваше время. Можно привести аналогию с алгоритмами. К примеру, можно придумывать "свой" алгоритм сортировки с блекджеком и цифрами и потратить на это много времени, а можно использовать уже давно описанный и реализовать его. То же самое и с паттернами. Плюс ко всему, с использованием паттернов код становится более стандартизирован, а при использовании нужных шаблонов у вас будет меньше вероятности сделать ошибки, так как их уже давно предвидели и устранили в этом паттернте. Ну и плюс ко всему, знание паттернов позволяет программистам лучше понимать друг друга. Достаточно просто произнести название шаблона, вместо того, чтобы пытаться объяснить своим коллегам-программистам, чего вы от них хотите. Итак, подытожим, шаблоны проектирования помогают:
  • не изобретать велосипед, а использовать стандартные решения;
  • стандартизировать код;
  • стандартизировать терминологию;
В заключении этого раздела отметим, что все многообразие паттернов можно упрощенно разбить на три большие группы:
Паттерны и Singleton – для всех, кто впервые с ними столкнулся - 3

Наконец-то паттерн Singleton

Singleton относится к порождающим паттернам. Его дословный перевод – одиночка. Этот паттерн гарантирует, что у класса есть только один объект (один экземпляр класса) и к этому объекту предоставляется глобальная точка доступа. Из описания должно быть понятно, что этот паттерн должен применяться в двух случаях:
  1. когда в вашей программе должно быть создано не более одного объекта какого-либо класса. Например, в компьютерной игре у вас есть класс «Персонаж», и у этого класса должен быть только один объект описывающий самого персонажа.

  2. когда требуется предоставить глобальную точку доступа к объекту класса. Другими словами, нужно сделать так, чтобы объект вызывался из любого места программы. И, увы, для этого не достаточно просто создать глобальную переменную, ведь она не защищена от записи и кто угодно может изменить значение этой переменной и глобальная точка доступа к объекту будет потеряна. Это свойства Singleton'a нужно, например, когда у вас есть объект класса, который работает с базой данных, и вам нужно чтобы к базе данных был доступ из разных частей программы. А Singleton будет гарантировать, что никакой другой код не заменил созданный ранее экземпляр класса.
Вот эти две задачи и решает Singleton: объект в программе должен быть один и к нему есть глобальный доступ. В примере на 15 уровне кэп просит создать реализовать этот паттерн для следующей задачи (вот ее описание):
Паттерны и Singleton – для всех, кто впервые с ними столкнулся - 4
Внимательно прочитав условие, становится понятно, зачем здесь нужен именно Singleton (Одиночка). Ведь в программе просят создать по одному объекту каждого класса: Sun, Moon, Earth. И логично предположить, что каждый класс в программе должен создавать не больше одного Солнца/Луны/Земли, иначе это будет абсурд, если конечно вы не пишите свою версию звездных воин. Особенность реализации Singleton в Java за три шага Поведение Одиночки на Java невозможно реализовать с помощью обычного конструктора, потому что конструктор всегда возвращает новый объект. Поэтому все реализации Singleton’a сводятся к тому, чтобы скрыть конструктор и создать публичный статический метод, который будет управлять существованием объекта-одиночки и «уничтожать» всех вновь-появляющихся объектов. В случае вызова Singleton’a он должен либо создать новый объект (если его еще нет в программе), либо вернуть уже созданный. Для этого: #1. – Нужно добавить в класс приватное статическое поле, содержащее одиночный объект:
public class LazyInitializedSingleton {
	private static LazyInitializedSingleton instance; //#1
}
#2. – Сделать конструктор класса (конструктор по-умолчанию) приватным (чтобы доступ к нему был закрыть за пределами класса, тогда он не сможет возвращать новые объекты):
public class LazyInitializedSingleton {
	private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){} // #2
}
#3. – Объявить статический создающий метод, который будет использоваться для получения одиночки:
public class LazyInitializedSingleton {
    private static LazyInitializedSingleton instance;
        private LazyInitializedSingleton(){}
        public static LazyInitializedSingleton getInstance(){ // #3
        if(instance == null){		//если объект еще не создан
            instance = new LazyInitializedSingleton();	//создать новый объект
        }
        return instance;		// вернуть ранее созданный объект
    }
}
Вышеописанный пример несколько топорный, ведь мы просто скрываем конструктор и предоставляем взамен стандартного конструктора свой метод. Так как эта статья направлена на то, чтобы студенты JavaRush’a смогли впервые соприкоснуться с этим паттерном (и паттернами в принципе), здесь не будут приведены особенности реализации более сложных Одиночек. Отметим лишь, что в зависимости от сложности программы может потребоваться более детальная доработка этого паттерна. Например, в многопоточной среде (см. тему Thread’ы), несколько разных потоков могут одновременно вызвать метод получения Одиночки, и описанный выше код перестанет работать, ибо каждый отдельный поток сможет создать сразу несколько экземпляров класса. Поэтому еще есть несколько разных подходов к созданию правильных Thread-safe одиночек. Но это уже другая история =) И напоследок. Что же такое Ленивая Инициализация, о которой просил кэп Ленивую инициализацию (Lazy Initialization) еще называют отложенной инициализацией. Это прием в программировании, когда ресурсоемкая операция (а создание объекта – это ресурсоемкая операция) выполняется по требованию, а не заблаговременно. Что в общем-то и происходит в нашем коде Singleton’a. Другими словами, наш объект создается в момент обращения к нему, а не заранее. Не следует полагать, что понятие ленивой инициализации как-то жестко связана именно с Singleton’ом. Отложенная инициализация также используется и в других порождающих паттернах проектирования, например в таких как Proxy (Заместитель) и Factory Method (Фабричный метод), но это тоже другая история =) При подготовке материалов статьи использовались следующие источники:
  1. Java Singleton Design Pattern Best Practices with Examples
  2. Паттерны проектирования
  3. Правильный Singleton в Java