Паттерны и Singleton – для всех, кто впервые с ними столкнулся

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

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

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

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

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

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

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

  2. когда требуется предоставить глобальную точку доступа к объекту класса. Другими словами, нужно сделать так, чтобы объект вызывался из любого места программы. И, увы, для этого не достаточно просто создать глобальную переменную, ведь она не защищена от записи и кто угодно может изменить значение этой переменной и глобальная точка доступа к объекту будет потеряна. Это свойства Singleton'a нужно, например, когда у вас есть объект класса, который работает с базой данных, и вам нужно чтобы к базе данных был доступ из разных частей программы. А Singleton будет гарантировать, что никакой другой код не заменил созданный ранее экземпляр класса.
Вот эти две задачи и решает Singleton: объект в программе должен быть один и к нему есть глобальный доступ. В примере на 15 уровне кэп просит реализовать этот паттерн для следующей задачи (вот ее описание):
Паттерны и Singleton – для всех, кто впервые с ними столкнулся - 5
Внимательно прочитав условие, становится понятно, зачем здесь нужен именно 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
Комментарии (93)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Konstantin_B Уровень 15, Москва, США
4 мая 2022
"...доступ к нему был закрыть..." - это как-то не по-русски :)
Konstantin_B Уровень 15, Москва, США
4 мая 2022
На самом деле "впервые и неожиданно" Кэп говорит про Singleton на 14 уровне - 8 лекция с кучей хардовых задачек: "Реализуй Singleton pattern: Создай класс Singleton в отдельном файле"... И без всякой "ленивой реализации" Видимо, авторы курса стараются его адаптировать под критикой студентов - это радует :)
KOTNinja Уровень 32, Санкт-Петербург, Россия
8 марта 2022
Статья ок, спасибо. А картинка с паттернами вообще потрясающая, в голове сразу всплыло это..
newNoName Уровень 28, Киев, Ukraine
14 февраля 2022
"подЫтожим" - режет глаза. Проверочное - "И"тог.
Бостон Уровень 24, Russian Federation
29 января 2022
Правильно ли я понимаю, что при "неленивой инициализации" мы бы просто написали

private static LazyInitializedSingleton instance = new LazyInitializedSingleton();
И в методе getInstance возвращали бы этот instance без каких либо условий? Спасибо.
Алексей Сивенков Уровень 16, Москва
12 января 2022
огромное спасибо!!! жаль только что авторы курса не прикрепляют ссылки на подобные статьи в описании задания(( с одной стороны идея подтолкнуть к самостоятельному изучению кажется перспективной, НО на этапе первоначального обучения считаю ее несостоятельной - информации и так слишком много а времени очень мало. Поэтому очень требуются подобные емкие статьи!!!!Спасибо )
SomeBoy Уровень 35, Москва, Russian Federation
10 декабря 2021
Ты тоже искал реализацию паттерна?....🤣 Для начала займитесь поиском и найдите где-нибудь в интернете пример "ленивой" реализации Singleton и создайте по образу и подобию три Singleton-класса: Sun, Moon и Earth.
Cold Уровень 51, Эстония
5 ноября 2021
Хорошая статья , люблю такие! 😍
Valeriy Уровень 32, Киев
19 октября 2021
спасибо, помогли)
Aleksey Grin Уровень 22, Санкт-Петербург, Россия
19 октября 2021
Небольшая ошибка, метод getInstance() в любом случае должен возвращать объект. Не хватает ретурна в иф блоке: if(instance == null){ //если объект еще не создан instance = new LazyInitializedSingleton(); //создать новый объект } >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> if(instance == null){ //если объект еще не создан instance = new LazyInitializedSingleton(); //создать новый объект return instance; // вернуть только что созданный объект }