11.1. Singleton

  • EN: Singleton

  • PL: Singleton

  • Type: object

11.1.1. Pattern

  • To ensure a class has a single instance

  • Database connection pool

  • HTTP Gateway

  • Settings

  • Main game/program window

../../_images/designpatterns-singleton-pattern.png

11.1.2. Problem

../../_images/designpatterns-singleton-problem.png
from typing import Any


class ConfigManager:
    settings: dict[str, Any]

    def __init__(self) -> None:
        self.settings = {}

    def set(self, key: str, value: Any) -> None:
        self.settings[key] = value

    def get(self, key: str) -> Any:
        return self.settings.pop(key)


if __name__ == '__main__':
    manager = ConfigManager()
    manager.set('name', 'Mark')

    other = ConfigManager()
    print(other.get('name'))
    # Traceback (most recent call last):
    # KeyError: 'name'

11.1.3. Solution

../../_images/designpatterns-singleton-solution.png
from __future__ import annotations
from typing import Any


class ConfigManager:
    settings: dict[str, Any]
    instance: ConfigManager | None = None

    def __init__(self) -> None:
        self.settings = {}

    @classmethod
    def get_instance(cls) -> ConfigManager:
        if not cls.instance:
            cls.instance = super().__new__(cls)
            cls.instance.__init__()
        return cls.instance

    def set(self, key: str, value: Any) -> None:
        self.settings[key] = value

    def get(self, key: str) -> Any:
        return self.settings.pop(key)


if __name__ == '__main__':
    manager = ConfigManager.get_instance()
    manager.set('name', 'Mark')

    other = ConfigManager.get_instance()
    print(other.get('name'))
    # Mark

11.1.4. Use Case - 0x01

class Singleton:
    instance = None

    @classmethod
    def get_instance(cls):
        if not cls.instance:
            cls.instance = ...
        return cls.instance


# Creating first instance for the first time
first = Singleton.get_instance()

# Connecting for the second time
# Will use existing instance
second = Singleton.get_instance()

11.1.5. Use Case - 0x02

class Singleton:
    instance = None

    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            cls.instance = super().__new__(*args, **kwargs)
        obj = cls.instance
        obj.__init__()
        return obj


class MyClass(Singleton):
    pass

11.1.6. Use Case - 0x03

class Database:
    connection = None

    @classmethod
    def connect(cls):
        if not cls.connection:
            print('Establishing connection...')
            cls.connection = ...
        return cls.connection


# Connecting for the first time
# Will establish new connection
first = Database.connect()

# Connecting for the second time
# Will use existing connection to the DB
# The same handle as `first`
second = Database.connect()

11.1.7. Use Case - 0x04

class Singleton(type):
    instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls.instances:
            cls.instances[cls] = super().__call__(*args, **kwargs)
        return cls.instances[cls]


class MyClass(metaclass=Singleton):
    pass

11.1.8. Assignments