8.1. Singleton

  • EN: Singleton

  • PL: Singleton

  • Type: object

8.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

8.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'

8.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

8.1.4. Use Case - 0x01

from typing import Self


class Database:
    connection: Self | None = None

    @classmethod
    def connect(cls):
        if not cls.connection:
            cls.connection = ...  # connect to database
        return cls.connection


db1 = Database.connect()
db2 = Database.connect()

db1 is db2
# True

8.1.5. Use Case - 0x02

from typing import Self


class Singleton:
    instance: Self | None = None

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls)
            cls.instance.__init__(*args, **kwargs)
        return cls.instance


class Database(Singleton):
    pass



db1 = Database()
db2 = Database()

db1 is db2
# True

8.1.6. Use Case - 0x03

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 Database(metaclass=Singleton):
    pass

class Settings(metaclass=Singleton):
    pass


db1 = Database()
db2 = Database()

db1 is db2
# True

8.1.7. Assignments

Code 8.38. Solution
"""
* Assignment: DesignPatterns Creational SingletonSettings
* Complexity: easy
* Lines of code: 7 lines
* Time: 5 min

English:
    1. Create singleton class `Settings`
    2. Use `get_instance()` classmethod
    3. Run doctests - all must succeed

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> result_a = Settings.get_instance()
    >>> result_b = Settings.get_instance()

    >>> result_a is result_b
    True
"""
from typing import Self


class Settings:
    pass


Code 8.39. Solution
"""
* Assignment: DesignPatterns Creational SingletonDatabaseConnection
* Complexity: easy
* Lines of code: 7 lines
* Time: 5 min

English:
    1. Create singleton class `Database`
    2. Use `connect()` classmethod
    3. Run doctests - all must succeed

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> result_a = Database.connect()
    >>> result_b = Database.connect()

    >>> result_a is result_b
    True
"""
from typing import Self


class Database:
    pass


Code 8.40. Solution
"""
* Assignment: DesignPatterns Creational SingletonQueue
* Complexity: medium
* Lines of code: 7 lines
* Time: 5 min

English:
    1. Create singleton class `Singleton`
    2. Use `__new__()` object constructor
    3. Run doctests - all must succeed

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> class Queue(Singleton):
    ...     pass

    >>> result_a = Queue()
    >>> result_b = Queue()

    >>> result_a is result_b
    True
"""
from typing import Self


class Singleton:
    pass


Code 8.41. Solution
"""
* Assignment: DesignPatterns Creational SingletonLogger
* Complexity: medium
* Lines of code: 7 lines
* Time: 5 min

English:
    1. Create singleton class `Singleton`
    2. Use `Metaclass`
    3. Run doctests - all must succeed

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> class Logger(metaclass=Singleton):
    ...     pass

    >>> result_a = Logger()
    >>> result_b = Logger()

    >>> result_a is result_b
    True
"""
from typing import Self


class Singleton(type):
    pass