User Дмитрий Денисов
Дмитрий Денисов
22 уровень
Екатеринбург

Паттерны и 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
Комментарии (77)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ СДЕЛАТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Nadezhda Goncharova Уровень 27
18 октября 2021
Спасибо за лекцию!
Александр Горохов Уровень 15, Дятьково, Россия
17 октября 2021
Ёмаё, узнал, что если сделать конструктор по умолчанию приватным, это не даст создавать новые экземпляры класса. Вспышка, буря, озарение 👨‍💻
Николай Уровень 16, Москва, Россия
3 октября 2021
Спасибо, актуально.
piersto Уровень 17, Montreal, Canada
13 июля 2021
Вот тут можно посмотреть какие существуют паттерны: https://refactoring.guru/ru/design-patterns/catalog
Mixer-X Уровень 37, Санкт-Петербург
3 июля 2021
"Это свойствА Singleton'a нужно,..." А следует исправить на О. "...что понятие ленивой инициализации как-то жестко связанА..." Понятие связанО. Исправить не помешает. "...так как их уже давно предвидели и устранили в этом паттернТе..." Лишняя буквочка. И штук 5 запятых, но бог с ними. Спасибо за урок.
Yarik Уровень 33, Оренбург, Россия
24 июня 2021
Нормальный пример, для понимания самое то. Конструктор по умолчанию спрятанный за privat ом исключает создание экземпляров класса, а метод в котором реализована проверка уже создает в зависимости от условий. Для понимания очень хороший пример.
Алексей С Уровень 22, Россия
8 мая 2021
Очень полезная статья.
pechkin Уровень 17, Санкт-Петербург
25 апреля 2021
Недотерли картинку с образами из Футурамы
SPetr Уровень 14, Минск
24 марта 2021
Название класса подобрано просто идеально, видеть его в коде четыре строчки под ряд просто рай для шизофреников...
Валера Калиновский Уровень 32, Харьков, Украина
11 февраля 2021
все ок но наверное стоило бы добавить для случая многопоточности, это всего 2 строки