Incapsulamento: Proteggere i Dati
🎯 Obiettivi della lezione
- Comprendere il concetto di incapsulamento
- Imparare i modificatori di accesso (public, private, protected)
- Creare getter e setter
- Capire i vantaggi dell'information hiding
- Applicare la validazione nei setter
💡 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();
}
}
🔧 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();
}
}
✅ 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!
}
}
🚗 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
FACILEObiettivo: 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
MEDIOObiettivo: 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
MEDIOCrea 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
MEDIOCrea 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
DIFFICILECrea 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)