Introduzione
In Python, ereditarietà e polimorfismo sono due concetti fondamentali della programmazione orientata agli oggetti. L’ereditarietà permette di creare nuove classi a partire da classi esistenti, riutilizzando attributi e comportamenti. Il polimorfismo, invece, consente a oggetti di classi diverse di rispondere allo stesso messaggio in modo differente, purché espongano un’interfaccia compatibile.
In questo tutorial scegliamo un sotto-argomento molto pratico: la progettazione di una gerarchia di classi per un sistema di notifiche. È un caso reale e utile perché mostra come modellare oggetti diversi con un comportamento comune, ma con implementazioni specifiche. Vedremo come definire una classe base, come specializzarla e come sfruttare il polimorfismo per inviare notifiche senza preoccuparsi del tipo concreto dell’oggetto.
L’obiettivo non è solo scrivere codice che “funziona”, ma imparare a progettare in modo pulito, leggibile e facile da estendere.
Codice completo
from dataclasses import dataclass
from typing import List
class Notifica:
"""Classe base per tutte le notifiche."""
def __init__(self, destinatario: str):
self.destinatario = destinatario
def prepara_contenuto(self) - Spiegazione
La classe Notifica rappresenta il contratto comune per tutte le notifiche. Contiene un attributo condiviso, destinatario, e due metodi:
- prepara_contenuto(): definisce il contenuto specifico della notifica.
- invia(): gestisce il comportamento comune di invio.
Il punto chiave è che prepara_contenuto() non ha un’implementazione concreta nella classe base. Invece di lasciare un metodo vuoto, solleviamo NotImplementedError. Questo segnala chiaramente che le sottoclassi devono fornire la propria versione.
Le classi NotificaEmail, NotificaSMS e NotificaPush ereditano da Notifica. Ognuna aggiunge attributi specifici:
- NotificaEmail: oggetto e testo.
- NotificaSMS: solo testo.
- NotificaPush: titolo e testo.
Tutte implementano prepara_contenuto() in modo differente. Questa è una forma concreta di polimorfismo: il metodo ha lo stesso nome, ma produce risultati diversi in base al tipo reale dell’oggetto.
La classe CentroNotifiche riceve una lista di oggetti di tipo Notifica. Il metodo invia_tutte() scorre la lista e chiama invia() su ciascun elemento. Qui non serve sapere se l’oggetto è un’email, un SMS o una push notification: il codice funziona comunque, perché ogni oggetto “sa” come preparare il proprio contenuto.
Questo è uno dei vantaggi più importanti del polimorfismo: il codice cliente resta semplice e stabile, mentre le differenze di comportamento restano incapsulate nelle classi specializzate.
Notare anche l’uso di super().__init__(destinatario) nelle sottoclassi. Serve a riutilizzare l’inizializzazione comune della classe base, evitando duplicazione.
Best practice
- Definisci una responsabilità chiara per la classe base: la classe padre deve rappresentare un concetto generale, non contenere logiche troppo specifiche.
- Usa metodi comuni e comportamenti specializzati: metodi come
invia()possono essere condivisi, mentreprepara_contenuto()può variare nelle sottoclassi. - Evita gerarchie troppo profonde: più la catena di ereditarietà è lunga, più il codice diventa difficile da mantenere.
- Preferisci nomi espliciti:
NotificaPushè più chiaro di un nome generico comePush. - Fai attenzione al “fragile base class problem”: modifiche nella classe base possono rompere sottoclassi esistenti. Mantieni l’interfaccia stabile.
- Usa il polimorfismo per semplificare il codice cliente: se hai molti
if isinstance(...), probabilmente puoi spostare la logica nelle classi. - Documenta i metodi da sovrascrivere: se una sottoclasse deve implementare un metodo, scrivilo chiaramente nella docstring.
Riepilogo
In questo tutorial abbiamo visto come usare ereditarietà e polimorfismo per modellare un sistema di notifiche. La classe base Notifica definisce il comportamento comune, mentre le sottoclassi specializzano la preparazione del contenuto. Il risultato è un design più pulito, estendibile e facile da leggere.
I concetti chiave da ricordare sono:
- Ereditarietà: una sottoclasse riusa e specializza una classe base.
- Polimorfismo: oggetti diversi rispondono allo stesso metodo in modo differente.
- Separazione delle responsabilità: la logica comune resta nella base, quella specifica nelle sottoclassi.
- Estendibilità: aggiungere un nuovo tipo di notifica richiede poca modifica al codice esistente.
Se vuoi migliorare ulteriormente il progetto, puoi aggiungere validazione dei dati, gestione degli errori, logging e persino un sistema di priorità per ordinare le notifiche prima dell’invio.
Approfondisci con risorse ufficiali
- Documentazione ufficiale Python su classi: utile per ripassare sintassi, ereditarietà e metodi speciali.
- Documentazione ufficiale su
dataclasses: perfetta per creare classi semplici e leggibili. - PEP 8: guida ufficiale sullo stile del codice Python, importante per scrivere classi pulite e coerenti.
- Documentazione ufficiale su
typing: utile per annotare liste di oggetti e migliorare la leggibilità del codice.
Studiare questi argomenti ti aiuterà a usare ereditarietà e polimorfismo in modo più consapevole, soprattutto in progetti reali dove la manutenibilità conta quanto la correttezza del codice.
