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

Кратко

  • Паттерн (шаблон проектирования) это стандартное решение часто встречающейся задачи в ООП.
  • Паттерны экономят время, стандартизируют код и терминологию (не изобретаешь велосипед).
  • Singleton (Одиночка) это порождающий паттерн: он гарантирует, что у класса только один объект, и дает к нему глобальный доступ.
  • Реализуется в 3 шага: приватное статическое поле, приватный конструктор, статический метод getInstance().
  • Приватный конструктор не дает создавать новые объекты извне, а getInstance() отдает единственный экземпляр.
  • Ленивая инициализация (Lazy Initialization) это создание объекта по требованию (при первом обращении), а не заранее.
Паттерны проектирования и Singleton для начинающих

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

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

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

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

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

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

  2. когда требуется предоставить глобальную точку доступа к объекту класса. Другими словами, нужно сделать так, чтобы объект вызывался из любого места программы. И, увы, для этого не достаточно просто создать глобальную переменную, ведь она не защищена от записи и кто угодно может изменить значение этой переменной и глобальная точка доступа к объекту будет потеряна. Это свойства Singleton'a нужно, например, когда у вас есть объект класса, который работает с базой данных, и вам нужно чтобы к базе данных был доступ из разных частей программы. А Singleton будет гарантировать, что никакой другой код не заменил созданный ранее экземпляр класса.
Вот эти две задачи и решает Singleton: объект в программе должен быть один и к нему есть глобальный доступ. В примере на 15 уровне кэп просит реализовать этот паттерн для следующей задачи (вот ее описание):
Задача 15 уровня: по одному объекту классов Sun, Moon и Earth
Внимательно прочитав условие, становится понятно, зачем здесь нужен именно 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;		// вернуть ранее созданный объект
    }
}
Коротко все три шага реализации Singleton можно свести в таблицу:
ШагЧто добавляемЗачем
1приватное статическое поле instanceхранит единственный объект класса
2приватный конструкторзакрывает создание объектов извне
3статический метод getInstance()создает объект при первом вызове или возвращает уже созданный
Вышеописанный пример несколько топорный, ведь мы просто скрываем конструктор и предоставляем взамен стандартного конструктора свой метод. Так как эта статья направлена на то, чтобы студенты 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

Читайте также

Вопросы и ответы

Что такое паттерн проектирования?

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

Что такое паттерн Singleton (Одиночка)?

Это порождающий паттерн, который гарантирует, что у класса существует только один объект, и предоставляет к нему глобальную точку доступа.

Когда использовать Singleton?

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

Как реализовать Singleton в Java?

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

Почему конструктор Singleton делают приватным?

Обычный конструктор всегда возвращает новый объект. Чтобы объект был только один, конструктор закрывают (private), а создание контролирует статический метод getInstance().

Что такое ленивая инициализация (Lazy Initialization)?

Это создание объекта по требованию, при первом обращении к нему, а не заранее. В Singleton объект создается только при первом вызове getInstance().