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).

 



 

 



Ti potrebbe interessare anche

commenta la notizia

C'è 1 commento
Redazione
Ti interessano altri articoli su questo argomento?
Chiedi alla nostra Redazione!