📚 Perché usare la Programmazione ad Oggetti?
📦 Organizzazione
Il codice è suddiviso in moduli facilmente gestibili e comprensibili
♻️ Riutilizzabilità
Le classi possono essere riutilizzate in progetti diversi senza riscrivere codice
🔧 Manutenibilità
Facilita la modifica del codice senza intaccare altre parti del programma
🎭 Astrazione
Permette di nascondere i dettagli di implementazione, esponendo solo le funzionalità necessarie
🔥 Teoria: Classi e Oggetti
In Python, possiamo creare classi e oggetti per modellare il mondo reale. Una classe è come un progetto, mentre un oggetto è la realizzazione concreta di quel progetto.
💡 Esempio Base:
class Persona:
def __init__(self, nome, età):
self.nome = nome
self.età = età
def saluta(self):
print(f"Ciao, sono {self.nome}")
# Creazione di un oggetto
luca = Persona("Luca", 25)
luca.saluta()
# Output: Ciao, sono Luca
Classe e Oggetto
Una classe è un modello (template) che definisce la struttura e il comportamento degli oggetti. Un oggetto è un’istanza concreta di quella classe, con i propri dati specifici.
class Persona:
def __init__(self, nome, eta):
self.nome = nome # Attributo pubblico
self.eta = eta # Attributo pubblico
def saluta(self):
return f"Ciao, mi chiamo {self.nome} e ho {self.eta} anni."
# Creazione di un oggetto (istanza della classe)
persona1 = Persona("Alice", 25)
print(persona1.saluta())
# Output: Ciao, mi chiamo Alice e ho 25 anni.
📋 Quando usarlo?
- Quando vuoi creare strutture dati complesse con metodi associati
- Per rappresentare entità del mondo reale (Utente, Auto, Studente, ecc.)
- Quando hai bisogno di creare più istanze con la stessa struttura
✍ Esercitazioni Guidate
🔹 Esercizio 1: Classe Studente
Crea una classe Studente con attributi nome, età e media_voti.
✅ Codice da completare:
class Studente:
def __init__(self, nome, età, media_voti):
self.nome = ____
self.età = ____
self.media_voti = ____
def info(self):
print(f"{self.nome}, {self.età} anni, media voti: {self.media_voti}")
studente1 = Studente("Anna", 17, 8.5)
studente1.info()
✅ Soluzione:
class Studente:
def __init__(self, nome, età, media_voti):
self.nome = nome
self.età = età
self.media_voti = media_voti
def info(self):
print(f"{self.nome}, {self.età} anni, media voti: {self.media_voti}")
studente1 = Studente("Anna", 17, 8.5)
studente1.info()
# Output: Anna, 17 anni, media voti: 8.5
🔹 Sfida Extra:
Aggiungi un metodo aumenta_media(valore) per aumentare la media dei voti dello studente.
Incapsulamento
L’incapsulamento è il principio che protegge lo stato interno di un oggetto, permettendo l’accesso ai dati solo attraverso metodi dedicati. Questo previene modifiche accidentali e mantiene l’integrità dei dati.
🔐 Livelli di accesso in Python:
- Pubblico: accessibile ovunque (
self.nome) - Protetto: accessibile solo dalla classe e dalle sottoclassi (
self._nome) - Privato: offuscato e non accessibile direttamente (
self.__nome)
class ContoBancario:
def __init__(self, titolare, saldo):
self.titolare = titolare # Attributo pubblico
self.__saldo = saldo # Attributo privato (protetto)
def deposita(self, importo):
if importo > 0:
self.__saldo += importo
return f"✅ Deposito di {importo}€ effettuato. Nuovo saldo: {self.__saldo}€"
else:
return "❌ Importo non valido."
def preleva(self, importo):
if 0 < importo <= self.__saldo:
self.__saldo -= importo
return f"✅ Prelievo di {importo}€ effettuato. Saldo residuo: {self.__saldo}€"
else:
return "❌ Fondi insufficienti o importo non valido."
def mostra_saldo(self):
return f"💰 Saldo disponibile: {self.__saldo}€"
# Utilizzo
conto = ContoBancario("Mario Rossi", 1000)
print(conto.deposita(500))
print(conto.preleva(200))
print(conto.mostra_saldo())
# Tentativo di accesso diretto (NON funziona)
# print(conto.__saldo) # Genera errore!
📋 Quando usarlo?
- Per proteggere dati sensibili (password, saldo bancario, informazioni personali)
- Quando vuoi controllare come i dati vengono modificati
- Per evitare modifiche accidentali alle variabili interne
✍ Esercitazioni Guidate
🔹 Esercizio 2: Classe Auto
Crea una classe Auto con marca, modello e velocità massima.
✅ Codice da completare:
class Auto:
def __init__(self, marca, modello, velocita_max):
self.marca = ____
self.modello = ____
self.velocita_max = ____
def info(self):
print(f"{self.marca} {self.modello} - Velocità massima: {self.velocita_max} km/h")
✅ Soluzione:
class Auto:
def __init__(self, marca, modello, velocita_max):
self.marca = marca
self.modello = modello
self.velocita_max = velocita_max
def info(self):
print(f"{self.marca} {self.modello} - Velocità massima: {self.velocita_max} km/h")
# Test
auto1 = Auto("Fiat", "500", 160)
auto1.info()
# Output: Fiat 500 - Velocità massima: 160 km/h
🔥 Sfida Extra: Garage Virtuale
🎯 Obiettivi della Sfida:
- Crea una lista di oggetti Auto
- Aggiungi un metodo per accelerare
- Stampa la velocità attuale di ogni auto
✅ Struttura suggerita:
class Auto:
def __init__(self, marca, modello, velocita_max):
self.marca = marca
self.modello = modello
self.velocita = 0
self.velocita_max = velocita_max
def accelera(self, incremento):
self.velocita = min(self.velocita + incremento, self.velocita_max)
# Crea il garage
garage = [Auto("Fiat", "500", 160), Auto("Ferrari", "F8", 340)]
# Accelera ogni auto
for auto in garage:
auto.accelera(50)
print(f"{auto.marca} {auto.modello}: {auto.velocita} km/h")
💡 Suggerimenti:
- Il metodo
min()assicura che la velocità non superi il massimo - Prova ad aggiungere anche un metodo
frena() - Considera di creare un metodo
stato_auto()che mostri tutte le info
Ereditarietà
L'ereditarietà consente di creare una nuova classe che eredita attributi e metodi da un'altra classe esistente. La classe da cui si eredita si chiama classe base (o superclasse), mentre la nuova classe è la sottoclasse.
class Persona:
def __init__(self, nome, eta):
self.nome = nome
self.eta = eta
def saluta(self):
return f"Ciao, mi chiamo {self.nome} e ho {self.eta} anni."
# Studente eredita da Persona
class Studente(Persona):
def __init__(self, nome, eta, scuola):
super().__init__(nome, eta) # Chiama il costruttore della classe base
self.scuola = scuola
def info_scuola(self):
return f"📚 Frequento {self.scuola}."
# Creazione di un oggetto della sottoclasse
studente1 = Studente("Marco", 18, "Liceo Scientifico")
print(studente1.saluta()) # Metodo ereditato ✅
print(studente1.info_scuola()) # Metodo specifico della sottoclasse ✅
📋 Quando usarlo?
- Per evitare duplicazione del codice creando classi più generiche
- Quando un oggetto ha caratteristiche comuni con un altro ma necessita di personalizzazioni
- Per creare gerarchie logiche (es: Animale → Cane, Gatto)
Polimorfismo
Il polimorfismo permette a oggetti di classi diverse di essere trattati allo stesso modo se condividono un'interfaccia comune. In pratica, puoi chiamare lo stesso metodo su oggetti diversi e ottenere comportamenti specifici per ciascuno.
class Animale:
def verso(self):
pass # Metodo da implementare nelle sottoclassi
class Cane(Animale):
def verso(self):
return "🐕 Bau bau!"
class Gatto(Animale):
def verso(self):
return "🐈 Miao!"
class Mucca(Animale):
def verso(self):
return "🐄 Muuu!"
# Uso polimorfico - stesso metodo, comportamenti diversi
animali = [Cane(), Gatto(), Mucca()]
for animale in animali:
print(animale.verso())
# Output:
# 🐕 Bau bau!
# 🐈 Miao!
# 🐄 Muuu!
📋 Quando usarlo?
- Quando più classi devono comportarsi in modo simile ma con implementazioni diverse
- Per scrivere codice più generico che funzioni su tipi diversi senza modifiche
- Per creare sistemi estensibili e flessibili
🎯 Riepilogo dei Concetti Fondamentali
📦 Classe e Oggetto
La struttura base della OOP. La classe definisce il modello, l'oggetto è l'istanza concreta.
🔐 Incapsulamento
Protegge i dati interni controllando l'accesso attraverso metodi specifici.
🧬 Ereditarietà
Riutilizza codice esistente creando classi specializzate da classi generiche.
🎭 Polimorfismo
Permette flessibilità: stesso metodo, comportamenti diversi per ogni classe.
Utilizzando questi principi, puoi scrivere codice più robusto, manutenibile e professionale! 🚀