6.1 Ereditarietà — è facile
L'ereditarietà è un concetto fondamentale della programmazione orientata agli oggetti (OOP), che permette a una classe (detta figlia o sottoclasse) di ereditare campi e metodi di un'altra classe (detta padre o superclasse).
Questo approccio permette di creare classi più generali e riutilizzare il codice, migliorando l'organizzazione e la mantenibilità del codice.
Perché serve l'ereditarietà?
Supponiamo che tu debba scrivere un certo codice e decidi di farlo sotto forma di classe. Poi scopri che nel tuo progetto esiste già una classe che fa quasi tutto ciò che ti serve nella tua classe. Puoi semplicemente copiare il codice di questa classe nella tua e utilizzarlo a tuo piacimento.
Oppure puoi "come se copiarlo". Puoi dichiarare quella classe come padre della tua classe e Python aggiungerà alla tua classe il comportamento della classe padre.
Immagina di essere la natura e di voler creare un Cane. Cosa sarà più veloce: creare un cane a partire da un batterio in miliardi di anni o addomesticare un lupo in 200 mila anni?
Esempio di ereditarietà di base
Supponiamo che tu abbia una classe padre Animal con il campo name:
class Animal:
def __init__(self, name):
self.name = name
Vogliamo creare 2 classi figlie per essa — Dog e Cat, e aggiungere a entrambe il metodo speak:
class Dog(Animal):
def speak(self):
return f"{self.name} dice Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} dice Meow!"
Nell'esempio sopra, le classi figlie Dog e Cat ereditano da Animal e aggiungono il metodo speak.
Classe Dog:
- Eredita l'attributo
namedaAnimal. - Aggiunge il metodo
speakche restituisce una stringa specifica per i cani.
Classe Cat:
- Eredita l'attributo
namedaAnimal. - Aggiunge il metodo
speakche restituisce una stringa specifica per i gatti.
La versione finale del codice appare così:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def speak(self):
return f"{self.name} dice Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} dice Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Uscita: Buddy dice Woof!
print(cat.speak()) # Uscita: Whiskers dice Meow!
In questo esempio, Animal è la classe padre, mentre Dog e Cat sono classi figlie. Le classi figlie ereditano l'attributo name e il metodo __init__ dalla classe padre Animal, ma aggiungono metodi speak.
6.2 Sotto il cofano dell'ereditarietà
Se hai aggiunto un genitore alla tua classe, è molto simile a come se avessi copiato il codice della classe genitore nella tua classe.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name):
super().__init__(name) # Chiamata al costruttore della classe genitore
def speak(self):
return f"{self.name} dice Woof!"
class Cat(Animal):
def __init__(self, name):
super().__init__(name) # Chiamata al costruttore della classe genitore
def speak(self):
return f"{self.name} dice Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Uscita: Buddy dice Woof!
print(cat.speak()) # Uscita: Whiskers dice Meow!
Non è una descrizione esatta, ma se non hai mai avuto a che fare con il concetto di ereditarietà, puoi per ora pensarla in questo modo. Dopo aggiungeremo altri dettagli.
6.3 Gerarchia dell'ereditarietà
Molto spesso, quando si progetta un modello complesso di un grande gruppo di classi, ci si può imbattere in tutta una gerarchia di ereditarietà.
Per esempio, hai una classe Animal — è la classe base per tutti gli animali:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("La sottoclasse deve implementare il metodo astratto")
Abbiamo anche aggiunto un metodo speak, ma poiché un animale astratto non parla, questo metodo solleva solo un'eccezione NotImplementedError — è una pratica standard.
Successivamente, aggiungiamo classi intermedie che corrispondono a categorie di animali: Mammal — sono i mammiferi e Bird per gli uccelli.
class Mammal(Animal):
def __init__(self, name, fur_color):
super().__init__(name) # Chiamata al costruttore della classe genitore
self.fur_color = fur_color
class Bird(Animal):
def __init__(self, name, wing_span):
super().__init__(name) # Chiamata al costruttore della classe genitore
self.wing_span = wing_span
def fly(self):
return f"{self.name} vola con un'apertura alare di {self.wing_span} metri."
E infine, solo alla fase finale appaiono classi di specifiche specie animali:
class Dog(Mammal):
def speak(self):
return f"{self.name} dice Woof!"
class Cat(Mammal):
def speak(self):
return f"{self.name} dice Meow!"
class Parrot(Bird):
def speak(self):
return f"{self.name} dice Squawk!"
Con questi solitamente lavora il codice finale:
animals = [Dog("Buddy", "marrone"), Cat("Whiskers", "bianco"), Parrot("Polly", 0.5)]
for animal in animals:
print(animal.speak())
print(f"{dog.name} ha il pelo {dog.fur_color}.") # Uscita: Buddy ha il pelo marrone.
print(f"{cat.name} ha il pelo {cat.fur_color}.") # Uscita: Whiskers ha il pelo bianco.
print(parrot.fly()) # Uscita: Polly vola con un'apertura alare di 0.5 metri.
Anche se tecnicamente non ci sono divieti nel creare gerarchie con decine di antenati, è importante ricordare che senza necessità è meglio attenersi alla semplicità. Nella semplicità c'è forza.
GO TO FULL VERSION