☕ Lezione 4

Incapsulamento e Modificatori di Accesso

Incapsulamento: Proteggere i Dati

🎯 Obiettivi della lezione

💡 Cos'è l'Incapsulamento?

L'incapsulamento è uno dei pilastri fondamentali della OOP.

Significa nascondere i dettagli interni di un oggetto e fornire un'interfaccia controllata per accedervi.

È come avere una cassaforte: dall'esterno non si vede cosa c'è dentro, e si può accedervi solo in modo controllato.

❌ Problema: Accesso Diretto

Finora abbiamo sempre acceduto direttamente agli attributi:

public class ContoBancario {
    String intestatario;
    double saldo;
    
    public ContoBancario(String intestatario, double saldo) {
        this.intestatario = intestatario;
        this.saldo = saldo;
    }
}

// Nel main:
ContoBancario conto = new ContoBancario("Mario", 1000);
conto.saldo = -500;  // PROBLEMA! Posso mettere valori non validi!
System.out.println(conto.saldo);  // -500

⚠️ Problemi di questo approccio:

  • Chiunque può modificare direttamente i dati
  • Non possiamo controllare se i valori sono validi
  • Difficile fare debug (non sappiamo chi ha modificato cosa)
  • Se cambiamo il nome di un attributo, dobbiamo modificare tutto il codice

🔐 Modificatori di Accesso

Java fornisce tre modificatori principali per controllare l'accesso:

Modificatore Accesso da Uso tipico
public Ovunque Classi, metodi pubblici
private Solo dalla classe stessa Attributi, metodi interni
protected Dalla classe e sue sottoclassi Per l'ereditarietà (lo vedremo)
(nessuno) Stesso package Accesso a livello di package

🛡️ Rendere gli Attributi Private

La best practice è rendere gli attributi private:

public class ContoBancario {
    // Attributi PRIVATE - non accessibili dall'esterno
    private String intestatario;
    private String numeroConto;
    private double saldo;
    
    public ContoBancario(String intestatario, String numeroConto, double saldoIniziale) {
        this.intestatario = intestatario;
        this.numeroConto = numeroConto;
        // Controllo che il saldo sia valido
        if (saldoIniziale >= 0) {
            this.saldo = saldoIniziale;
        } else {
            this.saldo = 0;
            System.out.println("Saldo iniziale non valido. Impostato a 0.");
        }
    }
    
    // Metodi pubblici per interagire con l'oggetto
    public void deposita(double importo) {
        if (importo > 0) {
            saldo += importo;
            System.out.println("Depositati €" + importo);
        } else {
            System.out.println("Importo non valido!");
        }
    }
    
    public boolean preleva(double importo) {
        if (importo > 0 && saldo >= importo) {
            saldo -= importo;
            System.out.println("Prelevati €" + importo);
            return true;
        } else {
            System.out.println("Operazione non consentita!");
            return false;
        }
    }
    
    public void stampaSaldo() {
        System.out.println("Saldo di " + intestatario + ": €" + saldo);
    }
}
public class TestConto {
    public static void main(String[] args) {
        ContoBancario conto = new ContoBancario("Mario Rossi", "IT001", 1000);
        
        // conto.saldo = -500;  // ERRORE! saldo è private!
        
        conto.stampaSaldo();
        conto.deposita(500);
        conto.preleva(200);
        conto.preleva(2000);  // Non può prelevare, fondi insufficienti
        conto.stampaSaldo();
    }
}
Saldo di Mario Rossi: €1000.0 Depositati €500.0 Prelevati €200.0 Operazione non consentita! Saldo di Mario Rossi: €1300.0

🔧 Getter e Setter

I getter e setter sono metodi pubblici che permettono di leggere e modificare gli attributi privati in modo controllato.

  • Getter: metodo per LEGGERE un valore (get + NomeAttributo)
  • Setter: metodo per MODIFICARE un valore (set + NomeAttributo)

📝 Sintassi dei Getter e Setter

public class Studente {
    // Attributi privati
    private String nome;
    private String cognome;
    private int eta;
    private double mediaVoti;
    
    // Costruttore
    public Studente(String nome, String cognome, int eta, double mediaVoti) {
        this.nome = nome;
        this.cognome = cognome;
        this.eta = eta;
        this.mediaVoti = mediaVoti;
    }
    
    // GETTER - metodi per leggere i valori
    public String getNome() {
        return nome;
    }
    
    public String getCognome() {
        return cognome;
    }
    
    public int getEta() {
        return eta;
    }
    
    public double getMediaVoti() {
        return mediaVoti;
    }
    
    // SETTER - metodi per modificare i valori CON CONTROLLI
    public void setNome(String nome) {
        if (nome != null && !nome.isEmpty()) {
            this.nome = nome;
        } else {
            System.out.println("Nome non valido!");
        }
    }
    
    public void setCognome(String cognome) {
        if (cognome != null && !cognome.isEmpty()) {
            this.cognome = cognome;
        } else {
            System.out.println("Cognome non valido!");
        }
    }
    
    public void setEta(int eta) {
        if (eta >= 0 && eta <= 120) {
            this.eta = eta;
        } else {
            System.out.println("Età non valida!");
        }
    }
    
    public void setMediaVoti(double mediaVoti) {
        if (mediaVoti >= 0 && mediaVoti <= 10) {
            this.mediaVoti = mediaVoti;
        } else {
            System.out.println("Media voti non valida!");
        }
    }
    
    // Metodo per stampare le info
    public void stampaInfo() {
        System.out.println(nome + " " + cognome + 
                         ", " + eta + " anni, media: " + mediaVoti);
    }
}

🎨 Uso di Getter e Setter

public class TestStudente {
    public static void main(String[] args) {
        Studente studente = new Studente("Mario", "Rossi", 17, 7.5);
        
        // Leggere i valori con i GETTER
        System.out.println("Nome: " + studente.getNome());
        System.out.println("Età: " + studente.getEta());
        System.out.println("Media: " + studente.getMediaVoti());
        
        System.out.println("\n--- Modifico i valori ---");
        
        // Modificare i valori con i SETTER
        studente.setMediaVoti(8.0);  // OK
        studente.setEta(18);         // OK
        
        // Tentativo con valori non validi
        studente.setMediaVoti(15);   // Rifiutato!
        studente.setEta(-5);         // Rifiutato!
        
        System.out.println("\n--- Info finali ---");
        studente.stampaInfo();
    }
}
Nome: Mario Età: 17 Media: 7.5 --- Modifico i valori --- Media voti non valida! Età non valida! --- Info finali --- Mario Rossi, 18 anni, media: 8.0

✅ Vantaggi di Getter e Setter:

  • Validazione: possiamo controllare che i valori siano corretti
  • Flessibilità: possiamo cambiare l'implementazione interna senza modificare il codice esterno
  • Read-only: possiamo creare attributi in sola lettura (solo getter, no setter)
  • Write-only: possiamo creare attributi in sola scrittura (solo setter, no getter)
  • Calcoli: un getter può calcolare un valore invece di restituirlo direttamente

🔍 Esempio: Getter Calcolato

public class Persona {
    private String nome;
    private String cognome;
    private int annoNascita;
    
    public Persona(String nome, String cognome, int annoNascita) {
        this.nome = nome;
        this.cognome = cognome;
        this.annoNascita = annoNascita;
    }
    
    // Getter normali
    public String getNome() {
        return nome;
    }
    
    public String getCognome() {
        return cognome;
    }
    
    // Getter CALCOLATO - non esiste un attributo "eta"
    public int getEta() {
        int annoCorrente = 2025;
        return annoCorrente - annoNascita;
    }
    
    // Getter che restituisce il nome completo
    public String getNomeCompleto() {
        return nome + " " + cognome;
    }
    
    // Setter
    public void setNome(String nome) {
        this.nome = nome;
    }
    
    public void setCognome(String cognome) {
        this.cognome = cognome;
    }
}
public class TestPersona {
    public static void main(String[] args) {
        Persona persona = new Persona("Giulia", "Bianchi", 2007);
        
        System.out.println("Nome completo: " + persona.getNomeCompleto());
        System.out.println("Età: " + persona.getEta());  // Calcolata!
    }
}
Nome completo: Giulia Bianchi Età: 18

🚗 Esempio Completo: Classe Auto

public class Auto {
    private String marca;
    private String modello;
    private int anno;
    private double velocitaAttuale;
    private double velocitaMassima;
    private boolean accesa;
    
    public Auto(String marca, String modello, int anno, double velocitaMassima) {
        this.marca = marca;
        this.modello = modello;
        this.anno = anno;
        this.velocitaMassima = velocitaMassima;
        this.velocitaAttuale = 0;
        this.accesa = false;
    }
    
    // Getter
    public String getMarca() {
        return marca;
    }
    
    public String getModello() {
        return modello;
    }
    
    public int getAnno() {
        return anno;
    }
    
    public double getVelocitaAttuale() {
        return velocitaAttuale;
    }
    
    public double getVelocitaMassima() {
        return velocitaMassima;
    }
    
    public boolean isAccesa() {  // Per boolean si usa "is" invece di "get"
        return accesa;
    }
    
    // Setter con validazione
    public void setVelocitaMassima(double velocitaMassima) {
        if (velocitaMassima > 0 && velocitaMassima <= 500) {
            this.velocitaMassima = velocitaMassima;
        } else {
            System.out.println("Velocità massima non valida!");
        }
    }
    
    // Metodi pubblici per l'interazione
    public void accendi() {
        if (!accesa) {
            accesa = true;
            velocitaAttuale = 0;
            System.out.println(marca + " " + modello + " accesa!");
        }
    }
    
    public void spegni() {
        if (accesa && velocitaAttuale == 0) {
            accesa = false;
            System.out.println("Auto spenta.");
        } else if (velocitaAttuale > 0) {
            System.out.println("Ferma l'auto prima di spegnerla!");
        }
    }
    
    public void accelera(double incremento) {
        if (!accesa) {
            System.out.println("Accendi l'auto prima!");
            return;
        }
        
        velocitaAttuale += incremento;
        
        if (velocitaAttuale > velocitaMassima) {
            velocitaAttuale = velocitaMassima;
        }
        
        System.out.println("Velocità: " + velocitaAttuale + " km/h");
    }
    
    public void frena(double decremento) {
        velocitaAttuale -= decremento;
        
        if (velocitaAttuale < 0) {
            velocitaAttuale = 0;
        }
        
        System.out.println("Velocità: " + velocitaAttuale + " km/h");
    }
}

✏️ Esercizi Guidati

Esercizio 1: Classe Prodotto Incapsulato

FACILE

Obiettivo: Creare una classe Prodotto con incapsulamento.

Requisiti:

  • Attributi privati: nome, prezzo, quantitaDisponibile
  • Costruttore
  • Getter per tutti gli attributi
  • Setter per prezzo (deve essere > 0)
  • Metodo vendi(int quantita) che riduce la disponibilità
  • Metodo rifornisci(int quantita) che aumenta la disponibilità

Codice iniziale:

public class Prodotto {
    private String nome;
    private double prezzo;
    private int quantitaDisponibile;
    
    public Prodotto(String nome, double prezzo, int quantitaDisponibile) {
        // Completa
    }
    
    // Crea getter e setter
    
    public boolean vendi(int quantita) {
        // Controlla se c'è abbastanza disponibilità
        // Se sì, riduci e restituisci true
        // Altrimenti restituisci false
    }
    
    public void rifornisci(int quantita) {
        // Aumenta la disponibilità
    }
}

Esercizio 2: Classe DataNascita

MEDIO

Obiettivo: Creare una classe per gestire date di nascita con validazione.

Requisiti:

  • Attributi privati: giorno, mese, anno
  • Costruttore che valida la data
  • Setter con validazione (giorno 1-31, mese 1-12, anno ragionevole)
  • Getter per tutti gli attributi
  • Metodo calcolaEta() che restituisce l'età
  • Metodo stampaData() formato "gg/mm/aaaa"

💪 Esercizi da Svolgere

Esercizio 3: Classe Password

MEDIO

Crea una classe Password con:

  • Attributo privato: password (String)
  • Setter che accetta solo password con almeno 8 caratteri
  • Metodo verifica(String tentativo) che restituisce true se corrisponde
  • NO getter per la password (sicurezza!)

Esercizio 4: Classe TermostatoTemperatura

MEDIO

Crea una classe Termostato con:

  • Attributi privati: temperaturaAttuale, temperaturaImpostata
  • Temperatura impostata deve essere tra 15 e 30 gradi
  • Metodi per aumentare/diminuire la temperatura impostata
  • Metodo isRiscaldamentoAttivo()

Esercizio 5: Classe Email

DIFFICILE

Crea una classe Email con:

  • Attributo privato: indirizzo
  • Setter che valida che la mail contenga "@" e "."
  • Metodo getDominio() che restituisce la parte dopo la @
  • Metodo getUtente() che restituisce la parte prima della @

🎯 Concetti Chiave da Ricordare

  • Incapsulamento = nascondere i dettagli interni e fornire un'interfaccia controllata
  • Gli attributi dovrebbero essere private
  • I metodi pubblici dovrebbero essere public
  • Getter per leggere i valori (get + NomeAttributo)
  • Setter per modificare i valori con validazione (set + NomeAttributo)
  • Per boolean: usa isAttributo() invece di getAttributo()
  • I setter permettono di validare i dati prima di modificarli

📚 Best Practices

  • Rendi SEMPRE privati gli attributi (salvo rare eccezioni)
  • Fornisci getter e setter solo per gli attributi che devono essere accessibili
  • Valida SEMPRE i dati nei setter
  • Non creare setter per attributi che non dovrebbero essere modificati dall'esterno
  • Documenta bene i tuoi metodi pubblici (sono l'interfaccia della classe)