Uno dei concetti più strani per chi approccia alla programmazione orientata agli oggetti OOP è quello dell’incapsulamento. Questo prevede la dichiarazione degli attributi privati e la conseguente modifica o reperimento del contenuto di tali attributi solo passando per i metodi di accesso set/get. Ma perché questa è una soluzione vincente? Non sarebbe meglio inserire attributi pubblici e modificarli direttamente? Vediamo un esempio.
Indice dei contenuti
La classe Contocorrente
Proponiamo una classe semplicissima: il Contocorrente. La creiamo con pochi elementi essenziali: un id che identifica il conto corrente, il nome e cognome del correntista e un numero decimale che contiene la disponibilità, ovvero liquidità di denaro del conto che possiamo prelevare. Tutti gli attributi li impostiamo a public, quindi accessibili in modo diretto, ovvero possiamo assegnargli dei valori come fossero sostanzialmente variabili normali a cui siamo abituati. Non inseriamo metodi per semplicità e inseriamo solo un costruttore di default.
public class Contocorrente
{
public String id;
public String nomeCorrentista;
public String cognomeCorrentista;
public double saldo;
public Contocorrente()
{
}
}Creiamo il nostro programma main. Inizializziamo un oggetto Contocorrente attraverso il costruttore vuoto che abbiamo previsto. Se proviamo ad assegnare ai vari attributi dei valori, non abbiamo avvisi o errori. Gli attributi sono impostati pubblici, direttamente accessibili. Lanciamo il nostro programma main: non avremo nessun errore. Per id, nome e cognome non abbiamo particolari criticità, mentre il campo saldo vediamo che possiamo assegnare, aggiungere o sottrarre valori. In effetti l’idea pratica è che possiamo aprire il conto con un certo valore di soldi iniziale, versare sul nostro conto soldi o prelevare soldi sottraendo al totale. Qual è la criticità? Ad ogni operazione di prelievo dovrei controllare se c’è la disponibilità a sottrarre la quantità di denaro richiesta per impedire un prelievo non autorizzato che manderebbe “in rosso” il conto.
void main()
{
Contocorrente conto = new Contocorrente();
conto.id = "IT12A1234567890123456789012";
conto.nomeCorrentista = "Alfredo";
conto.cognomeCorrentista = "Centinaro";
conto.saldo= 100.00;
conto.saldo+= 200.00;
conto.saldo-= 1000.00;
IO.print(conto.saldo);
}Continuiamo con la similitudine di vita reale. Vi immaginate ad uno sportello bancomat che io debba prima controllare il saldo e in base a quello prelevare solo una quantità disponibile di denaro? Vi immaginate quante persone con accesso diretto al prelievo sul saldo si disinteressano della quantità disponibile e ritirano deliberatamente una quantità di denaro non idonea? Consentire un accesso diretto ad un attributo, senza controlli, è pericoloso! Occorre una strategia che filtra l’accesso ad un attributo valutando se i valori che gli vengono assegnati è adeguato o provvedere ad opportune segnalazioni di errore.
Riscriviamo la nostra classe per impedire l’accesso diretto alle variabili. Dichiariamo gli attributi privati con la clausola “private”. Il costruttore invece rimane public, utilizzabile quindi sempre e comunque direttamente.
public class Contocorrente
{
private String id;
private String nomeCorrentista;
private String cognomeCorrentista;
private double saldo;
public Contocorrente()
{
}
}Lasciamo il main invariato con lo stesso codice. Con buona probabilità l’ide di sviluppo darà già un avviso che qualcosa non va con l’assegnazione diretta di valori ad attributi privati. La cosa sicuramente sarà comprovata se proviamo ad eseguire il codice del nostro main con un messaggio di errore “java: id has private access in Contocorrente” e così per tutti i campi che sto cercando di modificare indiscriminatamente in modo diretto.
Come modifichiamo allora i nostri campi? Abbiamo bisogno di metodi che possono essere invocati con opportuni parametri per modificare o reperire il valore degli attributi. Torniamo sulla nostra classe Contocorrente. Posizioniamo il cursore del mouse sotto il costruttore, premiamo il tasto destro del mouse, nella tendina clicchiamo la voce “Generete…”, selezioniamo la voce “Getter and Setter”. Nella finestrella che si apre selezioniamo con il tasto shift o ctrl tutti gli attributi che vengono mostrati. Avremo un codice automatico come il seguente.
I metodi di accesso set/get
public class Contocorrente
{
private String id;
private String nomeCorrentista;
private String cognomeCorrentista;
private double saldo;
public Contocorrente()
{
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNomeCorrentista() {
return nomeCorrentista;
}
public void setNomeCorrentista(String nomeCorrentista) {
this.nomeCorrentista = nomeCorrentista;
}
public String getCognomeCorrentista() {
return cognomeCorrentista;
}
public void setCognomeCorrentista(String cognomeCorrentista) {
this.cognomeCorrentista = cognomeCorrentista;
}
public double getSaldo() {
return saldo;
}
public void setSaldo(double saldo) {
this.saldo = saldo;
}
}Come potete vedere, abbiamo tutto un mondo di nuove funzioni set e get, dove set procede a fare assegnazioni su this.campo, su questo campo in questa classe potremmo quasi tradurre. Get invece restituisce il contenuto di questo attributo. Ogni attributo ha la sua coppia set/get pubblici, ovvero utilizzabili in modo diretto all’interno di un’altra classe o del main.
Qualcuno dirà: e cosa ci abbiamo guadagnato? E’ solo più codice da scrivere ed organizzare. Potenzialmente vero, ma concentriamoci sul metodo set dell’attributo saldo. Riscriviamolo in modo più interessante.
Con la versione attuale del set, il valore dell’attributo saldo viene sovrascritto a forza qualunque sia il suo valore. Se invece usiamo
public void setSaldo(double saldo) {
this.saldo += saldo;
}Ad un utente distratto sarà impossibile sovrascrivere ma solo aggiungere/versare o sottrarre/prelevare da saldo. Tanto che potremmo rinominare il parametro saldo passato alla funzione con “_soldi” più intuitivo. Ma ancora, proteggiamo il nostro saldo da prelievi che mandano in confusione il sistema. Ovvero voglio impedire di prelevare più di quanto è disponibile, mentre consento qualsiasi versamento.
public void setSaldo(double _soldi) {
//gestisco i prelievi non coperti
if (_soldi < 0 && Math.abs(_soldi) <= this.saldo)
this.saldo += _soldi;
//se sto versando non faccio controlli particolari
if (_soldi >0)
this.saldo += _soldi;
}Ci siamo quasi, mettiamo un messaggio in modo tale che sia visibile l’esito dell’operazione se avvenuta con successo o meno per indisponibilità del saldo. Semplifichiamo un po’ anche se non proprio pulito per i puristi del codice e aggiungiamo messaggi forzando però l’uscita dopo l’operazione positiva.
public void setSaldo(double _soldi) {
//gestisco i prelievi non coperti
if (_soldi < 0 && Math.abs(_soldi) <= this.saldo) {
this.saldo += _soldi;
IO.println("Operazione avvenuta con successo");
return;
}
//se sto versando non faccio controlli particolari
if (_soldi >0) {
this.saldo += _soldi;
IO.println("Operazione avvenuta con successo");
return;
}
IO.println("Operazione fallita");
}e creiamo un main per testare i messaggi e le relative operazioni.
void main()
{
Contocorrente conto = new Contocorrente();
conto.setId("IT12A1234567890123456789012");
conto.setNomeCorrentista("Alfredo");
conto.setCognomeCorrentista("Centinaro");
conto.setSaldo(100.00);
conto.setSaldo(200.00);
conto.setSaldo(-1000.00);
conto.setSaldo(-50.00);
IO.print("Saldo: " + conto.getSaldo());
}L’operazione di aggiunta di 100 e 200 stampa messaggi positivi, mentre il prelievo di -1000 non passa, contrariamente al prelievo di 50 che invece è disponibile.
Possiamo ancora di più oltre che semplificare il controllo sulla disponibilità, possiamo anche potenziare il nostro metodo tornando un valore non void ma boolean, vero se possiamo eseguire l’operazione, falso se non è andato a buon fine il prelievo. Non ci resta che provare questa moltitudine di controlli nel nostro main. Usiamo una variabile boolean per gestire l’esito e stampare un messaggio a video descrittivo dell’esito.
void main()
{
Contocorrente conto = new Contocorrente();
boolean esito;
conto.setId("IT12A1234567890123456789012");
conto.setNomeCorrentista("Alfredo");
conto.setCognomeCorrentista("Centinaro");
esito = conto.setSaldo(100.00);
if (esito)
IO.println("Versamento 100.00 ok");
esito = conto.setSaldo(200.00);
if (esito)
IO.println("Versamento 200.00 ok");
esito = conto.setSaldo(-1000.00);
if (esito)
IO.println("Prelievo 10000 ok");
else
IO.println("Prelievo 10000 fallito");
esito = conto.setSaldo(-50.00);
if (esito)
IO.println("Prelievo 50 ok");
else
IO.println("Prelievo 50 fallito");
IO.print("Saldo: " + conto.getSaldo());
}Ovviamente il prelievo di 1000 che non sono disponibili sul conto con 300 da messaggio errore mentre il prelievo di 50 su 300 va in porto.
Conclusioni
Abbiamo visto come i metodi di accesso set/get impediscano di modificare esplicitamente un attributo e consentano, dove necessario, un sistema complesso di controlli di valori non consentiti. Il tutto in modo trasparente all’utente che usa la classe nel main non abbia da fare controlli ulteriori espliciti. Questo è molto comodo: se la classe è ben documentata e generica potrà essere riusata in altri software vista la robustezza.
I dati sono sempre integri. con un congruo numero di controlli nessun utente sbadato può creare caos nei dati immessi.
Se volessi modificare il controllo con soglie o altre strategie, mi basta modificare una tantum il metodo, non tutti i punti dove viene valorizzato l’attributo. Questa è una grande cosa per la manutenzione del software quando sale la complessità.
Astrazione: se usi la classe nel tuo main, l’utente non deve conoscere le regole bancarie, quindi è a prova di errore!
Potete provare a modificare i metodi testuali controllando la validità dei valori inseriti e stampare un messaggio magari direttamente dentro il metodo set senza scomodare variabili booleane.
Ultima modifica 23 Gennaio 2026


