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