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

Существует две формы реализации Singleton:

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

2. Отложенное создание экземпляров. В этой форме экземпляр класса Singleton создается только при первом запросе. Это сделано для того, чтобы избежать ненужного использования ресурсов, когда синглтон не нужен немедленно. Ленивое создание экземпляров может быть достигнуто с помощью различных методов, таких как «блокировка с двойной проверкой», «идиома держателя инициализации по требованию».

Преимущества шаблона проектирования Singleton:

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

2. Управление ресурсами. Это помогает в эффективном управлении ресурсами, особенно для дорогостоящих объектов, которые необходимо совместно использовать во всем приложении.

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

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

Использование шаблона проектирования Singleton:

Шаблон Singleton обычно используется в сценариях, где вы хотите иметь только один экземпляр класса, например:

1. Управление конфигурацией. Управление параметрами конфигурации приложения в одном экземпляре.

2. Подключения к базе данных: создание единого пула подключений к базе данных для обработки всех операций с базой данных.

3. Регистратор. Использование одного экземпляра регистратора во всем приложении.

4. Кэширование. Поддержание одного экземпляра кэша для хранения часто используемых данных.

5. Пул потоков. Реализация единого пула потоков для одновременного управления и выполнения задач.

Как создать шаблон проектирования Singleton:

Статический член: он получает память только один раз, так как статический содержит экземпляр класса Singleton.

Частный конструктор: предотвращает создание экземпляра класса Singleton вне класса.

Статический фабричный метод. Он предоставляет глобальную точку доступа к объекту Singleton и возвращает экземпляр вызывающей стороне.

Значение сериализации:

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

Чтобы сохранить шаблон Singleton во время десериализации, вам необходимо предоставить метод readResolve(), который позволяет вам управлять экземпляром, возвращаемым после десериализации. Метод readResolve() будет вызываться при десериализации объекта, и вы сможете вернуть существующий экземпляр Singleton.

Давайте рассмотрим реальный пример класса Configuration Manager, реализующего шаблон Singleton. В этом примере рассматриваются аспекты отложенного создания экземпляров и сериализации.

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class ConfigurationManager implements Serializable {
    private static final long serialVersionUID = 1L;
    private static volatile ConfigurationManager instance;
    private Map<String, String> configMap;

    private ConfigurationManager() {
        // Private constructor to prevent direct instantiation from other classes.
        this.configMap = new HashMap<>();
        // Simulating loading configuration from a file or other sources.
        loadConfiguration();
    }

    public static ConfigurationManager getInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = new ConfigurationManager();
                }
            }
        }
        return instance;
    }

    public String getConfig(String key) {
        return configMap.get(key);
    }

    public void setConfig(String key, String value) {
        configMap.put(key, value);
    }

    // Simulating loading configuration from a file or other sources.
    private void loadConfiguration() {
        configMap.put("app.mode", "production");
        configMap.put("db.url", "jdbc:mysql://localhost:3306/mydb");
        configMap.put("db.username", "root");
        configMap.put("db.password", "password");
    }

    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Cloning of Singleton is not allowed.");
    }

    // Maintaining Singleton during deserialization
    protected Object readResolve() {
        return getInstance();
    }
}

Использование диспетчера конфигураций:

public class Main {
    public static void main(String[] args) {
        ConfigurationManager configManager1 = ConfigurationManager.getInstance();
        ConfigurationManager configManager2 = ConfigurationManager.getInstance();

        // Both configManager1 and configManager2 are references to the same instance.
        System.out.println(configManager1 == configManager2); // Output: true

        // Get and set configuration values
        String mode = configManager1.getConfig("app.mode");
        System.out.println("Application mode: " + mode); // Output: Application mode: production

        configManager1.setConfig("app.mode", "development");
        mode = configManager2.getConfig("app.mode");
        System.out.println("Updated application mode: " + mode); // Output: Updated application mode: development
    }
}

В этом примере мы используем класс ConfigurationManager для управления параметрами конфигурации приложения. И configManager1, и configManager2 указывают на один и тот же экземпляр, демонстрируя поведение шаблона Singleton. Любые изменения, внесенные в конфигурацию через одну ссылку, будут отражены при доступе к конфигурации через другую ссылку. Класс ConfigurationManager также обеспечивает правильную обработку проблем с сериализацией и загрузчиком классов для поддержки шаблона Singleton.