python OOP ereditarietà classi-astratte polimorfismo

OOP Avanzata: Ereditarietà, Classi Astratte e Polimorfismo

Obiettivi della pillola

In questa seconda parte approfondita imparerai a:

  • Definire gerarchie di classi con l’Ereditarietà.
  • Usare le Classi Astratte per imporre regole alle sottoclassi.
  • Utilizzare i Metodi Statici per utility slegate dall’istanza.
  • Implementare l’Overloading degli Operatori per confrontare oggetti (es. <, ==).

Classi Astratte ed Ereditarietà

Spesso vogliamo definire un “modello base” che non può essere usato direttamente, ma serve solo come fondamenta per classi più specifiche.

Il modulo abc (Abstract Base Classes)

Immaginiamo di voler gestire sia indirizzi IPv4 che IPv6. Creiamo una classe genitore IPAddress che obbliga i figli a implementare certi metodi.

Gerarchia delle classi:

from abc import ABC, abstractmethod

# Classe Astratta (eredita da ABC)
class IPAddress(ABC):
    def __init__(self, address):
        self.address = address

    # Metodo Astratto: i figli DEVONO implementarlo
    @abstractmethod
    def to_binary(self):
        pass

    # Metodo Concreto: i figli lo ereditano gia pronto
    def __str__(self):
        return f"IP: {self.address}"

# Sottoclasse Concreta
class IPv4(IPAddress):
    def to_binary(self):
        # Logica specifica per IPv4
        ottetti = self.address.split('.')
        binario = [format(int(x), '08b') for x in ottetti]
        return "".join(binario)

Se provassi a istanziare IPAddress direttamente o a creare una sottoclasse senza to_binary, Python solleverebbe un errore.

Metodi Statici (@staticmethod)

A volte serve una funzione che logicamente appartiene alla classe, ma che non ha bisogno di accedere a self (cioè ai dati dell’istanza). Si usano spesso per funzioni di validazione o utility.

class IPv4(IPAddress):
    # ... metodi precedenti ...

    @staticmethod
    def is_valid_format(ip_str):
        """Controlla se una stringa sembra un IPv4 valido."""
        parts = ip_str.split('.')
        if len(parts) != 4:
            return False
        for p in parts:
            if not p.isdigit() or not (0 <= int(p) <= 255):
                return False
        return True

# Possiamo chiamarlo SENZA creare un oggetto!
print(IPv4.is_valid_format("192.168.1.1"))  # True
print(IPv4.is_valid_format("300.0.0.1"))    # False

Overloading degli Operatori e Comparatori

In Python non esiste l’overloading dei metodi (stesso nome, parametri diversi) come in Java, ma è potentissimo l’Overloading degli Operatori. Possiamo ridefinire come gli oggetti reagiscono agli operatori standard (+, -, ==, <) usando i Magic Methods.

Comparazione (__eq__, __lt__)

Vogliamo poter ordinare una lista di IP o chiedere if ip1 > ip2.

class IPv4(IPAddress):
    # ...

    def __eq__(self, other):
        """Definisce il comportamento dell'operatore =="""
        if not isinstance(other, IPv4):
            return False
        return self.address == other.address

    def __lt__(self, other):
        """Definisce il comportamento dell'operatore < (Less Than)"""
        # Trucco: confrontiamo i valori interi o le stringhe binarie
        return self.to_binary() < other.to_binary()

# Utilizzo
ip_a = IPv4("10.0.0.1")
ip_b = IPv4("192.168.1.1")

if ip_a < ip_b:
    print(f"{ip_a} viene prima di {ip_b}")

# Ordinamento automatico nelle liste!
lista_ip = [ip_b, ip_a]
lista_ip.sort() # Usa automaticamente __lt__
print([str(ip) for ip in lista_ip])

Overloading Operatori Matematici

Possiamo anche definire l’addizione. Ad esempio, sommare un intero a un IP per ottenere l’IP successivo.

    def __add__(self, n):
        """Gestisce: nuovo_ip = oggetto_ip + intero"""
        # 1. Converti IP in intero
        # 2. Somma n
        # 3. Riconverti in stringa e restituisci nuovo oggetto
        # (Implementazione lasciata per esercizio)
        pass

Riepilogo Concettuale

  • Ereditarietà: class B(A). “B è un tipo di A”. Serve a non riscrivere codice comune.
  • Classe Astratta: Una classe “incompleta” che definisce le regole per i figli.
  • Metodo Statico: Funzione dentro una classe che non usa self.
  • Overloading: Dare nuovi significati agli operatori +, ==, < per i nostri oggetti.

Esercitazione Avanzata

Estendi la classe IPv4 creata sopra:

  1. Implementa __add__: Fai in modo che IPv4("192.168.1.1") + 2 restituisca un nuovo oggetto IPv4("192.168.1.3").
  2. Implementa __sub__: La differenza tra due IP deve restituire il numero di host tra di essi (un intero).
  3. Crea una classe Network che accetta un IP e una maschera, e usa un metodo per generare tutti gli IP utilizzabili (usando il tuo operatore +).