Menu
Coddy logo textTech

Паттерн Singleton

Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 45 из 64.

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

Вот базовая реализация Singleton:

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Метод __new__ управляет созданием объектов. Он проверяет, существует ли уже экземпляр, прежде чем создавать новый.

Создайте два экземпляра класса Singleton:

singleton1 = Singleton()
singleton2 = Singleton()

Проверьте, ссылаются ли обе переменные на один и тот же объект:

print(singleton1 is singleton2)  # True
print(id(singleton1))            # Тот же адрес в памяти
print(id(singleton2))            # Тот же адрес в памяти

Вот более практичный пример с подключением к базе данных:

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to MySQL database"
            print("Creating new database connection")
        return cls._instance
    
    def query(self, sql):
        return f"Executing: {sql}"

# Первое обращение создает соединение
db1 = DatabaseConnection()
print(db1.connection)

# Второе обращение повторно использует то же самое соединение
db2 = DatabaseConnection()
print(db2.connection)

print(db1.query("SELECT * FROM users"))
print(db1 is db2)

Создайте менеджер конфигурации, используя Singleton:

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {}
        return cls._instance
    
    def set_setting(self, key, value):
        self.settings[key] = value
    
    def get_setting(self, key):
        return self.settings.get(key)

config1 = Config()
config1.set_setting("debug", True)

config2 = Config()
print(config2.get_setting("debug"))  # True - те же настройки

Вывод:

True
140234567890123
140234567890123
Creating new database connection
Connected to MySQL database
Connected to MySQL database
Executing: SELECT * FROM users
True
True

Ключевой момент: Паттерн Singleton использует __new__ для управления созданием объектов, гарантируя существование только одного экземпляра. Используйте его для ресурсов, которые должны иметь только одну копию во всем приложении, таких как подключения к базе данных, логгеры или менеджеры конфигурации. Помните, что все переменные, указывающие на Singleton, ссылаются на один и тот же объект в памяти.

challenge icon

Задание

Легко

В этом испытании вы реализуете паттерн проектирования Одиночка (Singleton) для класса подключения к базе данных.

  • Отредактируйте databaseconnection.py, чтобы реализовать паттерн Одиночка, следуя комментариям TODO
  • Файл driver.py содержит обширные тестовые сценарии и не должен изменяться

Шпаргалка

Паттерн Одиночка (Singleton) гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.

Базовая реализация Одиночки с использованием __new__:

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Метод __new__ управляет созданием объекта, проверяя, существует ли уже экземпляр, прежде чем создавать новый.

Тестирование экземпляров Одиночки:

singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True - тот же объект

Практический пример с подключением к базе данных:

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to MySQL database"
        return cls._instance
    
    def query(self, sql):
        return f"Executing: {sql}"

Пример менеджера конфигурации:

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {}
        return cls._instance
    
    def set_setting(self, key, value):
        self.settings[key] = value
    
    def get_setting(self, key):
        return self.settings.get(key)

Используйте Одиночку для ресурсов, которые должны иметь только одну копию во всем приложении, таких как подключения к базе данных, логгеры или менеджеры конфигурации.

Попробуйте сами

from databaseconnection import DatabaseConnection

# Комплексный обработчик тестовых случаев
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # Должно вывести True

elif test_case == "connect_state":
    db = DatabaseConnection()
    print(f"Initial connected state: {db.connected}")  # Должно вывести False
    db.connect()
    print(f"After connect: {db.connected}")  # Должно вывести True

elif test_case == "disconnect_state":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()
    print(f"After disconnect: {db.connected}")  # Должно вывести False

elif test_case == "multiple_instances_same_state":
    db1 = DatabaseConnection()
    db1.connect()
    db2 = DatabaseConnection()
    print(f"db2 connected state: {db2.connected}")  # Должно вывести True

elif test_case == "host_value":
    db = DatabaseConnection()
    print(f"Default host: {db.host}")  # Должно вывести "localhost"
    db.host = "new-server"
    db2 = DatabaseConnection()
    print(f"New instance host: {db2.host}")  # Должно вывести "new-server"

elif test_case == "init_once":
    db1 = DatabaseConnection()
    db1.host = "custom-host"
    db2 = DatabaseConnection()
    print(f"Both instances have same host: {db1.host == db2.host}")  # Должно вывести True
    print(f"Host value: {db1.host}")  # Должно вывести "custom-host"

elif test_case == "connect_message":
    db = DatabaseConnection()
    db.connect()  # Должно вывести "Connected to database at localhost"

elif test_case == "disconnect_message":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()  # Должно вывести "Disconnected from database"

elif test_case == "attribute_modification":
    db1 = DatabaseConnection()
    db1.port = 3306
    db2 = DatabaseConnection()
    print(f"db2 has port attribute: {hasattr(db2, 'port')}")  # Должно вывести True
    print(f"db2 port value: {db2.port}")  # Должно вывести 3306

elif test_case == "reset_connection":
    db1 = DatabaseConnection()
    db1.connect()
    print(f"Connected state: {db1.connected}")  # Должно вывести True
    db2 = DatabaseConnection()
    print(f"New instance connected state: {db2.connected}")  # Должно вывести True
    db2.disconnect()
    db3 = DatabaseConnection()
    print(f"After disconnect, new instance state: {db3.connected}")  # Должно вывести False

elif test_case == "multiple_connects":
    db = DatabaseConnection()
    db.connect()
    db.connect()
    db.connect()
    print(f"Connected state after multiple connects: {db.connected}")  # Должно вывести True
    db.disconnect()
    print(f"Connected state after disconnect: {db.connected}")  # Должно вывести False

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # Все равно выведет "Connected to database at localhost"
    # Метод connect имеет жестко заданное сообщение, которое не использует атрибут host
    print("Message still shows localhost: True")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming