Статьи

Шаблоны креационного дизайна: шаблон Singleton

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

При создании класса как одиночного есть некоторые определенные проблемы, которые необходимо решить.

  • Как можно гарантировать, что у класса есть только один экземпляр.
  • Как можно легко получить доступ к единственному экземпляру класса?
  • Как класс может контролировать свою реализацию
  • Как можно ограничить количество экземпляров класса

Предположим, у нас есть класс, который отправляет сообщения.
Класс Messenger.

1
2
3
4
5
6
7
8
package com.gkatzioura.design.creational.singleton;
 
public class Messenger {
 
    public void send(String message) {
         
    }
}

Однако мы хотим, чтобы процедура сообщения была обработана только одним экземпляром класса Messenger. Представьте себе сценарий, в котором класс Messenger открывает tcp-соединение (например, xmpp) и должен поддерживать соединение в активном состоянии для отправки сообщений. Будет довольно неэффективно открывать новое соединение xmpp каждый раз, когда нам нужно будет отправить сообщение.

Поэтому мы продолжим и сделаем класс посланника синглтоном.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com.gkatzioura.design.creational.singleton;
 
public class Messenger {
 
    private static Messenger messenger = new Messenger();
 
    private Messenger() {}
 
    public static Messenger getInstance() {
        return messenger;
    }
 
    public void send(String message) {
 
    }
}

Как видите, мы установили конструктор мессенджера как приватный и инициализировали мессенджер, используя статическую переменную.
Статические переменные являются переменными уровня класса, распределение памяти происходит только один раз, когда класс загружается в память. Таким образом мы гарантируем, что класс мессенджера будет создан только один раз. Метод getInstance будет извлекать экземпляр статического мессенджера после его вызова.

Очевидно, что предыдущий подход имеет свои плюсы и минусы. Нам не нужно беспокоиться о безопасности потоков, и экземпляр будет создан только тогда, когда будет загружен класс Messenger. Однако ему не хватает гибкости. Рассмотрим сценарий передачи переменных конфигурации в конструктор Messenger. Это невозможно при использовании предыдущего подхода.

Обходной путь должен создать экземпляр класса мессенджера в методе getInstance.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package com.gkatzioura.design.creational.singleton.lait;
 
public class Messenger {
 
    private static Messenger messenger;
 
    private Messenger() {}
 
    public static Messenger getInstance() {
 
        if(messenger==null) {
            messenger = new Messenger();
        }
 
        return messenger;
    }
     
    public void send(String message) {
 
    }
}

Вышеупомянутый подход может работать в определенном случае, но он пропускает безопасность потока в случаях, когда класс может быть создан в многопоточной среде.

Самый простой подход к обеспечению безопасности потока нашего класса — это синхронизировать метод getInstance.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package com.gkatzioura.design.creational.singleton.lait;
 
public class Messenger {
 
    private static Messenger messenger;
 
    private Messenger() {}
 
    public synchronized static Messenger getInstance() {
 
        if(messenger==null) {
            messenger = new Messenger();
        }
 
        return messenger;
    }
 
    public void send(String message) {
 
    }
}

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

Другой подход заключается в использовании метода двойной проверки блокировки. Теперь блокировка с двойной проверкой требует особой осторожности, так как легко выбрать неправильную реализацию вместо правильной.
Наилучшим подходом является реализация отложенной загрузки с использованием ключевого слова volatile .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.gkatzioura.design.creational.singleton.dcl;
 
public class Messenger {
 
    private static final Object lock = new Object();
    private static volatile Messenger messenger;
 
    private Messenger() {}
 
    public static Messenger getInstance() {
 
        if(messenger==null) {
            synchronized (lock) {
                if(messenger==null) {
                    messenger = new Messenger();
                }
            }
        }
 
        return messenger;
    }
 
    public void send(String message) {
 
    }
}

Используя ключевое слово volatile, мы предотвращаем изменение порядка записи volatile по отношению к любому предыдущему чтению или записи, а также чтение volatile, которое необходимо изменить по отношению к любому последующему чтению или записи. Также объект взаимного исключения используется для достижения синхронизации.

Подводя итог, мы создали объект, и мы также убедились, что будет только один экземпляр этого объекта. Также мы позаботились о том, чтобы не возникало проблем при создании объекта в многопоточной среде.

Вы можете найти исходный код на github .

В следующем посте мы рассмотрим образец прототипа .

Также я собрал шпаргалку, содержащую сводку шаблонов креационного дизайна. Зарегистрируйтесь в ссылке, чтобы получить его.

Опубликовано на Java Code Geeks с разрешения Эммануила Гкациоураса, партнера нашей программы JCG. Смотрите оригинальную статью здесь: Шаблоны креационного дизайна: шаблон Singleton

Мнения, высказанные участниками Java Code Geeks, являются их собственными.