Lezione 8 – Programmazione a Oggetti (OOP)

🐍 Programmazione ad Oggetti (OOP) in Python

Scopri come strutturare il tuo codice in modo modulare, riutilizzabile e professionale con la programmazione orientata agli oggetti

📚 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:

Python
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

1

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.

💡 Analogia: La classe è come il progetto di una casa, mentre l’oggetto è la casa vera e propria costruita seguendo quel progetto.
Python
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:

Esercizio
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:

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.

2

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)
Python
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:

Esercizio
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:

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:

Sfida
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

3

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.

💡 Vantaggi: Evita la duplicazione del codice e crea una gerarchia logica tra le classi. Puoi specializzare una classe generale aggiungendo nuove funzionalità.
Python
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)

4

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.

💡 Significato: "Poli" = molte, "morfismo" = forme. Un'unica interfaccia per molte implementazioni diverse.
Python
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! 🚀


📚 Approfondisci con Esempi Pratici

Torna in alto