Menu
Coddy logo textTech

Singleton-Muster

Teil des Abschnitts Object Oriented Programming der Python-Journey von Coddy — Lektion 45 von 64.

Das Singleton-Muster stellt sicher, dass eine Klasse nur eine Instanz hat, und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für Ressourcen wie Datenbankverbindungen oder Konfigurationseinstellungen.

Hier ist eine einfache Singleton-Implementierung:

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

Die __new__-Methode steuert die Objekterzeugung. Sie prüft, ob bereits eine Instanz existiert, bevor sie eine neue erstellt.

Erstellen Sie zwei Instanzen der Singleton-Klasse:

singleton1 = Singleton()
singleton2 = Singleton()

Prüfen Sie, ob beide Variablen auf dasselbe Objekt verweisen:

print(singleton1 is singleton2)  # True
print(id(singleton1))            # Gleiche Speicheradresse
print(id(singleton2))            # Gleiche Speicheradresse

Hier ist ein praxisorientierteres Beispiel mit einer Datenbankverbindung:

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)

Erstellen Sie einen Konfigurationsmanager mit dem Singleton-Muster:

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 - dieselben Einstellungen

Ausgabe:

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

Kernpunkt: Das Singleton-Muster verwendet __new__, um die Objekterstellung zu steuern und sicherzustellen, dass nur eine Instanz existiert. Verwenden Sie es für Ressourcen, von denen es während der gesamten Anwendung nur eine Kopie geben sollte, wie Datenbankverbindungen, Logger oder Konfigurationsmanager. Denken Sie daran, dass alle Variablen, die auf ein Singleton zeigen, auf dasselbe Objekt im Speicher verweisen.

challenge icon

Aufgabe

Einfach

In dieser Challenge implementierst du das Singleton-Entwurfsmuster für eine Datenbankverbindungsklasse.

  • Bearbeite databaseconnection.py, um das Singleton-Muster gemäß den TODO-Kommentaren zu implementieren
  • Die Datei driver.py enthält umfangreiche Testszenarien und sollte nicht geändert werden

Spickzettel

Das Singleton-Muster stellt sicher, dass eine Klasse nur eine Instanz hat, und bietet einen globalen Zugriffspunkt darauf.

Grundlegende Singleton-Implementierung unter Verwendung von __new__:

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

Die Methode __new__ steuert die Objekterzeugung, indem sie prüft, ob bereits eine Instanz existiert, bevor eine neue erstellt wird.

Testen von Singleton-Instanzen:

singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True - dasselbe Objekt

Praktisches Beispiel mit einer Datenbankverbindung:

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

Beispiel für einen Konfigurationsmanager:

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)

Verwenden Sie Singleton für Ressourcen, von denen es während der gesamten Anwendung nur eine Kopie geben sollte, wie Datenbankverbindungen, Logger oder Konfigurationsmanager.

Probier es selbst

from databaseconnection import DatabaseConnection

# Umfassender Testfall-Handler
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # Sollte True ausgeben

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

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

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

elif test_case == "host_value":
    db = DatabaseConnection()
    print(f"Default host: {db.host}")  # Sollte "localhost" ausgeben
    db.host = "new-server"
    db2 = DatabaseConnection()
    print(f"New instance host: {db2.host}")  # Sollte "new-server" ausgeben

elif test_case == "init_once":
    db1 = DatabaseConnection()
    db1.host = "custom-host"
    db2 = DatabaseConnection()
    print(f"Both instances have same host: {db1.host == db2.host}")  # Sollte True ausgeben
    print(f"Host value: {db1.host}")  # Sollte "custom-host" ausgeben

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

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

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

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

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

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # Wird weiterhin "Connected to database at localhost" ausgeben
    # Die connect-Methode hat eine fest codierte Nachricht, die das host-Attribut nicht verwendet
    print("Message still shows localhost: True")
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Object Oriented Programming