Паттерн Одиночка – порождающий паттерн проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр класса с глобальной точкой доступа.
Реализация: паттерн чрезвычайно прост, попробуем с его помощью создать машину для президента, которая будет существовать в единственном экземпляре. Создание приватного конструктора в классе будет неплохим первым шагом в достижении нашей цели, это позволит избежать создание одиночки другими классами, вторым шагом будет создание метода получения экземпляра класса. Если с конструктором все понятно, то с методом, который возвращает экземпляр нашего «одиночки», все несколько сложнее. Единственный класс приложения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class PresidentCar{ private static PresidentCar presidentCar = new PresidentCar(); private int armor; private PresidentCar(){ this.armor = 120; } public static PresidentCar getInstance(){ return presidentCar; } public int getArmor(){ return armor; } public void setArmor(int armor){ this.armor = armor; } } |
В классе две переменных: ссылка на сам объект класса (presidentCar) и переменная для хранения информации о бронировании машины. Для того, чтобы объект класса нельзя было создать из вне, конструктор класса был сделан приватным, в конструкторе задается начальное значение бронирования автомобиля в 120 мм. Для получения ссылки на объект используется статичный метод getInstance(). Два оставшихся метода getArmor() и setArmor(int armor) используются для получения информации о бронировании машины и ее изменении. В некоторых источниках рекомендуется для переменной экземпляра класса (presidentCar) использовать модификатор final. Давайте посмотрим, как это все работает:
1 2 3 4 5 6 7 8 9 |
public class Example{ public static void main(String[] args) { PresidentCar prCar = PresidentCar.getInstance(); System.out.println(prCar.getArmor()); prCar.setArmor(250); PresidentCar prCar1 = PresidentCar.getInstance(); System.out.println(prCar1.getArmor()); } } |
Вывод:
120
250
И так, первой строчкой мы получаем ссылку на экземпляр класса PresidentCar и присваиваем ее переменной prCar. Второй строчкой выводим информацию о бронировании машины – 120 мм, значение было установлено приватным конструктором. Третьей строчкой мы меняем значение брони на 250 мм. Для того, чтобы убедиться, что в памяти создан только один экземпляр класса PresidentCar мы создаем еще одну переменную и через метод getInstance() получаем ссылку на объект класса PresidentCar. Последней строчкой проверяем информацию о бронировании, как видите объект в памяти остался прежний и вернул новое значение в 250 мм.
Вы, наверное, обратили внимание в определении паттерна на небольшое уточнение — «в однопоточном приложении», а если у нас есть необходимость в нескольких потоках, то как быть?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class PresidentCar{ private volatile static PresidentCar presidentCar; private int armor; private PresidentCar(){ this.armor = 120; } public static PresidentCar getInstance(){ if(presidentCar == null){ synchronized (PresidentCar.class){ if(presidentCar == null){ presidentCar = new PresidentCar(); } } } return presidentCar; } public int getArmor(){ return armor; } public void setArmor(int armor){ this.armor = armor; } } |
Первое изменение это – переменная presidentCar в ее объявлении теперь присутствует ключевое слово volatile, оно гарантирует, что параллельные потоки будут корректно с ней работать; второе изменение коснулось метода getInstance() – теперь в нем используется двойная проверка, если экземпляр класса не существует, то происходит вход в блок synchronized, другими словами синхронизация выполняется только при первом вызове, внутри этого блока снова проверяется создан ли экземпляр класса, если нет, то он создается. Для чего это сделано – синхронизация сама по себе очень затратная операция с точки зрения системных ресурсов и использовать ее при каждом вызове экземпляра класса нецелесообразно.
И так, самым главным плюсом данного патерна проектирования «Одиночка» является контролируемый доступ к единственному экземпляру класса.
Полный исходный код для однопоточного приложения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package example; class PresidentCar{ private static PresidentCar presidentCar = new PresidentCar(); private int armor; private PresidentCar(){ this.armor = 120; } public static PresidentCar getInstance(){ return presidentCar; } public int getArmor(){ return armor; } public void setArmor(int armor){ this.armor = armor; } } public class Example{ public static void main(String[] args) { PresidentCar prCar = PresidentCar.getInstance(); System.out.println(prCar.getArmor()); prCar.setArmor(250); PresidentCar prCar1 = PresidentCar.getInstance(); System.out.println(prCar1.getArmor()); } } |
Полный исходный код для многопоточного приложения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package example; class PresidentCar{ private volatile static PresidentCar presidentCar; private int armor; private PresidentCar(){ this.armor = 120; } public static PresidentCar getInstance(){ if(presidentCar == null){ synchronized (PresidentCar.class){ if(presidentCar == null){ presidentCar = new PresidentCar(); } } } return presidentCar; } public int getArmor(){ return armor; } public void setArmor(int armor){ this.armor = armor; } } public class Example{ public static void main(String[] args) { PresidentCar prCar = PresidentCar.getInstance(); System.out.println(prCar.getArmor()); prCar.setArmor(250); PresidentCar prCar1 = PresidentCar.getInstance(); System.out.println(prCar1.getArmor()); } } |