📚 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
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
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
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! 🚀