Menu
Coddy logo textTech

Patron Singleton

Fait partie de la section Object Oriented Programming du Journey Python de Coddy — leçon 45 sur 64.

Le patron de conception Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci. Ceci est utile pour des ressources telles que des connexions à une base de données ou des paramètres de configuration.

Voici une implémentation de base du Singleton :

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

La méthode __new__ contrôle la création d'objets. Elle vérifie si une instance existe déjà avant d'en créer une nouvelle.

Créez deux instances de la classe Singleton :

singleton1 = Singleton()
singleton2 = Singleton()

Vérifiez si les deux variables font référence au même objet :

print(singleton1 is singleton2)  # True
print(id(singleton1))            # Même adresse mémoire
print(id(singleton2))            # Même adresse mémoire

Voici un exemple plus pratique avec une connexion à une base de données :

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}"

# First access creates the connection
db1 = DatabaseConnection()
print(db1.connection)

# Second access reuses the same connection
db2 = DatabaseConnection()
print(db2.connection)

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

Créez un gestionnaire de configuration en utilisant le 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 - mêmes paramètres

Résultat :

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

Point clé : Le pattern Singleton utilise __new__ pour contrôler la création d'objets, garantissant qu'une seule instance existe. Utilisez-le pour les ressources qui ne devraient avoir qu'une seule copie dans toute votre application, comme les connexions aux bases de données, les loggers ou les gestionnaires de configuration. N'oubliez pas que toutes les variables pointant vers un Singleton font référence au même objet en mémoire.

challenge icon

Défi

Facile

Dans ce défi, vous implémenterez le patron de conception Singleton pour une classe de connexion à une base de données.

  • Modifiez databaseconnection.py pour implémenter le patron Singleton en suivant les commentaires TODO
  • Le fichier driver.py contient des scénarios de test approfondis et ne doit pas être modifié

Aide-mémoire

Le patron de conception Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci.

Implémentation de base du Singleton utilisant __new__ :

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

La méthode __new__ contrôle la création d'objets en vérifiant si une instance existe déjà avant d'en créer une nouvelle.

Test des instances Singleton :

singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True - même objet

Exemple pratique avec une connexion à une base de données :

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}"

Exemple de gestionnaire de configuration :

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)

Utilisez le Singleton pour les ressources qui ne devraient avoir qu'une seule copie dans toute votre application, comme les connexions aux bases de données, les loggers ou les gestionnaires de configuration.

Essayez vous-même

from databaseconnection import DatabaseConnection

# Gestionnaire de cas de test complet
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # Devrait afficher True

elif test_case == "connect_state":
    db = DatabaseConnection()
    print(f"Initial connected state: {db.connected}")  # Devrait afficher False
    db.connect()
    print(f"After connect: {db.connected}")  # Devrait afficher True

elif test_case == "disconnect_state":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()
    print(f"After disconnect: {db.connected}")  # Devrait afficher False

elif test_case == "multiple_instances_same_state":
    db1 = DatabaseConnection()
    db1.connect()
    db2 = DatabaseConnection()
    print(f"db2 connected state: {db2.connected}")  # Devrait afficher True

elif test_case == "host_value":
    db = DatabaseConnection()
    print(f"Default host: {db.host}")  # Devrait afficher "localhost"
    db.host = "new-server"
    db2 = DatabaseConnection()
    print(f"New instance host: {db2.host}")  # Devrait afficher "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}")  # Devrait afficher True
    print(f"Host value: {db1.host}")  # Devrait afficher "custom-host"

elif test_case == "connect_message":
    db = DatabaseConnection()
    db.connect()  # Devrait afficher "Connected to database at localhost"

elif test_case == "disconnect_message":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()  # Devrait afficher "Disconnected from database"

elif test_case == "attribute_modification":
    db1 = DatabaseConnection()
    db1.port = 3306
    db2 = DatabaseConnection()
    print(f"db2 has port attribute: {hasattr(db2, 'port')}")  # Devrait afficher True
    print(f"db2 port value: {db2.port}")  # Devrait afficher 3306

elif test_case == "reset_connection":
    db1 = DatabaseConnection()
    db1.connect()
    print(f"Connected state: {db1.connected}")  # Devrait afficher True
    db2 = DatabaseConnection()
    print(f"New instance connected state: {db2.connected}")  # Devrait afficher True
    db2.disconnect()
    db3 = DatabaseConnection()
    print(f"After disconnect, new instance state: {db3.connected}")  # Devrait afficher False

elif test_case == "multiple_connects":
    db = DatabaseConnection()
    db.connect()
    db.connect()
    db.connect()
    print(f"Connected state after multiple connects: {db.connected}")  # Devrait afficher True
    db.disconnect()
    print(f"Connected state after disconnect: {db.connected}")  # Devrait afficher False

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # Affichera toujours "Connected to database at localhost"
    # La méthode connect a un message codé en dur qui n'utilise pas l'attribut host
    print("Message still shows localhost: True")
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming