Ereditarietà e polimorfismo in Python: progettare gerarchie di classi con metodi dinamici

by theArchitect
SHARE
Ereditarietà e polimorfismo in Python: progettare gerarchie di classi con metodi dinamici
© Guida-HTML5.it

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, mentre prepara_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 come Push.
  • 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.

SHARE