Одиночка

Описание
Одиночка — компонент, экземпляр которого создается только один раз. Применяется в случаях:

- в системе присутствует только один компонент
- вызов конструктора трудоемко и делается только один раз
- запрет на создание копии объекта

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

Создание через аллокатор
В классе создается дандер-метод __new__. В котором проверяется состояние переменной _instance и создается объект, если этого не происходило.

Этого достаточно при условии, когда в классе нет конструктора. Метод __init__ вызывается сразу же после метода __new__/

'''Использование метода new, в котором проверяется создание объекта'''


from dataclasses import dataclass
from typing import ClassVar


@dataclass
class Database:
    _instance: ClassVar = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Database, cls)\
                .__new__(cls, *args, **kwargs)

        return cls._instance


if __name__ == '__main__':
    d1 = Database()
    d2 = Database()

    print(d1 == d2)

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


Теперь при создании объектов, инициализация происходит 1 раз и изменение name не происходит

from dataclasses import dataclass
from typing import ClassVar


def singleton(class_):
    instances = {}

    def get_instance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]

    return get_instance

@singleton
@dataclass
class Database:
    name: str

    def __post_init__(cls):
        print(cls.name)


if __name__ == '__main__':
    d1 = Database('SQL')
    d2 = Database('NOSQL')

    print(d1 == d2)
    print(d2.name)