Singleton

Проблем

Да се разработи клас Logger, който записва съобщенията, генерирани от различните модули на приложение.

Решение без използване на шаблона

Най-естественото решение е използването на обикновен клас:

import java.util.ArrayList;
import java.util.List;

public class Logger {

    private List<String> messages = new ArrayList<>();

    public void log(String message) {
        messages.add(message);
    }

    public List<String> getMessages() {
        return new ArrayList<>(messages);
    }
}

Клиентски код:

public class Application {

    public static void main(String[] args) {

        Logger authenticationLogger = Logger.getInstance();
        Logger paymentLogger = Logger.getInstance();

        authenticationLogger.log("User login");
        paymentLogger.log("Payment completed");

        for (String message : authenticationLogger.getMessages()) {
            System.out.println(message);
        }
    }
}

Резултат:

User login

Недостатъци на решението

В представения пример променливите authenticationLogger и paymentLogger изглеждат като различни логъри, но всъщност е желателно всички съобщения да се записват на едно място. Ако всеки модул създава собствен обект Logger, управлението на записите се затруднява и се губи централизацията на логването.

Следователно е необходимо решение, което да гарантира използването на един и същ обект от всички части на приложението.

Шаблонът като решение

Шаблонът Singleton гарантира, че от даден клас може да съществува само една инстанция и предоставя глобална точка за достъп до нея.

Това се реализира чрез:

  • частен конструктор, който не позволява създаване на обекти извън класа;
  • статично поле, което съхранява единствената инстанция;
  • публичен статичен метод, който връща тази инстанция.

Дефиниция

Singleton е шаблон за проектиране от групата на шаблоните за създаване (Creational Design Patterns). Неговата цел е да гарантира наличието на единствен обект от даден клас и да осигури контролиран глобален достъп до него.

Всички реализации на шаблона следват две основни изисквания:

  • конструкторът е деклариран като private;
  • единствената инстанция се достъпва чрез публичен статичен метод.

UML диаграма

Singleton

Примерна реализация (Lazy Initialization)

import java.util.ArrayList;
import java.util.List;

public class Logger {

    private static Logger instance;

    private List<String> messages = new ArrayList<>();

    private Logger() {
    }

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }

    public void log(String message) {
        messages.add(message);
    }

    public List<String> getMessages() {
        return messages;
    }
}

Използване:

public class Application {

    public static void main(String[] args) {

        Logger authenticationLogger = Logger.getInstance();
        Logger paymentLogger = Logger.getInstance();

        authenticationLogger.log("User login");
        paymentLogger.log("Payment completed");

        for (String message : authenticationLogger.getMessages()) {
            System.out.println(message);
        }
    }
}

Конструкторът на класа е деклариран като private, поради което създаването на обекти чрез оператора new извън класа е невъзможно.

Единствената инстанция се съхранява в статичното поле instance. При първото извикване на метода getInstance() обектът се създава и се записва в това поле. При всички следващи извиквания се връща вече съществуващият обект.

По този начин всички части на приложението използват един и същ обект Logger, което позволява централизирано управление на процеса на записване.

Реализации на Singleton

Реализация Характеристика
Eager Initialization Инстанцията се създава при зареждане на класа. Подходящо, когато обектът винаги ще бъде използван.
Lazy Initialization Инстанцията се създава при първото извикване на getInstance().
Thread-safe Singleton Използва синхронизация, за да бъде безопасен при работа с множество нишки.
Enum Singleton Реализира се чрез enum; прост и надежден вариант, препоръчван в съвременна Java.

Разглежданата в настоящото упражнение реализация е най-подходяща за илюстриране на основната идея на шаблона.

[!IMPORTANT] Singleton гарантира една инстанция на клас и глобална точка за достъп до нея. Това обаче не означава, че шаблонът трябва да се използва винаги, когато е необходим „общ“ обект. Прекомерното му използване може да доведе до скрити зависимости и затруднено тестване.

Предимства

  • гарантира съществуването на единствена инстанция;
  • предоставя глобална точка за достъп;
  • централизира управлението на споделени ресурси;
  • предотвратява случайното създаване на множество обекти.

Недостатъци на шаблона

  • нарушава принципа Single Responsibility Principle (SRP), тъй като класът едновременно управлява собствената си логика и жизнения си цикъл;
  • създава глобално състояние в приложението;
  • затруднява модулното тестване;
  • при неправилна реализация може да създаде проблеми в многонишкова среда.

Приложение

Singleton е подходящ при разработване на:

  • конфигурационни мениджъри;
  • логващи системи;
  • кеширащи компоненти;
  • управление на връзки към база данни;
  • управление на споделени системни ресурси.

This site uses Just the Docs, a documentation theme for Jekyll.