Паттерн Singleton - Одиночка
m.vk.comП Паттерн Singleton - Одиночка
CODE BLOG | Программирование·15 янв в 22:57
Паттерны проектирования - это описание некоторых проблем, возникающих во время объектно-ориентированного проектирования, а также способов их решения (как практических, так и теоретических). Иными словами - это примеры правильных подходов к решению типичных задач проектирования.
Одним из самых распространенных паттернов является Singleton (Одиночка). Задача этого шаблона ограничить количество экземпляров некоторого класса в пределах приложения. Зачем это может понадобиться на практике? Об этом читайте чуть ниже.
П Простая реализация Singleton
Один из самых простых способов реализовать паттерн Singleton на языке Java выглядит так:
public final class Singleton { private static Singleton _instance = null; private Singleton() {} public static synchronized Singleton getInstance() { if (_instance == null) _instance = new Singleton(); return _instance; } }
Теперь приведу некоторые объяснения по поводу реализации шаблона.
Конструктор класса необходимо объявить с модификатором видимости private. Это предотвратит создание экземпляров класса как с помощью класса Singleton, так и с помощью его наследников. В связи с этим к объявлению класса смело можно дописать модификатор final.
Метод getInstance() создаст ровно один экземпляр класса Singleton. Этот метод объявлен как synchronized. Сделано это вот почему. В многопоточных программах при одновременном вызове метода getInstance() из нескольких потоков можно создать несколько экземпляров класса Singleton. А должен остаться только один!
От модификатора synchronized можно избавиться. Для этого _instance нужно проинициализировать:
private static final Singleton _instance = new Singleton(),
а в методе getInstance() убрать конструкцию "if". Тогда инициализация произойдет во время загрузки класса.
Но использование поздней инициализации (lazy initialization) предпочтительнее в случае, если создание экземпляра класса занимает много времени. Да и в случае ленивой инициализации есть возможность обработать возникшие исключитальные ситуации при вызове конструктора.
С Синглтон с double-checked locking
Проблема с lazy инициализацией остается только в том, что синхронизация нужна по идее только один раз, чтобы несколько потоков не вошли в критическую секцию одновременно. Но после создания экземпляра класса от синхронизации хотелось бы избавиться.
Самый распространенный способ избавиться от лишней синхронизации - это double-checked locking, который выглядит таким образом:
public final class Singleton { private static volatile Singleton _instance = null; private Singleton() {} public static synchronized Singleton getInstance() { if (_instance == null) synchronized (Singleton.class) { if (_instance == null) _instance = new Singleton(); } return _instance; } }
С Синглтон с Instance Holder
А вот еще один заслуживающий внимания вариант реализации шаблона Одиночка с ленивой инициализацией:
public final class Singleton { private Singleton() {} private static class Holder { private static final Singleton _instance = new Singleton(); } public static Singleton getInstance() { return Holder._instance; } }
Объект будет проинициализирован при первом вызове метода getInstance(). То есть мы перенесли проблему с синхронизацией на уровень загрузчика классов (class loader).
Но что если в приложении несколько Class Loader'ов или вообще у нас распределенная система с несколькими виртуальнми машинами Java? Тогда все сложно. Поговорим об этом в другой раз.
А вообще сейчас можно говорить, что реально крутой вариант паттерна Одиночка выглядит так:
public enum Singleton { INSTANCE; }
Вот собственно и все... Ах, да! Зачем нам это нужно.
П Практический пример синглтона
Мне приходится чаще всего использовать этот паттерн при работе с конфигурацией. Иногда конфигурацию программы удобно хранить в файле. Допустим, это будет простой текстовый файл "props.txt" со строками типа "ключ=значение". Нам нужно гарантировать, что конфигурация в программе будет в единственном экземпляре. Вторую мы бы и так не создали, но нужно запретить это делать пользователю класса. Итак,
import java.util.*; import java.io.*; public class Configuration { private static Configuration _instance = null; private Properties props = null; private Configuration() { props = new Properties(); try { FileInputStream fis = new FileInputStream( new File("props.txt")); props.load(fis); } catch (Exception e) { // обработайте ошибку чтения конфигурации } } public synchronized static Configuration getInstance() { if (_instance == null) _instance = new Configuration(); return _instance; } // получить значение свойства по имени public synchronized String getProperty(String key) { String value = null; if (props.containsKey(key)) value = (String) props.get(key); else { // сообщите о том, что свойство не найдено } return value; } }
Теперь для работы с конфигурацией можно использовать конструкцию вида:
String propValue = Configuration.getInstance().getProperty(propKey).
Если имена свойств в "props.txt" меняться не будут, можно описать их в классе таким образом:
public static final String PROP_KEY = "propKey",
а значения получать так:
String propValue = Configuration.getInstance() .getProperty(Configuration.PROP_KEY).
Паттерн Singleton полезен не только при работе с конфигурациями. Его можно использовать также при написании ConnectionPool, Factory и других вещей.
Также рекомендую ознакомиться с другими материалами посвященными
Java и
паттернам проектирования. Поставь лайк и поделись статьей с друзьями — это лучший способ помочь развитию сообщества.