1
|
2 |
3 |
4
3 - Unità didattica 5.3)
Elenco delle pagine di "Il
linguaggio Java: lez.6"
Introduzione e Unità didattica 5.1) - Pagina 1
Introduzione e Unità didattica 5.2) - Pagina 2
Unità didattica 5.3) - Pagina 3
Unità didattica 5.4) - Pagina 4
Unità didattica 5.5) - Pagina 5
Unità didattica 5.6) - Pagina 6
- Incapsulamento
L’incapsulamento è la chiave della
programmazione orientata agli oggetti. Tramite esso, una classe
riesce ad acquisire caratteristiche di robustezza, indipendenza e
riusabilità. Inoltre la sua manutenzione risulterà più semplice al
programmatore.
Una qualsiasi classe è essenzialmente costituita da dati e metodi.
La filosofia dell’incapsulamento è semplice. Essa si basa
sull’accesso controllato ai dati mediante metodi che possono
prevenirne l’usura e la non correttezza dei dati stessi. A livello
di implementazione, ciò si traduce nel dichiarare privati i membri
di una classe e quindi inaccessibili al di fuori della classe stessa
(a tale scopo esiste il modificatore private). Allora, l’accesso ai
dati, potrà essere fornito da un’interfaccia pubblica costituita da
metodi dichiarati public, e quindi accessibili da altre classi. In
questo modo, tali metodi potrebbero ad esempio permettere di
realizzare controlli prima di confermare l’accesso ai dati privati.
Se l’incapsulamento è gestito in maniera intelligente, le nostre
classi potranno essere utilizzate nel modo migliore e più a lungo,
giacché le modifiche e le revisioni potranno riguardare solamente
parti di codice non visibili all’esterno. Se volessimo fare un
esempio basandoci sulla realtà che ci circonda, potremmo prendere in
considerazione un telefono. La maggior parte degli utenti, infatti,
sa utilizzare il telefono, ma ne ignora il funzionamento interno.
Chiunque infatti, può alzare la cornetta, comporre un numero
telefonico, e conversare con un’altra persona, ma pochi conoscono in
dettaglio la sequenza dei processi scatenati da queste poche,
semplici azioni. Evidentemente per utilizzare il telefono, non è
necessario essere un tecnico: basta conoscere la sua interfaccia
pubblica, non la sua implementazione interna.
Di seguito è presentata una classe che utilizza l’incapsulamento,
gestendo l’accesso ad un saldo bancario personale, mediante
l’inserimento di un codice segreto:
class ContoBancario
{
private String contoBancario = "5000000 di Euro";
private int codice= 1234;
public String getContoBancario(int codiceDaTestare)
{
if (codiceDaTestare==codice)
{
return contoBancario;
}
else
{
return "codice errato!!!";
}
}
}
In generale, nella programmazione
ad oggetti, si preferisce sempre, dichiarare i dati privati, e
semmai fornire alla classe metodi pubblici di tipo "set" e "get" per
accedervi. Possiamo comunque chiamare questi metodi utilizzando un
qualsiasi identificatore, possibilmente significativo: "set" e "get"
non sono parole chiave. Per correttezza la classe Data definita nel
modulo 3, dovrebbe essere definita comunque in questo modo:
class Data
{
private int giorno;
private int mese;
private int anno;
public void setGiorno(int g)
{
if (g>0 && g<=31)
{
//Altri controlli conoscendo il mese. . .
giorno = g;
}
else . . .
}
public int getGiorno()
{
return giorno;
}
public void setMese(int m)
{
if (m>0 && m<=12)
{
mese = m;
}
else . . .
}
public int getMese()
{
return mese;
}
public void setAnno(int a)
{
anno = a;
}
public int getAnno()
{
return anno;
}
}
Supponiamo inoltre che esista un’ altra classe che
dichiara il seguente blocco di codice:
1) Data oggi=new Data();
oggi.setGiorno(9);
Il lettore avrà notato che utilizzare l’incapsulamento, richiederà
un maggior sforzo d’implementazione. Tuttavia, questo sforzo sarà
presto ricompensato. Infatti, supponiamo di voler apportare
modifiche migliorative alla classe precedente. Per esempio, notiamo
che per testare efficacemente il settaggio della variabile giorno,
dovremmo tener conto anche del valore del mese e dell’anno (se
l’anno è bisestile, e il mese è febbraio, allora risulterà legale il
giorno con valore 29…). Ecco allora che le modifiche riguarderanno
solo l’implementazione del metodo setGiorno all’interno della classe
Data e le classi che dichiarano il blocco di codice 1)non dovranno
essere modificate.
- Prima osservazione sull’incapsulamento:
Sino ad ora abbiamo visto degli esempi di incapsulamento abbastanza
classici, dove nascondevamo all’interno delle classi gli attributi
mediante il modificatore private. Nulla ci vieta di utilizzare
private, anche come modificatore di metodi, ottenendo così un
incapsulamento funzionale. Un metodo privato infatti, potrà essere
invocato solo da un metodo definito nella stessa classe, che
potrebbe a sua volta essere dichiarato pubblico. Per esempio la
classe ContoBancario definita precedentemente, in un progetto
potrebbe evolversi nel seguente modo:
class ContoBancario
{
private String contoBancario = "5000000 di Euro";
private int codice= 1234;
public String getContoBancario(int codiceDaTestare)
{
return controllaCodice(codiceDaTestare);
}
private String controllaCodice(int codiceDaTestare)
{
if (codiceDaTestare==codice)
{
return contoBancario;
}
else
{
return "codice errato!!!";
}
}
}
Ciò favorirebbe il riuso di codice
in quanto, introducendo nuovi metodi (come probabilmente accadrà in
progetto che si incrementa), questi potrebbero risfruttare il metodo
controllaCodice. - Seconda osservazione sull’incapsulamento: Abbiamo
affermato che un membro di una classe dichiarato private, diventa
"inaccessibile da altre classi". Questa frase è ragionevole per
quanto riguarda l’ambito della compilazione, dove la dichiarazione
delle classi è il problema da superare. Ma, se ci spostiamo
nell’ambito della Java Virtual Machine, dove, come abbiamo detto, i
protagonisti assoluti non sono le classi ma gli oggetti, dobbiamo
rivalutare l’affermazione precedente. L’incapsulamento infatti,
permetterà a due oggetti istanziati dalla stessa classe di accedere
in "modo pubblico", ai rispettivi membri privati.
Facciamo un esempio, consideriamo la seguente classe Dipendente:
class Dipendente
{
private String nome;
private int anni; //intendiamo età in anni
. . .
public String getNome()
{
return nome;
}
public void setNome(String n)
{
nome=n;
}
public String getAnni()
{
return anni;
}
public void setAnni(int n)
{
anni=n;
}
public int getDifferenzaAnni(Dipendente altro)
{
return (anni – altro.anni);
}
}
Nel metodo getDifferenzaAnni
notiamo che è possibile accedere direttamente alla variabile anni
dell’oggetto altro, senza dover utilizzare il metodo getAnni. Il
lettore è invitato a riflettere soprattutto sul fatto che il codice
precedente è valido per la compilazione, ma, il seguente metodo
public int getDifferenzaAnni(Dipendente altro)
{
return (getAnni() – altro.getAnni());
}
favorirebbe sicuramente di più il riuso di codice,
e quindi è da considerarsi preferibile.
- Il reference "this":
L’esempio precedente potrebbe aver
provocato nel lettore qualche dubbio. Sino ad ora, avevamo dato per
scontato che accedere ad una variabile d’istanza all’interno di una
classe che la definisce, fosse un processo naturale che non aveva
bisogno di reference. Ad esempio della classe precedentemente
descritta Data, all’interno del metodo getGiorno, accedevamo alla
variabile giorno, senza referenziarla. Alla luce dell’ultimo esempio
ci potremmo chiedere: se giorno è una variabile d’istanza, a quale
istanza appartiene? La risposta a questa domanda che sino ad ora non
avevamo neanche preso in considerazione, è: dipende "dall’oggetto
corrente", ovvero dall’oggetto su cui è chiamato il metodo getGiorno.
In fase di esecuzione di un certa applicazione, potrebbe essere
istanziati due particolari oggetti, supponiamo che si chiamino
mioCompleanno e tuoCompleanno. Entrambi questi oggetti hanno una
propria variabile giorno. Ad un certo punto, all’interno del
programma potrebbe presentarsi la seguente istruzione:
System.out.println(mioCompleanno.getGiorno());
Sappiamo che sarà stampato a video il valore della variabile giorno
dell’oggetto mioCompleanno, ma dal momento che sappiamo che una
variabile anche all’interno di una classe potrebbe (e dovrebbe)
essere referenziata, dovremmo sforzarci di capire come fa la Java
Virtual Machine a scegliere la variabile giusta senza avere a
disposizione reference! In realtà, se il programmatore non
referenzia una certa variabile d’istanza, al momento della
compilazione il codice sarà modificato dal compilatore stesso, che
aggiungerà un reference all’oggetto corrente davanti alla variabile.
Ma quale reference all’oggetto corrente? La classe non può conoscere
a priori i reference degli oggetti che saranno istanziati da essa in
fase di runtime!
Java introduce una parola chiave, che per definizione coincide ad un
reference all’oggetto corrente: this (questo). Il reference this
viene quindi implicitamente aggiunto nel bytecode compilato, per
referenziare ogni variabile d’istanza non esplicitamente
referenziata. Ancora una volta Java cerca di facilitare la vita del
programmatore. In un linguaggio orientato agli oggetti puro, non è
permesso non referenziare le variabili d’istanza.
In pratica il metodo getGiorno che avrà a disposizione la J.V.M.
dopo la compilazione sarà:
public int getGiorno()
{
return this.giorno; //il this lo aggiunge
} //il compilatore
In seguito vedremo altri utilizzi
del reference "segreto" this.
N.B.: anche in questo caso, abbiamo notato un’altro di quei
comportamenti del linguaggio, che fanno sì che Java sia definito
come "semplice". Se non ci siamo posti il problema della
referenzazione dei membri all’interno di una classe sino a questo
punto, vuol dire che anche questa volta, "Java ci ha dato una mano".
- Due stili di programmazione a confronto:
Nel secondo modulo abbiamo distinto le variabili d‘istanza dalle
variabili locali. La diversità tra i due concetti è tale che il
compilatore ci permette di dichiarare una variabile locale (o un
parametro di un metodo) ed una variabile di istanza, aventi lo
stesso identificatore, nella stessa classe. La parola chiave this si
inserisce in questo discorso nel seguente modo. Abbiamo più volte
avuto a che fare con passaggi di parametri in metodi, al fine di
inizializzare variabili d’istanza. Siamo stati costretti, sino ad
ora, ad inventare per il parametro passato un identificatore
differente da quello della variabile d’istanza da inizializzare.
Consideriamo la seguente classe:
class Cliente
{
private String nome, indirizzo;
private int numeroDiTelefono;
. . .
public void setCliente(String n,String ind,
int num)
{
nome=n;
indirizzo=ind;
numeroDiTelefono= num;
}
}
Notiamo l’utilizzo dell’identificatore
n per inizializzare nome, num per numeroDiTelefono, e ind per
indirizzo. Non c’è nulla di sbagliato in questo. Conoscendo
l’esistenza di this però, abbiamo la possibilità di scrivere
equivalentemente:
class Cliente
{
. . .
public void setCliente(String nome,String indirizzo
int numeroDiTelefono)
{
this.nome=nome;
this.indirizzo=indirizzo;
this.numeroDiTelefono= numeroDiTelefono;
}
. . .
}
Infatti, tramite la parola chiave
this, specifichiamo che la variabile referenziata, appartiene
all’istanza. Di conseguenza la variabile non referenziata sarà il
parametro del metodo. Non c’è ambiguità quindi, nel codice
precedente. Questo stile di programmazione è da alcuni (compreso chi
vi scrive) considerato preferibile. In questo modo, infatti, non c’è
possibilità di confondere le variabili con nomi simili. Nel nostro
esempio potrebbe capitare di assegnare il parametro n alla variabile
d’istanza numeroDiTelefono, ed il parametro num alla variabile nome.
Potremmo affermare che l’utilizzo di this aggiunge chiarezza al
nostro codice.
N.B.: il lettore noti che se scrivessimo:
class Cliente
{
. . .
public void setCliente(String nome,String
indirizzo,int numeroDiTelefono)
{
nome=nome;
indirizzo=indirizzo;
numeroDiTelefono= numeroDiTelefono;
}
. . .
}
il compilatore, non trovando riferimenti
espliciti, considererebbe variabili locali le prime incontrate, e di
istanza le seconde, in pratica leggerebbe:
class Cliente
{
. . .
public Cliente(String nome,String indirizzo
int numeroDiTelefono)
{
nome=this.nome;
indirizzo=this.indirizzo;
numeroDiTelefono=this.numeroDiTelefono;
}
}
Ovviamente, in questo modo,
comunque istanziamo un oggetto di questa classe, le sue variabili
verranno inizializzate ai relativi valori nulli (poiché così sono
inizializzate automaticamente le variabili d’istanza).
Elenco delle pagine di "Il
linguaggio Java: lez.6"
Introduzione e Unità didattica 5.1) - Pagina 1
Introduzione e Unità didattica 5.2) - Pagina 2
Unità didattica 5.3) - Pagina 3
Unità didattica 5.4) - Pagina 4
Unità didattica 5.5) - Pagina 5
Unità didattica 5.6) - Pagina 6