MokaByte 71- Febbraio 2003 
Corso di programmazione Java
X parte: usare gli oggetti
di
Andrea Gini
Dopo aver descritto a lungo le strutture di controllo del linguaggio, è finalmente arrivato il momento di introdurre i concetti base della programmazione ad oggetti. Dopo una breve panoramica sulla metafora degli Oggetti, verranno illustrati i costrutti necessari ad utilizzare gli Oggetti in un contesto di Programmazione Strutturata, simile a quello visto fino ad ora

Usare gli Oggetti
Nei capitoli precedenti, abbiamo studiato le direttive base della programmazione: istruzioni di input, output ed assegnamento e strutture di controllo sequenziali, condizionali ed iterative. I costrutti esposti fino ad ora denotano un modello di calcolo di tipo funzionale, in cui il programma è visto come una funzione matematica che associa ad ogni configurazione di input una corrispondente configurazione di output. I valori in ingresso vengono elaborati, e da essi si ottiene una risposta, il tutto in forma rigorosamente numerica.


Figura 1 - rappresentazione di programma come funzione matematica

Questo modello rispecchia fedelmente la natura dei computer, ma presenta un livello di astrazione non adeguato per la mente umana: è molto difficile descrivere in forma puramente numerica scenari applicativi complessi, ed è ancora più difficile realizzare le procedure necessarie a manipolare tali modelli restando a questo livello di astrazione.
Durante la fase di progettazione di un sistema informatico, si tende a modellare la struttura ed il comportamento di un programma secondo una metafora che rispecchi l’attività che si intende automatizzare. Nella realtà di tutti i giorni le attività comportano la trasformazione di un insieme di oggetti di input in un nuovo insieme di oggetti: se si introduce la metafora degli oggetti direttamente nel linguaggio di programmazione, diventa possibile creare programmi dotati di un livello di astrazione molto più vicino a quello del mondo reale.
Il paradigma della Programmazione ad Oggetti si è affermato gradualmente a partire dalla metà degli anni ’80 grazie alle sue caratteristiche di eleganza e naturalezza. La metafora degli oggetti, grazie alla sua analogia con l’esperienza di tutti i giorni, offre una modalità di rappresentazione dei problemi intuitiva, naturale e a volte persino divertente.
Lo studio della programmazione ad oggetti verrà suddiviso in due fasi: in un primo tempo ci concentreremo sull’uso di alcuni importanti oggetti di libreria in un contesto di programmazione procedurale; in seguito studieremo in modo approfondito le modalità di progettazione e sviluppo di oggetti. Questa suddivisione permetterà un approccio graduale alla Programmazione orientata agli Oggetti, che darà la precedenza agli argomenti più facili ed intuitivi per poi andare ad analizzare in profondità gli aspetti più sottili, e spesso trascurati o fraintesi, della filosofia degli Oggetti.

 

La metafora degli Oggetti
Nella vita di tutti i giorni, siamo soliti svolgere una serie di compiti classificabili come trasformazioni. Quando cuciniamo, tanto per fare un esempio, noi trasformiamo un insieme di ingredienti crudi in una pietanza cotta. Per portare a termine tali compiti, siamo soliti ricorrere a un certo numero di strumenti: in cucina tipicamente utilizziamo pentole, posate e fornelli. Nel gergo della programmazione ad Oggetti non viene fatta distinzione tra gli oggetti che provocano trasformazioni e quelli che le subiscono (strumenti e ed ingredienti in questo caso): gli uni e gli altri vengono descritti col termine generico di Oggetti. Vediamo ora una procedura che descrive le operazioni necessarie a lessare una patata:

1) Prendere una patata cruda
2) Sciacquare la patata sotto ad un rubinetto fino a quando non risulta pulita
3) Sbucciare la patata con un coltello
4) Mettere un pentolino sotto al rubinetto fino a quando è pieno
5) Accendere un fornello
6) Mettere il pentolino sul fornello
7) Mettere la patata nel pentolino
8) Se la patata è cotta, spegnere il fornello, altrimenti aspettare
9) Togliere la patata dal pentolino usando una forchetta

Per portare a termine il compito “Cottura di una patata” abbiamo utilizzato 6 oggetti di uso comune: una patata, un coltello, un rubinetto, un pentolino, un fornello ed una forchetta. Nel corso dell’operazione, abbiamo svolto alcune interazioni tra questi oggetti: abbiamo usato un coltello per sbucciare una patata, un fornello per scaldare un pentolino pieno di acqua e il pentolino per cuocere la patata. Le interazioni sono rese possibili dalla natura dell’oggetto stesso: la lama di un coltello serve a tagliare, il fuoco a scaldare e così via. Alcuni degli oggetti coinvolti hanno subito delle trasformazioni o dei cambiamenti di stato: la patata, inizialmente cruda, è stata dapprima lavata (prima trasformazione), poi sbucciata (seconda trasformazione) infine cucinata; il fornello, inizialmente spento, è stato dapprima acceso (primo cambiamento di stato), poi spento di nuovo (secondo cambiamento di stato). D’altra parte, nel descrivere la procedura siamo ricorsi ad una descrizione di tipo algoritmico, utilizzando i familiari costrutti della sequenza, iterazione e selezione.
Prima di introdurre la metafora degli oggetti nel mondo della programmazione, proviamo a chiarire i concetti di stato e comportamento negli oggetti del mondo reale.

 

Lo stato di un Oggetto e i suoi Attributi
Ogni oggetto del mondo reale può essere descritto attraverso un certo numero di attributi, come forma, dimensioni e colore. Qualunque qualità misurabile o enumerabile può costituire un attributo. Alcuni attributi, come le dimensioni, sono presenti in qualunque oggetto, altri invece sono caratteristici di una particolare classe di oggetti e non compaiono in altri: “crudo” e “cotto” sono attributi che hanno senso per una patata, ma di certo non per un coltello.

Lo stato di un oggetto è dato dall’insieme dei valori dei suoi attributi. Nel corso del suo ciclo di vita, un oggetto può cambiare stato per diverse ragioni: alcuni attributi possono essere modificati dall’utilizzatore attraverso un’interazione diretta (il fornello può essere acceso e spento direttamente, utilizzando l’apposita manopola), altri cambiano a seguito di interazioni con altri oggetti (la patata può essere sbucciata solo ricorrendo ad un coltello) altri, infine, cambiano spontaneamente per ragioni interne all’oggetto stesso (una patata marcisce spontaneamente dopo un certo tempo). D’altra parte ci sono anche attributi immutabili: il colore di una patata è il giallo, e tale colore, fisso per natura, non può cambiare per nessuna ragione (a meno di ricorrere ai prodigi dell’ingegneria genetica).

 

Le modalità di utilizzo di un Oggetto
Ogni oggetto ha delle specifiche modalità di utilizzo: il coltello può tagliare, il fornello può scaldare, la forchetta può infilzare. Alcuni oggetti hanno una sola modalità di utilizzo, mentre altri ne hanno più di una. Un normale coltello da cucina, tanto per fare un esempio, ha almeno tre modalità di utilizzo: taglia, spalma e infilza.

L’insieme delle modalità di utilizzo di un oggetto caratterizzano il suo comportamento, dal momento che esse forniscono l’unica via per interagire con l’oggetto stesso. Per portare a termine compiti complessi, dobbiamo normalmente utilizzare più oggetti, facendoli interagire tra di loro. Le modalità di utilizzo prevedono di solito limiti espliciti alla possibilità di interazione con altri oggetti: un coltello da cucina permette di sbucciare una mela, ma non di abbattere un albero, cosa che invece è possibile fare con la lama di un machete. Il coltello da cucina e il machete sono oggetti simili tra loro, dal momento che sono entrambi dei coltelli, ma è evidente a livello intuitivo che le rispettive modalità di utilizzo presentano delle differenze.

 

La metafora degli Oggetti nel mondo della Programmazione
Un oggetto è un’entità software dotata di uno stato (i suoi attributi) e di un insieme di metodi che permettono all’utente di interagire con l’oggetto stesso. Lo stato di un oggetto è accessibile solamente attraverso i suoi metodi: per questa ragione spesso gli oggetti vengono rappresentati come dei “contenitori” di attributi che mostrano al proprio esterno solamente i metodi.


Figura 2
- Una possibile rappresentazione grafica di un oggetto software

 

Gli oggetti software, al pari degli oggetti reali, possiedono una loro coerenza, che ne favorisce il corretto utilizzo. Ora procederemo a descrivere come si creano e si utilizzano gli oggetti in un qualunque programma Java, non diverso da quelli che abbiamo visto fino ad ora: sarà interessante notare la somiglianza tra un algoritmo che fa uso di oggetti e la descrizione dell’operazione di cottura di una patata vista nei paragrafi precedenti.

 

Creazione di un Oggetto
Prima di usare un oggetto all’interno di un programma è necessario crearlo. La creazione di un oggetto richiede tre fasi: dichiarazione, creazione ed assegnamento, in modo simile a come avviene per gli array. Vediamo ad esempio le fasi di creazione di un oggetto “finestra”, a partire dalla dichiarazione:

JFrame f;

Grazie alla dichiarazione, ora abbiamo una variabile dello stesso tipo dell’oggetto che stiamo per creare. Tale variabile non è l’oggetto vero e proprio, ma è un reference, ossia una specie di “centrale di controllo” che ci permette di comunicare con l’oggetto. Senza una centrale di controllo, l’oggetto risulta irraggiungibile dal programma: gli oggetti irraggiungibili vengono classificati come rifiuti (garbage) ed eliminati. Come vedremo più avanti, è possibile avere più di un reference allo stesso oggetto, in modo simile a come avviene per gli array. La creazione e l’assegnamento richiedono l’uso della parola riservata new:

f = new JFrame();

Ovviamente possiamo dichiarare, creare ed assegnare un oggetto in un’unica riga:

JFrame f = new JFrame();

Alcuni oggetti richiedono uno o più parametri in fase di creazione: tali parametri sono necessari per impostare lo stato iniziale dell’oggetto. Vediamo ad esempio come si può creare un pulsante grafico che presenti la scritta “Premere Qui”:

JButton b = new JButton(“Premere Qui”);



Figura 3
- Un reference e’ una centrale di controllo che permette
di inviare le direttive all’oggetto vero e proprio


Il comportamento di un Oggetto e i suoi Metodi
Per eseguire un’azione su un oggetto, dobbiamo effettuare una chiamata a metodo. Tale operazione richiede tre informazioni:
1. L’oggetto su cui invocare il metodo
2. Il nome del metodo
3. I parametri richiesti dal metodo stesso

La sintassi della chiamata a metodo in Java ha la forma:

oggetto.metodo(p1,p2,…,pn);

Come vedremo più avanti, i metodi possono anche restituire un valore, come si è già visto nel caso dei metodi static descritti nel capitolo precedente.

Per mostrare un uso concreto della chiamata di metodo su oggetti, possiamo utilizzare gli oggetti creati nel paragrafo precedente, mostrando come sia possibile inserire il bottone b nella finestra f:

f.add(b);

La chiamata a metodo denota un protocollo di comunicazione tra oggetti. Tale protocollo ha un grosso pregio: grazie ad esso, il comportamento di un oggetto software è espresso unicamente attraverso i suoi metodi. Dal momento che l’invocazione di metodo, copre qualunque possibile interazione tra oggetti, è possibile utilizzare questo stesso approccio nel caso di oggetti distribuiti, vale a dire oggetti residenti in macchine differenti collegate in rete.

 

Gli Attributi di un Oggetto e il suo Stato
Gli attributi sono dei valori che descrivono lo stato di un oggetto. In un oggetto software, gli attributi sono accessibili solamente attraverso i metodi: questa proprietà permette a chi progetta l’oggetto di proteggere l’integrità dell’oggetto stesso in almeno due maniere:

1. Impedendo la modifica di un attributo al fine di renderlo accessibile solo in lettura
2. Imponendo delle precise restrizioni ai valori che un determinato attributo può assumere, in modo da vietare l’uso improprio dell’oggetto

Di norma, gli attributi sono accessibili attraverso opportuni metodi, detti “getter” e “setter”, così detti a causa del prefisso “get” o “set”. I metodi getter permettono di leggere il valore di un attributo, i setter di modificarlo. Possiamo stampare a schermo il nome del bottone b usando l’apposito metodo getName():

String nome = b.getName();
System.out.println(nome);

Possiamo anche modificare il titolo e la posizione della finestra f ricorrendo ai seguenti metodi setter:

f.setTitle(“La mia prima finestra”);
f.setPosition(50,50);

Alcuni attributi non possono essere modificati direttamente attraverso un metodo setter, ma cambiano in conseguenza della chiamata un metodo che provoca una transizione di stato complessa, seguendo delle regole che permettono di conservare la consistenza interna dell’oggetto. Se ad esempio si vuole fare in modo che la finestra f assuma una dimensione corretta rispetto ai componenti che essa contiene, basterà chiamare il metodo pack:

f.pack();

Tale metodo, dopo aver controllato il numero e le dimensioni dei componenti contenuti in f, imposterà la dimensione in modo corretto, il tutto in un’unica operazione indivisibile. Se invece si desidera visualizzare la finestra f, si può chiamare il metodo show:

f.show();

Il metodo show effettua tre operazioni indivisibili: convalida la finestra, la mette in primo piano e la rende visibile.

Per chiarire meglio la distinzione tra stato e comportamento di un oggetto, si provi ad immaginare un oggetto software che modella la lampada di un semaforo stradale. Sicuramente esso disporrà di un metodo getColor() per rilevare lo stato della lampada; tuttavia non ha senso che esso disponga del corrispondente metodo setColor(Color c), dal momento che solo alcune transizioni sono permesse, mentre altre sono illegali (ad esempio è possibile passare dal rosso al verde, ma non viceversa). In questa circostanza, sarà di certo più indicato un metodo cambiaColore(), che ad ogni chiamata esegua un cambiamento di stato tra quelli rappresentati in Figura 4.

R->V->G->R


Figura 4 - Regole per il passaggio di stato in un semaforo


Interazioni complesse tra Oggetti
Le istruzioni illustrate nei precedenti paragrafi possono essere condensate in un unico esempio riepilogativo:

import java.awt.*;

public class ProvaFinestra {

  public static void main(String argv[]) {
   Frame f = new Frame();
   Button b = new Button("Premere Qui");

   f.add(b);
   f.setSize(200,150);
   f.setLocation(200,200);
   f.setTitle("Prima Finestra");
   f.show();
  }
}

Come si può notare, l’uso degli oggetti avviene nel familiare contesto della programmazione procedurale, già vista nei precedenti capitoli. Creazione e chiamata a metodo vengono trattate come normali istruzioni. Pertanto è possibile inserire una chiamata a metodo dentro ad un ciclo o come conseguenza di un costrutto condizionale:

for(int i = 0 ; i < 16 ; i++ ) {
  Button b = new Button(“Bottone numero “ + i);
  f.add(b);
}


E’ anche possibile utilizzare come parametro di un oggetto il prodotto di una chiamata a metodo su un altro oggetto; pertanto, le due istruzioni seguenti

String nome = b.getName();
f.setTitle(nome);

possono essere espresse in un’unica riga:

f.setTitle(b.getName());

Si può già notare come gli oggetti aggiungano espressività al codice, e come tendano a fornire una dimensione materiale alle entità altrimenti evanescenti che compongono un programma. Come diverrà più chiaro in seguito, il ricorso agli oggetti fornisce un approccio concettuale di altissimo livello a qualunque contesto applicativo, favorendo la modularità e la riutilizzabilità dei ai vari componenti di un programma. Grazie agli oggetti, il lavoro di programmazione diventa qualcosa di simile ad un gioco di costruzioni ad incastro come il Lego.


Figura 5 -
Il Lego, grazie alla sua modularità,
permettedi realizzare qualunque costruzione
ricorrendo a pezzi standard

 

Vantaggi degli oggetti
Molti degli oggetti che ci stanno attorno, sebbene facili da utilizzare, nascondono al loro interno una complessità enorme. Pensiamo ad un telefono cellulare: per capirne il funzionamento interno è necessaria una laurea in Ingegneria Elettronica, mentre bastano pochi minuti per imparare a comporre un numero e mettersi in contatto con qualcuno. Una caratteristica importante degli oggetti, sia nel mondo reale che nel mondo della programmazione, è quella di nascondere la propria complessità strutturale dietro ad una facciata che rivela solamente le modalità di utilizzo consentite. Questa filosofia di progettazione ha almeno due grossi vantaggi:

  • Imparare ad usare un oggetto è di norma più facile che capire come funziona.
  • Una volta appreso il funzionamento comune ad una classe di oggetti (automobili, telefoni, forbici ecc), saremo in grado di usare senza ulteriori sforzi qualunque oggetto di quella particolare classe (una Fiat Punto, una Renault Clio e così via)

La portata di queste proprietà diverranno chiare non appena inizieremo ad occuparci di progettazione e sviluppo di oggetti.

 

Conclusioni
Questo mese abbiamo finalmente introdotto i concetti base della programmazione ad oggetti. Il mese prossimo daremo un’occhiata ai più importanti oggetti di libreria del linguaggio Java.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it