Menu
Coddy logo textTech

Modello Singleton

Fa parte della sezione Programmazione orientata agli oggetti del percorso Python di Coddy. Lezione 45 di 64.

Il pattern Singleton garantisce che una classe abbia una sola istanza e fornisce un punto di accesso globale a essa. È utile per risorse come le connessioni al database o le impostazioni di configurazione.

Ecco un'implementazione di base del Singleton:

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

Il metodo __new__ controlla la creazione degli oggetti. Verifica se esiste già un'istanza prima di crearne una nuova.

Crea due istanze della classe Singleton:

singleton1 = Singleton()
singleton2 = Singleton()

Verifica se entrambe le variabili fanno riferimento allo stesso oggetto:

print(singleton1 is singleton2)  # True
print(id(singleton1))            # Stesso indirizzo di memoria
print(id(singleton2))            # Stesso indirizzo di memoria

Ecco un esempio più pratico con una connessione al database:

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

# Il primo accesso crea la connessione
db1 = DatabaseConnection()
print(db1.connection)

# Il secondo accesso riutilizza la stessa connessione
db2 = DatabaseConnection()
print(db2.connection)

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

Crea un gestore di configurazione usando il 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 - stesse impostazioni

Output:

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

Punto chiave: il pattern Singleton usa __new__ per controllare la creazione degli oggetti, assicurando che esista una sola istanza. Usalo per le risorse di cui dovrebbe esistere una sola copia in tutta la tua applicazione, come le connessioni al database, i logger o i gestori della configurazione. Ricorda che tutte le variabili che puntano a un Singleton fanno riferimento allo stesso oggetto in memoria.

challenge icon

Sfida

Facile

In questa sfida, implementerai il pattern di progettazione Singleton per una classe di connessione al database.

  • Modifica databaseconnection.py per implementare il pattern Singleton seguendo i commenti TODO
  • Il file driver.py contiene scenari di test approfonditi e non deve essere modificato

Provalo tu

from databaseconnection import DatabaseConnection

# Gestore completo dei casi di test
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # Dovrebbe stampare True

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

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

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

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

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

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

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

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

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

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # Stamperà comunque "Connected to database at localhost"
    # Il metodo connect contiene un messaggio predefinito che non usa l'attributo host
    print("Message still shows localhost: True")
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Python online