introduzione agli oggetti - programmazione, web marketing ... · alcuni progettisti di linguaggi...

50
CAPITOLO 1 Introduzione agli oggetti I computer, però, più che macchine sono strumenti per amplificare la mente (“biciclette per la mente”, come si compiace di ripetere Steve Jobs) e sono un veicolo espressivo di tipo diverso. Di conseguenza, questi strumenti cominciano a somigliare sempre meno a macchine e sempre più a parti delle nostre menti, avvicinandosi anche ad altre forme di espressione quali la scrittura, la pittura, la scultura, l’animazione e il cinema. La program- mazione orientata agli oggetti (Object-Oriented Programming: OOP) fa parte di questo movimento che va verso l’utilizzo del computer come veicolo espressivo. In questo capitolo vi presentiamo i concetti essenziali della OOP, passando in rassegna anche i metodi di sviluppo. Qui, come nel resto del libro, diamo per scontato che abbiate un po’ di esperienza in un linguaggio di programmazione procedurale, non necessaria- mente il C. Questo capitolo fornisce materiali di inquadramento generale e materiali complementari. Molte persone non si sentono a proprio agio nell’affrontare la programmazione orientata agli oggetti senza prima essersi fatta un’idea del quadro generale. Per questa ragione, qui si presentano molti concetti atti a dare una buona panoramica della OPP. Per contro, molti non riescono ad afferrare i concetti di carattere generale se prima non hanno visto qualche aspetto concreto dei meccanismi; questo genere di persone potrebbe impantanar- si e smarrirsi, se non gli si presenta un po’ di codice sul quale mettere le mani. Se appar- tenete al secondo gruppo e non vedete l’ora di passare alle specifiche del linguaggio, sal- tate pure la lettura di questo capitolo: ciò non vi impedirà di scrivere programmi né di imparare il linguaggio. Tuttavia, farete bene a tornare qui, alla fine, per completare le vo- stre conoscenze e capire perché gli oggetti sono importanti e come si fa a progettare con gli oggetti. Il progresso dell’astrazione Tutti i linguaggi di programmazione forniscono astrazioni. Si può affermare che la com- plessità dei problemi che siete in grado di risolvere è direttamente correlata al genere e alla qualità dell’astrazione. Per “genere” intendo dire “Che cosa stai astraendo?”. Il linguaggio assemblatore è una piccola astrazione della macchina sottostante. Molti linguaggi cosid- detti “asseverativi” che sono venuti dopo (quali il Fortran, il BASIC e il C) erano astrazio- ni del linguaggio assemblatore. Questi linguaggi rappresentano notevoli passi avanti ri- spetto all’assemblatore, ma la loro astrazione primaria continua a imporvi di pensare in La rivoluzione dei computer ebbe la sua genesi in una macchina. Per questa ragione, la genesi dei nostri linguaggi di programmazione tende ad assomigliare a quella macchina. 001ThIJava01.fm Page 1 Tuesday, April 9, 2002 12:07 AM

Upload: phungkhanh

Post on 25-Feb-2019

217 views

Category:

Documents


0 download

TRANSCRIPT

C

APITOLO

1

Introduzione agli oggetti

I computer, però, più che macchine sono strumenti per amplificare la mente (“bicicletteper la mente”, come si compiace di ripetere Steve Jobs) e sono un veicolo espressivo ditipo diverso. Di conseguenza, questi strumenti cominciano a somigliare sempre meno amacchine e sempre più a parti delle nostre menti, avvicinandosi anche ad altre forme diespressione quali la scrittura, la pittura, la scultura, l’animazione e il cinema. La program-mazione orientata agli oggetti (Object-Oriented Programming: OOP) fa parte di questomovimento che va verso l’utilizzo del computer come veicolo espressivo.In questo capitolo vi presentiamo i concetti essenziali della OOP, passando in rassegnaanche i metodi di sviluppo. Qui, come nel resto del libro, diamo per scontato che abbiateun po’ di esperienza in un linguaggio di programmazione procedurale, non necessaria-mente il C.Questo capitolo fornisce materiali di inquadramento generale e materiali complementari.Molte persone non si sentono a proprio agio nell’affrontare la programmazione orientataagli oggetti senza prima essersi fatta un’idea del quadro generale. Per questa ragione, quisi presentano molti concetti atti a dare una buona panoramica della OPP. Per contro,molti non riescono ad afferrare i concetti di carattere generale se prima non hanno vistoqualche aspetto concreto dei meccanismi; questo genere di persone potrebbe impantanar-si e smarrirsi, se non gli si presenta un po’ di codice sul quale mettere le mani. Se appar-tenete al secondo gruppo e non vedete l’ora di passare alle specifiche del linguaggio, sal-tate pure la lettura di questo capitolo: ciò non vi impedirà di scrivere programmi né diimparare il linguaggio. Tuttavia, farete bene a tornare qui, alla fine, per completare le vo-stre conoscenze e capire perché gli oggetti sono importanti e come si fa a progettare congli oggetti.

Il progresso dell’astrazione

Tutti i linguaggi di programmazione forniscono astrazioni. Si può affermare che la com-plessità dei problemi che siete in grado di risolvere è direttamente correlata al genere e allaqualità dell’astrazione. Per “genere” intendo dire “Che cosa stai astraendo?”. Il linguaggioassemblatore è una piccola astrazione della macchina sottostante. Molti linguaggi cosid-detti “asseverativi” che sono venuti dopo (quali il Fortran, il BASIC e il C) erano astrazio-ni del linguaggio assemblatore. Questi linguaggi rappresentano notevoli passi avanti ri-spetto all’assemblatore, ma la loro astrazione primaria continua a imporvi di pensare in

La rivoluzione dei computer ebbe la sua genesi in una macchina. Per questa ragione, lagenesi dei nostri linguaggi di programmazione tende ad assomigliare a quella macchina.

001ThIJava01.fm Page 1 Tuesday, April 9, 2002 12:07 AM

C

APITOLO

1

2

termini di struttura del computer invece che della struttura del problema che state cercan-do di risolvere. Tocca al programmatore stabilire l’associazione fra il modello della mac-china (nello “spazio della soluzione”, che è il luogo nel quale si modella quel problema,quindi un computer) e il modello del problema che viene effettivamente risolto (nello“spazio del problema”, che è il luogo nel quale il problema esiste). L’impegno che vienerichiesto per stabilire questa corrispondenza e il fatto che sia estrinseca al linguaggio diprogrammazione, porta a creare programmi difficili da scrivere e costosi da gestire e, comeeffetto collaterale, ha creato l’intero settore industriale dei “metodi di programmazione”.L’alternativa al modello basato sulla macchina consiste nel creare il modello sul problemache si sta cercando di risolvere. I primi linguaggi tipo LISP e APL scelsero specifiche vi-sioni del mondo (“Tutti i problemi in ultima analisi sono liste” oppure “Tutti i problemisono di natura algoritmica”, rispettivamente.) Il PROLOG riconduce tutti i problemi acatene di decisioni. Si sono creati linguaggi per la programmazione basata sui vincoli e perprogrammare manipolando esclusivamente simboli grafici (quest’ultima impostazione siè dimostrata troppo restrittiva). Ciascuno di questi approcci può essere un’ottima soluzio-ne per risolvere quella particolare classe di problemi per la quale è stato concepito, ma nonappena si esce da quell’ambito l’approccio si dimostra goffo e inadeguato.L’approccio orientato agli oggetti si spinge oltre, mettendo a disposizione strumenti coni quali il programmatore può rappresentare elementi nello spazio dei problemi. Questarappresentazione è sufficientemente generalizzata da non vincolare il programmatore a oc-cuparsi soltanto di un determinato tipo di problema. Chiamiamo “oggetti” gli elementiche si trovano nello spazio dei problemi e le loro rappresentazioni nello spazio delle solu-zioni. (Naturalmente, occorrono anche altri oggetti che non hanno corrispondenti analo-ghi nello spazio dei problemi.) L’idea è quella di consentire al programma di adattarsi dasolo al gergo del problema aggiungendo nuovi tipi di oggetti, in modo che, quando leggeteil codice che descrive la soluzione, leggete parole che esprimono anche il problema. Questaè una forma di astrazione nel linguaggio più flessibile e potente di quelle che sono esistitein precedenza. L’OOP, quindi, vi permette di descrivere il problema nei suoi termini, in-vece che nei termini del computer sul quale verrà eseguita la soluzione. Ci si ricollega sem-pre al computer, però. Ciascun oggetto assomiglia un po’ a un piccolo computer; ha unostato e ha operazioni che gli si può chiedere di eseguire. In fondo, non è impropria l’ana-logia con gli oggetti del mondo reale: anch’essi hanno caratteristiche e comportamenti.Alcuni progettisti di linguaggi hanno stabilito che la programmazione orientata agli og-getti da sola non è adeguata per risolvere agevolmente tutti i problemi di programmazionee caldeggiano la combinazione di vari approcci in linguaggi di programmazione

multipa-radigma

.

1

Alan Kay ha riepilogato cinque caratteristiche essenziali di Smalltalk, il primo linguaggioorientato agli oggetti che ha avuto successo e uno dei linguaggi sul quale è basato Java;queste caratteristiche rappresentano un approccio puro alla programmazione orientataagli oggetti:

1.

Tutte le cose sono oggetti.

Pensate a un oggetto come a una variabile particolare;memorizza dati, ma potete “fare richieste” a quell’oggetto, domandandogli di esegui-re operazioni su se stesso. In teoria, potete prendere qualunque componente concet-tuale del problema che state cercando di risolvere (cani, edifici, servizi ecc.) e rappre-sentarlo come oggetto nel vostro programma.

1

Vedere

Multiparadigm Programming in Leda

, di Timothy Budd (Addison-Wesley, 1995)

001ThIJava01.fm Page 2 Tuesday, April 9, 2002 12:07 AM

I

NTRODUZIONE

AGLI

OGGETTI

3

2.

Un programma è un mucchio di oggetti che si dicono l’un l’altro che cosa fareinviandosi messaggi.

Per fare una richiesta a un oggetto, gli “si manda un messag-gio”. Più concretamente, potete considerare un messaggio come un invito a chiamareuna funzione che appartiene a un oggetto particolare.

3.

Ciascun oggetto ha la sua memoria formata da altri oggetti.

Detto in altri termi-ni, per creare un nuovo tipo di oggetto formate un pacchetto che contiene oggetti esi-stenti. In questo modo, potete arricchire di complessità un programma, nasconden-dola dietro la semplicità degli oggetti.

4.

Ogni oggetto ha un tipo.

Nel gergo, si dice che ciascun oggetto è un’

istanza

di una

classe

, dove “classe” è sinonimo di “tipo”. La caratteristica più importante che distin-gue una classe è “Quali messaggi potete inviarle?”.

5.

Tutti gli oggetti di un tipo determinato possono ricevere gli stessi messaggi.

Questa è un’affermazione non poco impegnativa, come vedrete più avanti. Siccomeun oggetto di tipo “cerchio” è anche un oggetto di tipo “forma”, siamo sicuri che uncerchio può accettare i messaggi delle forme. Questo significa che potete scrivere co-dice che parla alle forme e che gestisce automaticamente qualunque cosa che corri-sponda alla descrizione di una forma. Questa

sostituibilità

è uno dei concetti più im-portanti dell’OOP.

Un oggetto ha un’interfaccia

Aristotele fu probabilmente il primo a studiare con attenzione il concetto di “tipo”; par-lava della “classe dei pesci e della classe degli uccelli”. L’idea che tutti gli oggetti, pur es-sendo diversi gli uni dagli altri, appartengono anche a una classe di oggetti con caratteri-stiche e comportamenti comuni, venne usata esplicitamente nel primo linguaggio orien-tato agli oggetti, il Simula-67, la cui parola chiave fondamentale

class

introduce un nuo-vo tipo in un programma.Il Simula, come lascia intendere il suo nome, venne creato per sviluppare simulazioni, co-me per esempio il classico “problema dell’operatore di sportello bancario”. In questo pro-blema sono presenti operatori, clienti, conti, transazioni e unità monetarie: un mucchiodi “oggetti”. Gli oggetti che sono identici eccetto che per il loro stato durante l’esecuzionedi un programma sono raggruppati insieme in “classi di oggetti” ed è da lì che viene laparola chiave

class

. La creazione di tipi di dati astratti (classi) è un concetto fondamentalenella programmazione orientata agli oggetti. I tipi di dati astratti operano esattamente co-me i tipi intrinseci: potete creare variabili di un tipo (chiamate

oggetti

o

istanze

nel gergoorientato agli oggetti) e manipolare tali variabili (si parla di

inviare messaggi

o

richieste

; voiinviate un messaggio e l’oggetto capisce che cosa farne). I membri (gli elementi) di ciascu-na classe condividono alcuni aspetti comuni: tutti i conti hanno un saldo, tutti gli spor-telli possono accettare versamenti, ecc. Nello stesso tempo, ciascun elemento ha il propriostato, tutti i conti hanno un saldo diverso, tutti gli operatori hanno un nome. Di conse-guenza operatori, clienti, conti, transazioni ecc. possono tutti essere rappresentati conun’entità univoca nel programma del computer. Questa entità è l’oggetto e ciascun ogget-to appartiene a una classe particolare che ne definisce caratteristiche e comportamenti.Quindi, sebbene quel che in realtà facciamo nella programmazione orientata agli oggettiè creare nuovi tipi di dati, virtualmente tutti i linguaggi di programmazione orientata agli

001ThIJava01.fm Page 3 Tuesday, April 9, 2002 12:07 AM

C

APITOLO

1

4

oggetti utilizzano la parola chiave “class”. Quando vedete la parola “tipo” pensate a “clas-se” e viceversa

2

.Dal momento che una classe descrive un insieme di oggetti che hanno caratteristiche (ele-menti di dati) e comportamenti (funzionalità) identici, una classe è in realtà un tipo didato perché anche un numero in virgola mobile, per esempio, ha un insieme di caratteri-stiche e di comportamenti. La differenza sta nel fatto che un programmatore definisceuna classe affinché si adatti a un problema, invece di essere costretto a utilizzare un tipodi dato esistente che era stato concepito per rappresentare una unità di memorizzazionein una macchina. Estendete il linguaggio di programmazione aggiungendo nuovi tipi didati specifici dei vostri fabbisogni. Il sistema di programmazione accetta volentieri le nuo-ve classi e dà loro tutte le attenzioni e i controlli che esercita sui tipi intrinseci.L’approccio orientato agli oggetti non si limita a costruire simulazioni. Che accettiate op-pure no l’idea che qualsiasi programma sia una simulazione del sistema che state descri-vendo, l’utilizzo di tecniche OOP può agevolmente ridurre a una semplice soluzione unampio insieme di problemi.Quando avete stabilito una classe, potete creare tanti oggetti di quella classe quanti ne vo-lete e poi manipolarli come se fossero gli elementi del problema che state cercando di ri-solvere. In effetti, una delle sfide della programmazione orientata agli oggetti consiste nelcreare una corrispondenza biunivoca fra gli elementi nello spazio dei problemi e gli ogget-ti nello spazio delle soluzioni.Ma come potete far sì che un oggetto esegua per voi un lavoro utile? Deve esistere un mo-do per chiedere a un oggetto di fare qualcosa, portare a termine una transazione, peresempio, oppure tracciare un segno sullo schermo o girare un interruttore. E ciascun og-getto può soddisfare soltanto determinate richieste. Le richieste che potete fare a un og-getto sono definite dalla sua

interfaccia

ed è il tipo a determinare l’interfaccia. Un esempiosemplice potrebbe essere la rappresentazione di una lampadina:

Luce lu = new Luce();lu.accesa();

L’interfaccia stabilisce

quali

richieste potete rivolgere a un determinato oggetto. Deve, pe-rò, esistere da qualche parte un codice che soddisfi tali richieste. Questo codice, insiemecon i dati nascosti, costituisce l’

implementazione

. Dal punto di vista della programmazio-ne procedurale non c’è niente di complicato. Un tipo ha una funzione associata con cia-scuna richiesta possibile e quando chiedete qualcosa a un oggetto viene chiamata la fun-zione corrispondente. Questo processo si riassume di solito dicendo che si “invia un mes-saggio” (si fa una richiesta) a un oggetto, che provvede poi a capire come reagire a quelmessaggio (esegue il codice).

2

C’è chi fa una distinzione, affermando che il tipo determina l’interfaccia mentre la classe è una imple-mentazione particolare di quell’interfaccia.

Luce

accesa( )spenta( )intensifica( )attenua( )

Nome tipo

Interfaccia

001ThIJava01.fm Page 4 Tuesday, April 9, 2002 12:07 AM

I

NTRODUZIONE

AGLI

OGGETTI

5

In questo caso il nome del tipo/classe è

Luce

, il nome di questo particolare oggetto

Luce

è

lu

e le richieste che potete fare a un oggetto

Luce

sono di accendersi, spegnersi, intensi-ficare la luminosità o attenuarla. Create un oggetto

Luce

definendo un “riferimento” (

lu

)per quell’oggetto e chiamando

new

per chiedere un nuovo oggetto di quel tipo. Per invia-re un messaggio all’oggetto, ne scrivete il nome collegandolo con la richiesta di messaggiomediante un punto (dot). Dal punto di vista di chi utilizza una classe predefinita, questoè tutto ciò che si deve fare per programmare con gli oggetti.Lo schema grafico precedente segue il formato dell’

Unified Modeling Language

(ULM).Ciascuna classe è rappresentata da un riquadro, che contiene il nome del tipo nella sezio-ne superiore, eventuali elementi di dati che si vogliono descrivere nella sezione mediana ele

funzioni membro

(le funzioni che appartengono a questo oggetto, che ricevono gli even-tuali messaggi inviati all’oggetto) nella sezione inferiore del riquadro. Spesso nei diagram-mi di progettazione ULM si indicano soltanto il nome della classe e le funzioni membropubbliche, senza mostrare la sezione mediana. Se interessa soltanto il nome della classe,non viene mostrata neppure la sezione inferiore.

L’implementazione nascosta

È utile ripartire il campo di gioco fra

creatori di classi

(coloro che creano nuovi tipi di dati)e

programmatori client

3

(i consumatori di classi che utilizzano i tipi di dati nelle loro ap-plicazioni). L’obiettivo del programmatore client è mettere insieme una cassetta di attrez-zi piena di classi da utilizzare per sviluppare rapidamente le applicazioni. L’obiettivo delcreatore di classi è costruire una classe che espone soltanto quello che è necessario al pro-grammatore client, mantenendo nascosto tutto il resto. Perché? Perché se è nascosto ilprogrammatore client non può utilizzarlo, il che significa che il creatore della classe puòmodificare a suo piacimento la porzione nascosta senza preoccuparsi dell’effetto che talimodifiche potrebbero avere su chiunque altro. La porzione nascosta di solito è formatadai delicati meccanismi interni di un oggetto, che potrebbero venire facilmente alterati daun programmatore client incauto o poco informato, quindi nascondendo l’implementa-zione si limitano i bachi nei programmi. Il concetto di occultamento dell’implementazio-ne non va preso alla leggera. In qualunque relazione è importante stabilire confini chevengano rispettati da tutte le parti in causa. Quando create una libreria, stabilite una re-lazione col programmatore client, che è anche lui un programmatore, ma è uno che stamettendo insieme un’applicazione utilizzando la vostra libreria, magari per costruirneuna più grande.Se tutti i membri di una classe sono disponibili per chiunque, il programmatore clientpuò fare qualunque cosa con quella classe e non c’è modi di imporre delle regole. Anchese potreste preferire che il programmatore client non manipoli direttamente qualchemembro della vostra classe, senza un controllo dell’accesso non c’è modo di evitarlo.È tutto alla luce del sole.Il controllo dell’accesso, quindi, serve in primo luogo per tenere le mani dei programma-tori client lontane dalle porzioni che non dovrebbero toccare: le parti che sono necessarieper il funzionamento interno del tipo di dati, ma che non fanno parte dell’interfaccia cheserve agli utenti per risolvere i loro specifici problemi. In questo modo si rende, in realtà,un servizio agli utenti, perché possono agevolmente vedere quello che è importante perloro e quello che possono ignorare.

3

Ho preso in prestito questo termine dal mio amico Scott Meyers.

001ThIJava01.fm Page 5 Tuesday, April 9, 2002 12:07 AM

C

APITOLO

1

6

La seconda ragione che giustifica il controllo dell’accesso è il fatto che consente ai proget-tisti di librerie di modificare i meccanismi interni delle classi senza preoccuparsi del modoin cui si rifletteranno sul programmatore client. Per esempio, potreste implementare unadeterminata classe in un modo semplice per agevolare lo sviluppo, salvo scoprire poi chedovete riscriverla per far sì che si esegua più rapidamente. Se l’interfaccia e l’implementa-zione sono nettamente separate e protette, potete ottenere facilmente questo risultato.Java utilizza tre parole chiave esplicite per stabilire i confini in una classe:

public

,

private

e

protected

, il cui utilizzo e significato sono chiari e intuitivi. Questi

specificatori dell’accesso

determinano chi può utilizzare le definizioni che li seguono. Con

public

si stabilisce che ledefinizioni che seguono sono disponibili per tutti. La parola chiave

private

, per contro, si-gnifica che nessuno può accedere a quelle definizioni, tranne voi, il creatore del tipo. La pa-rola

private

è un muro di mattoni pieni eretto fra voi e il programmatore client. Se qual-cuno cerca di accedere a un membro

private

, riceve una segnalazione di errore di compila-zione. La parola

protected

agisce come

private

, con l’eccezione che una classe ereditanteha accesso a membri

protected

, ma non a membri

private

. Parleremo fra breve dell’eredi-tarietà.Java dispone anche di un accesso predefinito o di “default”, che entra in gioco se non siutilizza alcuno degli specificatori elencati prima. In tali casi si parla talvolta di accesso“amichevole”, perché le classi possono accedere ai membri amichevoli di altre classi nellostesso pacchetto, però all’esterno del pacchetto gli stessi membri amichevoli si presentanocome

private

.

Riutilizzare l’implementazione

Quando una classe è stata creata e collaudata, dovrebbe (idealmente) rappresentare unutile elemento di codice. Ci si è resi conto che la riutilizzabilità non è poi così facile daottenere come molti vorrebbero sperare; ci vogliono esperienza e intuizione per produrreun buon progetto. Quando, però, riuscite a ottenere un progetto del genere, non chiedealtro che di essere riutilizzato. Il riutilizzo del codice è uno dei maggiori vantaggi offertidai linguaggi di programmazione orientati agli oggetti.Il modo più semplice per riutilizzare una classe consiste nell’utilizzare direttamente un og-getto di quella classe, però è anche possibile collocare un oggetto di quella classe in unanuova classe. Questo lo chiamiamo “creare un oggetto membro”. La nostra nuova classepuò essere composta da un qualunque numero di altri oggetti e di tipi, in qualsiasi com-binazione che possa servirci per ottenere la funzionalità che desideriamo avere nella nostranuova classe. Siccome si compone una nuova classe a partire da classi esistenti, questo con-cetto prende il nome di

composizione

(o, più in generale,

aggregazione

). Talvolta si dice chela composizione è una relazione di tipo “ha un”, come in “un’automobile ha un motore”.

(Il diagramma ULM qui sopra indica la composizione con il rombo, che asserisce che esi-ste un’automobile. Di norma utilizzerò uno schema grafico più semplice: una semplicelinea, senza il rombo, per indicare un’associazione.

4

)

4

Di solito la maggior parte dei diagrammi ha già abbastanza dettagli, quindi non avrete bisogno di esserespecifici indicando se utilizzate l’aggregazione o la composizione.

Automobile Motore

001ThIJava01.fm Page 6 Tuesday, April 9, 2002 12:07 AM

I

NTRODUZIONE

AGLI

OGGETTI

7

La composizione consente una notevole dose di flessibilità. Gli oggetti membri della no-stra nuova classe di solito sono privati, il che li rende inaccessibili ai programmatori clientche utilizzano la classe. Ciò consente di cambiare i membri senza disturbare il codiceclient esistente. Si possono modificare gli oggetti membri anche in fase di esecuzione, mo-dificando così dinamicamente il comportamento del programma. L’ereditarietà, di cuiparleremo fra poco, non ha questa flessibilità, perché il compilatore deve fissare vincoli infase di compilazione sulle classi create con l’ereditarietà. Siccome l’ereditarietà è molto importante nella programmazione orientata agli oggetti,viene spesso notevolmente enfatizzata e i nuovi programmatori possono farsi l’idea chel’ereditarietà si debba usare dappertutto. Ciò può portare a progetti goffi ed eccessiva-mente complicati. Quando create nuove classi dovreste, invece, prendere in considerazio-ne in primo luogo la composizione, perché è più semplice e più flessibile. Se seguite que-sto approccio, avrete progetti più puliti. Quando avrete fatto un po’ di esperienza, vi ri-sulteranno evidenti i casi nei quali avrete bisogno dell’ereditarietà.

Ereditarietà: riutilizzare l’interfaccia

Di per sé, l’idea dell’oggetto è un comodo strumento. Vi consente di confezionare insiemedati e funzionalità in base a un

concetto

, per cui potete rappresentare in modo appropriatoun’idea dello spazio dei problemi invece di essere costretti a utilizzare gli idiotismi dellamacchina sottostante. Questi concetti sono espressi come elementi base nel linguaggio diprogrammazione usando la parola chiave

class

.È un vero peccato, però, darsi tanto da fare per creare una classe e poi essere costretti acrearne una nuova di zecca che potrebbe avere una funzionalità simile. Sarebbe più gra-devole se potessimo prendere la classe esistente, clonarla e poi fare aggiunte e modifiche aquel clone. Ed è proprio questo che in effetti otteniamo con l’ereditarietà, eccetto che sela classe originale (chiamata classe

base

o

superclasse

o classe

genitore

) viene modificata, an-che il “clone” modificato (chiamato classe

derivata

o

ereditata

o

sottoclasse

o classe

figlia

)riflette quelle modifiche.

(La freccia in questo diagramma ULM punta dalla classe derivata alla classe base. Comevedrete, può esserci più di una sola classe derivata.)Un tipo non si limita a descrivere i vincoli su un insieme di oggetti; ha anche una relazio-ne con altri tipi. Due tipi possono avere caratteristiche e comportamenti in comune, mauno può contenere più caratteristiche dell’altro e può anche gestire più messaggi (o gestir-li in modo diverso). L’ereditarietà esprime questa somiglianza fra i tipi utilizzando i con-cetti di tipi base e tipi derivati. Un tipo base contiene tutte le caratteristiche e i compor-tamenti che sono condivisi dai tipi che da esso derivano. Creiamo un tipo base per rap-presentare il nucleo essenziale delle nostre idee su determinati oggetti nel nostro sistema.Dal tipo base, deriviamo altri tipi per esprimere i diversi modi nei quali questo nucleopuò essere realizzato.

Base

Derivata

001ThIJava01.fm Page 7 Tuesday, April 9, 2002 12:07 AM

C

APITOLO

1

8

Per esempio, una macchina per riciclare rifiuti seleziona vari tipi di rifiuti. Il tipo base è“rifiuto” e ciascun pezzo di rifiuto ha un peso, un valore e così via e può essere frantuma-to, fuso o sciolto. Da questo, vengono derivati tipi di rifiuti più specifici che possono ave-re ulteriori caratteristiche (una bottiglia ha un colore) o comportamenti (una scatola dialluminio può essere frantumata, una scatola di acciaio è magnetica). Utilizzando l’eredi-tarietà potete costruire una gerarchia di tipi che esprime nei termini dei suoi tipi il pro-blema che state cercando di risolvere.Un secondo esempio è quello classico della “sagoma”, talvolta usata in un sistema di pro-gettazione industriale assistita dal computer o in un gioco di simulazione. Il tipo base è “sa-goma” e ogni sagoma ha una dimensione, un colore, una posizione e così via. Ciascuna sa-goma può essere disegnata, cancellata, spostata, colorata ecc. Da questa, vengono derivati(ereditati) tipi specifici di sagome: cerchi, quadrati, triangoli e così via, ciascuno dei qualipuò avere ulteriori caratteristiche e comportamenti. Certe sagome possono essere ribaltate,per esempio. Alcuni comportamenti possono essere diversi, come quando si vuole calcolarel’area di una sagoma. La gerarchia dei tipi incorpora sia le somiglianze sia le differenze fra lesagome.

Formulare la soluzione negli stessi termini del problema è un grande vantaggio, perchénon avete bisogno di un sacco di modelli intermedi per passare da una descrizione delproblema a una descrizione della soluzione. Nel caso degli oggetti, la gerarchia dei tipi èil modello primario, per cui andate direttamente dalla descrizione del problema nel mon-do reale alla descrizione del sistema in codice. Addirittura, una delle difficoltà che le per-sone si trovano ad affrontare nella progettazione orientata agli oggetti è proprio il fattoche troppo semplice andare dall’inizio alla fine. Una mente addestrata a cercare soluzionicomplesse spesso viene messa in imbarazzo da questa semplicità, in prima battuta.Quando ereditate da un tipo esistente,ne create uno nuovo. Questo nuovo tipo contienenon soltanto tutti gli elementi (i membri) del tipo esistente (sebbene quelli private sianonascosti e inaccessibili), ma, cosa più importante, duplica l’interfaccia della classe base.Vale a dire, tutti i messaggi che potete inviare a oggetti della classe base potete inviarli an-che a oggetti della classe derivata. Siccome il tipo di una classe è identificato dai messaggiche gli si possono inviare, questo vuol dire che la classe derivata è dello stesso tipo della classebase. Riferendoci all’esempio di prima, “un cerchio è una sagoma”. Questa equivalenzadei tipi tramite l’ereditarietà è uno dei passaggi essenziali per capire il significato della pro-grammazione orientata agli oggetti.Dal momento che sia la classe base sia quella derivata hanno la stessa interfaccia, deve es-serci una qualche implementazione che si accompagni con quell’interfaccia. Vale a dire,deve esistere un po’ di codice da eseguire quando un oggetto riceve un determinato mes-

Cerchio Quadrato Triangolo

Sagoma

disegna( )cancella( )sposta( )prendiColore( )impostaColore( )

001ThIJava01.fm Page 8 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 9

saggio. Se ereditate semplicemente una classe e non fate nient’altro, i metodi dell’interfac-cia della classe base si ritrovano direttamente nella classe derivata. Ciò vuol dire che glioggetti della classe derivata non solo hanno lo stesso tipo, ma hanno anche lo stesso com-portamento, il che non è particolarmente interessante. Avete due modi per differenziarela vostra nuova classe derivata dalla classe base originale. Il primo è molto diretto: aggiun-gere semplicemente funzioni nuove di zecca alla classe derivata. Queste nuove funzioninon non fanno parte dell’interfaccia della classe base. Ciò vuol dire che la classe base nonfaceva tutto quello che volevate che facesse e quindi avete aggiunto più funzioni. Questoutilizzo semplice e primitivo dell’ereditarietà è, a volte, la soluzione perfetta per il vostroproblema. Tuttavia, dovreste verificare con cura la possibilità che anche la classe base pos-sa aver bisogno di queste funzioni addizionali. Questo modo di procedere nella progetta-zione per scoperte e iterazioni accade regolarmente nella programmazione orientata aglioggetti.

Sebbene l’ereditarietà possa talvolta significare (specialmente in Java, dove la parola chia-ve che indica l’ereditarietà è extends) che aggiungerete nuove funzioni all’interfaccia,questo non è necessariamente sempre vero. Il secondo, e più importante, modo per diffe-renziare una nuova classe consiste nel cambiare il comportamento di una funzione dellaclasse base. In questo caso si parla di forzare quella funzione.

Cerchio Quadrato Triangolo

Sagoma

disegna( )cancella( )sposta( )prendiColore( )impostaColore( )

CapovolgiVerticale( )CapovolgiOrizzontale( )

Cerchio Quadrato Triangolo

Sagoma

disegna( )cancella( )sposta( )prendiColore( )impostaColore( )

disegna( )cancella( )

disegna( )cancella( )

disegna( )cancella( )

001ThIJava01.fm Page 9 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 110

Per forzare una funzione, non dovete fare altro che creare una nuova definizione per quellafunzione nella classe derivata. Quel che dite è “Qui utilizzo la stessa funzione di interfac-cia, però voglio fare qualcosa di diverso per il mio nuovo tipo”.

Relazioni “è un” rispetto a “è come un”In merito all’ereditarietà può sorgere una discussione: l’ereditarietà deve forzare soltanto lefunzioni della classe base (e non aggiungere nuove funzioni che non si trovano nella classebase)? Questo vorrebbe dire che il tipo derivato è esattamente uguale alla classe base, dalmomento che ha esattamente la stessa interfaccia. Di conseguenza, potete sostituire esat-tamente un oggetto della classe derivata per un oggetto della classe base. Si tratterebbe diuna pura sostituzione e spesso si parla di principio di sostituzione. In un certo senso, è que-sto il modo ideale per trattare l’ereditarietà. Spesso, in casi del genere, chiamiamo relazio-ne è un la relazione fra la classe base e la classe derivata, perché possiamo dire “un cerchioè una sagoma”. Un test dell’ereditarietà consiste nello stabilire se si può dichiarare una re-lazione “è un” fra le classi e verificare se ha senso.Vi sono casi in cui dovete per forza aggiungere nuovi elementi di interfaccia a un tipo de-rivato, estendendo in questo modo l’interfaccia e creando un nuovo tipo. Il nuovo tipopuò ancora essere sostituito al tipo base, però la sostituzione non è perfetta, perché le vo-stre nuove funzioni non sono accessibili dal tipo base. In questo caso si potrebbe parlaredi una relazione è come un5; il nuovo tipo ha l’interfaccia del vecchio, ma contiene anchealtre funzioni, quindi non potete in realtà dire che sia esattamente lo stesso. Per esempio,considerate un condizionatore d’aria. Supponete che la vostra casa sia cablata con tutti icontrolli per il raffreddamento; vale a dire, ha un’interfaccia che vi consente di regolare ilraffreddamento. Immaginate che il condizionatore d’aria si rompa e che lo rimpiazziatecon un climatizzatore, che può raffreddare e riscaldare. Il climatizzatore è come un condi-zionatore d’aria, però può fare di più. Siccome il sistema di controllo della vostra casa èstato progettato per regolare soltanto il raffreddamento, è limitato a comunicare solo conla parte raffreddante del nuovo oggetto. L’interfaccia del nuovo oggetto è stata ampliata eil sistema esistente non ne sa nulla, essendo ancorato all’interfaccia originale.

5 Termine mio.

Climatizzatore

raffredda( )riscalda( )

Termostato

diminuisciTemperatura( )

Condizionatore

raffredda( )

Sistema di raffreddamento

raffredda( )

controlla

001ThIJava01.fm Page 10 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 11

Naturalmente, quando esaminate questo schema, appare chiaro che la classe base “sistemadi raffreddamento” non è abbastanza generale e bisognerebbe cambiarle il nome in “siste-ma per il controllo della temperatura”, in modo che possa comprendere anche il riscalda-mento: a questo punto il principio di sostituzione funzionerebbe. Il diagramma preceden-te è un esempio di quel che può accadere nella progettazione e nel mondo reale.Alla luce del principio di sostituzione è facile convincersi che questo approccio (la purasostituzione) sia l’unico modo per fare le cose e in effetti è gradevole quando il progetto sisviluppa in questo modo. Però vi accorgerete che, a volte, è altrettanto chiaro che doveteaggiungere nuove funzioni all’interfaccia di una classe derivata. La differenza fra i due casidovrebbe risultare ragionevolmente ovvia.

Oggetti intercambiabili grazie al polimorfismoQuando avete a che fare con le gerarchie dei tipi, spesso avete bisogno di trattare un og-getto non come il tipo specifico che è, ma come il suo tipo base. Ciò vi permette di scri-vere codice che non dipende da tipi specifici. Nell’esempio delle sagome, le funzioni ma-nipolano sagome generiche, senza tener conto del fatto che siano cerchi, quadrati, trian-goli o magari sagome che non sono ancora state definite. Tutte le sagome possono esseredisegnate, cancellate e spostate, quindi queste funzioni non fanno altro che inviare unmessaggio a un oggetto sagoma; non si preoccupano del modo in cui l’oggetto reagisce almessaggio.Un codice di questo genere non risente dell’aggiunta di nuovi tipi e l’aggiunta di nuovi tipiè il modo più consueto con cui estendere un programma orientato agli oggetti per gestiresituazioni nuove. Per esempio, potete derivare un nuovo sottotipo di sagoma chiamatopentagono senza modificare le funzioni che agiscono soltanto sulle sagome generiche.Questa possibilità di estendere agevolmente un programma derivando nuovi sottotipi èimportante, perché potenzia notevolmente la progettazione, riducendo al tempo stesso ilcosto della manutenzione del software.C’è, però, un problema quando si tenta di trattare oggetti di tipo derivato come i loro tipibase generici (cerchi come sagome, biciclette come veicoli, cormorani come volatili, ecc.).Se una funzione deve dire a una sagoma generica di tracciarsi, o a un veicolo generico disterzare o a un volatile generico di spostarsi, il compilatore non può sapere con esattezzaal momento della compilazione quale frammento di codice verrà eseguito. La questionesta tutta qui: quando il messaggio viene inviato, il programmatore non ha bisogno di sa-pere quale frammento di codice verrà eseguito; la funzione di disegno può essere applicataindifferentemente a un cerchio, a un quadrato o a un triangolo e l’oggetto eseguirà il co-dice opportuno in relazione al suo tipo specifico. Se non avete bisogno di sapere qualeframmento di codice verrà eseguito, quando aggiungete un nuovo sottotipo il codice chequesto esegue può essere diverso senza che si debba modificare la chiamata della funzione.Per cui il compilatore non può sapere con esattezza quale frammento di codice viene ese-guito e quindi che cosa fa? Per esempio, nel diagramma che segue l’oggetto Controllore-Pennuti lavora su oggetti Pennuto generici e non sa di quali tipi siano. Ciò è comodo dalpunto di vista del ControllorePennuti, perché non ha bisogno di scrivere un codice spe-ciale per stabilire l’esatto tipo di Pennuto col quale sta lavorando, né di conoscere il com-portamento del Pennuto. E allora come mai quando viene chiamato sposta( ) pur igno-rando il tipo specifico di Pennuto, si manifesta il comportamento giusto (un’Anitra cor-re, vola o nuota e un Pinguino corre o nuota)?

001ThIJava01.fm Page 11 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 112

La risposta viene dalla svolta fondamentale della programmazione orientata agli oggetti:il compilatore non può fare una chiamata di funzione nel senso tradizionale. La chiamatadi funzione generata da un compilatore non OOP provoca un effetto chiamato early bin-ding (concatenamento anticipato o statico), un termine che potreste non aver mai sentitoprima perché non potevate pensarlo diversamente. Significa che il compilatore generauna chiamata al nome di una funzione specifica e il linker risolve questa chiamata nell’in-dirizzo assoluto del codice da eseguire. Nella OOP, il programma non può determinarel’indirizzo del codice fino al momento dell’esecuzione, quindi bisogna ricorrere a un mec-canismo diverso quando si manda un messaggio a un oggetto generico. Per risolvere ilproblema, i linguaggi orientati agli oggetti utilizzano il concetto di late binding (collega-mento ritardato o dinamico). Quando inviate un messaggio a un oggetto, il codice cheviene chiamato non è determinato fino al momento dell’esecuzione. Il compilatore garan-tisce senz’altro che la funzione esista ed esegue i controlli sul tipo degli argomenti e delvalore restituito (un linguaggio nel quale questo non accade si dice a tipizzazione debole),però non conosce l’esatto codice da eseguire.Per eseguire il collegamento ritardato, Java utilizza uno speciale elemento di codice invecedi una chiamata assoluta. Questo codice calcola l’indirizzo del corpo della funzione, uti-lizzando informazioni immagazzinate nell’oggetto (questo processo è trattato diffusamen-te nel Capitolo 7). Di conseguenza, ciascun oggetto può comportarsi in modo diverso inbase al contenuto di quel particolare elemento di codice. Quando inviate un messaggio aun oggetto, è l’oggetto che provvede a capire che cosa farsene di quel messaggio.In alcuni linguaggi (il C++, in particolare) dovete dichiarare esplicitamente che volete cheuna funzione abbia la flessibilità che deriva dal collegamento ritardato. In questi linguag-gi, per impostazione predefinita, le funzioni non sono concatenate dinamicamente. Ciòha creato qualche problema e quindi in Java il collegamento dinamico è l’impostazionepredefinita e non avete bisogno di ricordarvi di aggiungere una qualsiasi parola chiave perottenere il polimorfismo.Riprendiamo l’esempio delle sagome. La famiglia delle classi (tutte basate sulla stessa in-terfaccia uniforme) è già stata presentata in forma di diagramma nelle pagine precedenti.Per dimostrare il polimorfismo, abbiamo bisogno di scrivere un solo frammento di codiceche ignora i dettagli specifici del tipo e parla soltanto alla classe base. Questo codice è di-saccoppiato dalle informazioni specifiche del tipo e quindi è più semplice da scrivere e piùfacile da capire. Inoltre, se tramite l’ereditarietà si aggiungesse un nuovo tipo – Esagono,per esempio – il codice che scriverete funzionerà per il nuovo tipo di Sagoma come fun-zionava per i tipi esistenti. Di conseguenza, il programma è estensibile.

ControllorePennuti

diminuisciTemperatura( )

Anitra

sposta( )

che cosa accade quando si chiama

sposta( )?

Pinguino

sposta( )

Pennuto

sposta( )

001ThIJava01.fm Page 12 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 13

Se scrivete un metodo in Java (impararete presto come si fa):

void faQualcosa(Sagoma s){ s.cancella();// . . .s.disegna();

}

Questa funzione parla con qualunque Sagoma, quindi è indipendente dal tipo di oggettoche sta disegnando e cancellando. Se da qualche altra parte del programma utilizziamo lafunzione faQualcosa( ):

Cerchio c = new Cerchio();Triangolo t = new Triangolo();Linea l = new Linea();faQualcosa(c);faQualcosa(t);faQualcosa(l);

La chiamata a faQualcosa( ) funziona automaticamente in modo corretto, indipenden-temente dal tipo esatto dell’oggetto.Questo è un trucco davvero sconcertante. Considerate questa riga:

faQualcosa(c);

Quello che qui accade è che viene passato un Cerchio in una funzione che si aspetta unaSagoma. Siccome un Cerchio è una Sagoma, può essere trattato come tale dafaQualcosa( ). Vale a dire, qualunque messaggio che faQualcosa( ) può inviare a una Sa-goma può essere accettato da un Cerchio. Per cui questa è la cosa del tutto sicura e logicada fare.Chiamiamo upcasting questo modo di trattare un tipo derivato come se fosse il suo tipobase. Nel nome cast è usato nel senso letterale inglese, fondere in uno stampo, e up vienedal modo in cui normalmente viene disposto il diagramma dell’ereditarietà, con il tipobase in testa e le classi derivate disposte a ventaglio verso il basso. Quindi, fare il castingsu un tipo base significa risalire lungo diagramma dell’ereditarietà: “upcasting”.

Un programma orientato agli oggetti contiene sempre un po’ di upcasting da qualcheparte, perché questo è il modo col quale potete distaccarvi dalla necessità di conoscere iltipo esatto col quale state lavorando. Esaminate il codice in faQualcosa( ):

s.cancella();// . . .s.disegna();

Osservate che non dice “Se sei un Cerchio fa questo, se sei un Quadrato fa quello ecc.”.Se scrivessimo un codice di questo genere, che controlla tutti i possibili tipi che Sagomapotrebbe materialmente essere, otterremmo qualcosa di pasticciato, che per di più dovre-

“Upcasting”

TriangoloQuadratoCerchio

Sagoma

001ThIJava01.fm Page 13 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 114

mo modificare ogni volta che aggiungiamo un nuovo tipo di Sagoma. Qui non diciamoaltro che “Tu sei una sagoma, so che puoi eseguire su te stesso le azioni cancella( ) edisegna( ), fallo e prenditi cura dei dettagli in modo corretto”.Ciò che colpisce del codice in faQualcosa( ) è il fatto che, in una qualche maniera, acca-dono le cose giuste. Chiamando disegna( ) per Cerchio si provoca l’esecuzione di un co-dice diverso da quello che si avrebbe chiamando disegna( ) per un Quadrato o una Li-nea, ma quando il messaggio disegna( ) viene inviato a una Sagoma anonima, si deter-mina il comportamento corretto sulla base del tipo effettivo della Sagoma. Questo èsconcertante perché, come abbiamo accennato prima, quando il compilatore Java stacompilando il codice per faQualcosa( ), non può sapere con quali tipi esattamente ha ache fare. Quindi, a regola, vi aspettereste che finisca per chiamare la versione di disegna( )e cancella( ) per la classe base Sagoma e non per le specifiche classi Cerchio, Quadratoe Triangolo. Eppure accade la cosa giusta, grazie al polimorfismo. Sono il compilatore eil sistema in fase di esecuzione a occuparsi di questi particolari; tutto quello che dovetesapere è che questo accade e, cosa più importante, come utilizzarlo nel vostro lavoro diprogettazione. Quando inviate un messaggio a un oggetto, questo farà la cosa giusta, an-che quando ciò comporta l’upcasting.

Classi base astratte e interfacceSpesso, durante la progettazione, avete bisogno che la classe base presenti soltanto un’in-terfaccia per le sue classi derivate. Vale a dire, non desiderate creare materialmente un og-getto della classe base, ma volete soltanto fare un upcast in modo da poter utilizzare la suainterfaccia. Questo risultato si ottiene rendendo quella classe astratta tramite la parolachiave abstract. Se qualcuno tenta di fare un oggetto da una classe abstract, il compila-tore glielo impedisce. Questo è uno strumento col quale si può imporre un schema deter-minato.Potete ricorrere alla parola chiave abstract anche per descrivere un metodo che non è sta-to ancora implementato: uno stub che indica “ecco una funzione di interfaccia per tutti itipi ereditati da questa classe, ma a questo punto non l’ho ancora implementata”. Si puòcreare un metodo abstract soltanto all’interno di una classe abstract. Quando la classeviene ereditata, quel metodo deve venire implementato, altrimenti la classe ereditante di-venta abstract anch’essa. La creazione di un metodo abstract vi permette di mettere unmetodo in una interfaccia senza essere costretti a fornire per quel metodo un segmento dicodice che potrebbe non avere alcun significato.La parola chiave interface fa fare un ulteriore passo avanti al concetto di classe abstract,impedendo del tutto la definizione di qualunque funzione. Con interface si dispone diuno strumento comodo e molto diffuso, che consente una perfetta separazione fra inter-faccia e implementazione. Inoltre, potete combinare insieme molte interfacce, se lo desi-derate, sebbene non sia possibile ereditare da più classi normali o classi astratte.

Spazi e durate degli oggettiTecnicamente, l’OOP è essenzialmente tipizzazione di dati astratti, ereditarietà e poli-morfismo, però esistono altri suoi aspetti non meno importanti, che andremo a esaminarenei prossimi paragrafi.Uno dei fattori più importanti è il modo in cui gli oggetti vengono creati e distrutti. Dovesi trovano i dati di un oggetto e in che modo viene controllata la durata in vita dell’ogget-to? Qui entrano in gioco diverse filosofie. Il C++ segue l’approccio secondo il quale il con-trollo dell’efficienza è l’aspetto più importante, quindi dà al programmatore la possibilitàdi scegliere. Per ottenere la massima velocità in fase di esecuzione, è possibile determinare

001ThIJava01.fm Page 14 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 15

– mentre si scrive il programma – lo spazio di immagazzinamento e la durata in vita, col-locando gli oggetti nello stack (questi vengono talvolta chiamati variabili automatiche odelimitate) oppure nell’area di immagazzinamento statica. In questo modo si dà prioritàalla velocità con la quale si assegnano e si liberano gli spazi di immagazzinamento e il con-trollo di queste operazioni può dimostrarsi molto utile in determinate situazioni. Tutta-via, si perde flessibilità, perché bisogna conoscere l’esatta quantità, la durata in vita e il ti-po degli oggetti mentre si scrive il programma. Se siete impegnati a risolvere un problemadi carattere più generale, nelle aree per esempio del disegno tecnico computerizzato, dellagestione delle scorte o del controllo del traffico aereo, questo modo di operare è tropporestrittivo.Il secondo approccio consiste nel creare oggetti dinamicamente in un serbatoio di imma-gazzinamento chiamato heap. Seguendo questo approccio, saprete soltanto in fase di ese-cuzione quanti sono gli oggetti che vi servono, qual è la loro durata in vita o qual è esat-tamente il loro tipo. Tutto ciò viene stabilito all’istante, mentre il programma è in esecu-zione. Se vi serve un nuovo oggetto, non fate altro che crearlo sullo heap nel punto in cuivi serve. Siccome l’immagazzinamento viene gestito dinamicamente, in fase di esecuzio-ne, la quantità di tempo che è necessaria per assegnare gli spazi nello heap è significativa-mente più elevata di quella che occorre per immagazzinare nello stack. (Per creare spazionello stack molto spesso basta una sola istruzione in assembler per spostare verso il bassoil puntatore dello stack e un’altra per spostarlo verso l’alto.) L’approccio dinamico dà ingenerale per scontato il fatto che gli oggetti tendono a essere complicati, quindi il sovrac-carico di lavoro necessario per trovare spazio di immagazzinamento e per liberarlo nonavrà un impatto rilevante sulla creazione di un oggetto. Inoltre, la maggior flessibilità dicui si dispone è essenziale per risolvere il problema di programmazione in generale.Java utilizza esclusivamente il secondo approccio6. Ogni volta che volete creare un nuovooggetto, utilizzate la parola chiave new per costruire un’istanza dinamica di quell’oggetto. C’è da considerare, però, un altro aspetto, quello della durata in vita di un oggetto. Neilinguaggi che consentono di creare gli oggetti sullo stack, il compilatore stabilisce quantotempo deve durare l’oggetto e può distruggerlo automaticamente. Se, però, create l’ogget-to sullo heap, il compilatore non può conoscerne la durata in vita. In un linguaggio comeil C++, dovete stabilire da programma quando distruggere l’oggetto, il che può provocareperdite di memoria se non procedete in modo corretto (e questo è un problema diffusodei programmi C++). Java mette a disposizione una funzionalità chiamata raccoglitore dirifiuti, che scopre automaticamente quando un oggetto non è più in uso e lo distrugge.Un raccoglitore di rifiuti è molto più comodo, perché riduce il numero di aspetti che do-vete tenere sotto controllo e vi fa scrivere meno codice. Quel che più importa è il fatto cheil raccoglitore di rifiuti offre migliori garanzie di protezione nei confronti dell’insidiosoproblema delle perdite di memoria (che ha messo in ginocchio molti progetti C++). Nei paragrafi che seguono esamineremo altri fattori che riguardano la durata in vita e glispazi degli oggetti.

Collezioni e iteratoriSe non sapete quanti oggetti vi serviranno per risolvere un particolare problema, e neppu-re quanto dureranno, non sapete neanche come immagazzinarli. Come potete saperequanto spazio creare per quegli oggetti? Non potete, perché quelle informazioni non sononote fino al momento dell’esecuzione.

6 I tipi primitivi, di cui ci occuperemo più avanti, costituiscono un caso particolare.

001ThIJava01.fm Page 15 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 116

La soluzione della maggior parte dei problemi nella progettazione orientata agli oggetti èpiuttosto disinvolta: si crea un altro tipo di oggetto. Il nuovo tipo di oggetto che risolvequesto problema particolare conserva riferimenti ad altri oggetti. Naturalmente potete fa-re la stessa cosa mediante un array, che è disponibile nella maggior parte dei linguaggi. Mac’è di più. Questo nuovo oggetto, chiamato in generale contenitore (viene detto anche col-lezione, ma la libreria Java utilizza il termine collection in un senso diverso, per cui in que-sto libro useremo “contenitore”), si espanderà ogni volta che occorre per accogliere qual-siasi cosa decidiate di mettervi dentro. Quindi non vi serve sapere quanti oggetti andretea depositare in un contenitore. Vi basta creare un oggetto contenitore e lasciare che prov-veda da sé a gestire i dettagli.Fortunatamente un buon linguaggio OOP viene distribuito completo di un insieme dicontenitori che fanno parte del suo pacchetto. Nel C++ questo fa parte della StandardC++ Library e viene talvolta chiamato Standard Template Library (STL). L’Object Pascaldispone di contenitori nella sua Visual Component Library (VCL). Lo Smalltalk ha uninsieme completo di contenitori. Anche Java ha i contenitori nella sua libreria standard.In alcune librerie un contenitore generico è considerato sufficiente per tutte le esigenze ein altre (quelle di Java, per esempio) la libreria ha tipi di contenitori diversi per fabbisognidiversi: un vettore (chiamato ArrayList in Java) per un accesso coerente a tutti gli elemen-ti, e una lista concatenata per l’inserimento coerente di tutti gli elementi, per esempio, co-sì potete scegliere il tipo particolare che meglio si adatta alle vostre esigenze. Le librerie dicontenitori possono anche comprendere insiemi, code, tabelle hash, alberi, stack ecc..Tutti i contenitori dispongono di un meccanismo per inserirvi cose e per prelevarle; esi-stono di solito funzioni per aggiungere elementi a un contenitore e altre per prelevarli.L’acquisizione degli elementi, però, può essere più problematica, perché una funzione cheesegue una singola selezione è restrittiva. E se volete manipolare o mettere a confronto uninsieme di elementi del contenitore invece di uno solo?La soluzione è un iteratore, un oggetto che ha il compito di selezionare gli elementi entroun contenitore e presentarli all’utente dell’iteratore stesso. Come classe, fornisce anche unlivello di astrazione. L’astrazione può essere utilizzata per separare i dettagli del conteni-tore dal codice che accede a quel contenitore. Il contenitore, tramite l’iteratore, vieneastratto riducendolo a una semplice sequenza. L’iteratore vi consente di scorrere quella se-quenza senza preoccuparvi della struttura sottostante, vale a dire, senza considerare se sitratta di una ArrayList, una LinkedList, uno Stack o qualcos’altro. Ciò vi dà la flessibi-lità necessaria per modificare agevolmente la struttura di dati sottostante senza disturbareil codice nel vostro programma. Java ha iniziato (nelle versioni 1.0 e 1.1) con un iteratorestandard chiamato Enumeration, per tutte le sue classi contenitore. Java 2 ha aggiuntouna libreria di contenitori molto più completa, dotata di un iteratore chiamato Iterator,che fa molte più cose del più vecchio Enumeration. Dal punto di vista della progettazione, quello che in realtà vi serve è una sequenza chepossiate manipolare per risolvere il vostro problema. Se un unico tipo di sequenza potessesoddisfare tutte le vostre necessità, non vi sarebbe alcuna ragione per averne di vari tipi.Esistono in realtà due ragioni che giustificano la disponibilità di vari contenitori. In pri-mo luogo, i contenitori forniscono tipi diversi di interfacce e di comportamenti esterni.Uno stack ha un’interfaccia e un comportamento diversi da quelli di una coda, che a suavolta è diversa da un insieme o da una lista. Uno di questi potrebbe fornire una soluzioneper il vostro problema più flessibile di quella di un altro. In secondo luogo, contenitoridiversi hanno efficienze diverse per determinate operazioni. L’esempio migliore si ha con-siderando una ArrayList e una LinkedList. Entrambe sono semplici sequenze che posso-no avere interfacce e comportamenti esterni identici. Determinate operazioni, però, pos-

001ThIJava01.fm Page 16 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 17

sono avere costi radicalmente diversi. L’accesso casuale a elementi di una ArrayList èun’operazione a durata costante; assorbe la stessa quantità di tempo indipendentementedall’elemento che selezionate. Invece, in una LinkedList è costoso spostarsi lungo la listaper selezionare in modo casuale un elemento e ci vuole più tempo per trovare un elemen-to collocato verso il fondo della lista. D’altro canto, se desiderate inserire un elemento nelmezzo di una sequenza, è molto più economico farlo in una LinkedList che in una Ar-rayList. Queste e altre operazioni hanno efficienze diverse in funzione della struttura sot-tostante delle sequenza. Nel corso della progettazione, potreste cominciare con unaLinkedList e, quando lavorate ad affinare le prestazioni, passare a una ArrayList. Grazieall’astrazione consentita dagli iteratori, potete passare dall’una all’altra con un impattominimo sul vostro codice.Alla fin fine, ricordatevi che un contenitore non è altro che un armadietto nel quale ripor-re oggetti. Se quell’armadietto risolve tutte le vostre necessità, non è rilevante, in realtà, ilmodo in cui viene implementato (un concetto fondamentale per la maggior parte deglioggetti). Se lavorate in un ambiente di programmazione che ha un sovraccarico intrinsecodovuto ad altri fattori, la differenza di costo fra una ArrayList e una LinkedList potrebbenon essere importante. Potreste aver bisogno di un tipo solo di sequenza. Potreste persinoimmaginare l’astrazione del contenitore “perfetto”, che può modificare automaticamentela sua implementazione sottostante a seconda del modo in cui viene utilizzato.

La gerarchia a radice unicaUna delle questioni relative alla OOP, che è diventata particolarmente rilevante da quan-do venne introdotto il C++, è se tutte le classi debbano in ultima analisi essere ereditate daun’unica classe base. In Java (come in pratica in tutti gli altri linguaggi OOP) la risposta è“sì” e il nome di questa classe base definitiva è semplicemente Object. I vantaggi offertidalla gerarchia a radice unica sono molteplici.Tutti gli oggetti in un gerarchia a radice unica hanno una interfaccia in comune, quindiin ultima analisi sono tutti dello stesso tipo. L’alternativa (fornita dal C++) consiste nelnon sapere che tutto è dello stesso tipo fondamentale. Dal punto di vista della compati-bilità a ritroso, ciò si adatta meglio al modello del C e può essere considerato come menorestrittivo, però, quando volete fare della vera programmazione orientata agli oggetti, do-vete costruirvi una vostra gerarchia che vi fornisca la stessa comodità che è intrinseca inaltri linguaggi OOP. E in qualunque nuova libreria di classi che andrete ad acquisire, siuseranno altre interfacce incompatibili. Occorre parecchio impegno (ed eventualmente ilricorso alla ereditarietà multipla) per inserire la nuova interfaccia nel vostro progetto.Questa “flessibilità” aggiuntiva del C++ merita lo sforzo? Se proprio ne avete bisogno – seavete un grosso investimento nel C – vale parecchio. Se vi accingete a partire da zero, altrealternative fra le quali Java possono spesso rivelarsi più produttive.In una gerarchia a radice unica (come quella fornita da Java) si ha la garanzia che tutti glioggetti hanno determinate funzionalità. Sapete di poter svolgere certe operazioni di basesu ogni oggetto nel vostro sistema. Una gerarchia a radice unica, oltre a creare tutti gli og-getti nello heap, semplifica notevolmente il passaggio degli argomenti (uno degli aspettipiù complessi del C++). Una gerarchia a radice unica rende molto più facile l’implementazione di un raccoglitoredi rifiuti (che è comodamente incorporato in Java). Si può installare il necessario suppor-to nella classe base e così il raccoglitore di rifiuti può inviare adeguati messaggi a ogni og-getto nel sistema. In assenza di una gerarchia a radice unica e di un sistema per manipo-lare gli oggetti tramite un riferimento, è difficile implementare un raccoglitore di rifiuti.

001ThIJava01.fm Page 17 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 118

Siccome è garantito che le informazioni sui tipi sono disponibili in tutti gli oggetti in fasedi esecuzione, non rischierete mai di ritrovarvi con un oggetto del quale non potete deter-minare il tipo. Ciò è particolarmente importante per le operazioni a livello di sistema, co-me per esempio la gestione delle eccezioni, e per consentire una maggior flessibilità nellaprogrammazione.

Librerie di collezioni e supporto per utilizzarle comodamenteSiccome un contenitore è uno strumento che si usa spesso, è ragionevole avere una libre-ria di contenitori costruiti in un modo riutilizzabile, così che si possa prelevarne uno dauno scaffale e inserirlo in un programma. Java mette a disposizione una libreria di questogenere, che dovrebbe soddisfare la maggior parte delle necessità.

Il downcastingPer poter essere riutilizzabili, questi contenitori contengono l’unico tipo universale di Ja-va che abbiamo citato in precedenza: Object. La gerarchia a radice unica implica che tut-to è un Object, quindi un contenitore che contenga degli Object può contenere qualun-que cosa. Ed è questo ciò che rende facile il riutilizzo dei contenitori.Per poter utilizzare un contenitore di questo tipo, dovete semplicemente aggiungervi rife-rimenti a oggetti e più tardi farveli restituire. Però, siccome il contenitore contiene soltan-to degli Object, quando aggiungete al contenitore il vostro riferimento a un oggetto que-sto viene sottoposto a upcast e diventa Object, perdendo in questo modo la sua identità.Quando lo richiamate, ottenete un riferimento a Object, non un riferimento al tipo cheavevate messo dentro. E allora come potete ritrasformarlo in qualcosa che abbia quellautile interfaccia dell’oggetto che avevate messo nel contenitore?In questo caso si usa di nuovo il meccanismo del cast, ma questa volta non fate il cast ver-so l’alto (upcast), risalendo la gerarchia dell’ereditarietà per arrivare a un tipo più generale,bensì scendete verso il basso (down) della stessa gerarchia per raggiungere un tipo più spe-cifico. Questo modo di operare si chiama downcasting. Con l’upcasting sapete, per esem-pio, che un Cerchio è un tipo di Sagoma, quindi potete fare l’upcast senza rischi, perònon sapete che un Object è necessariamente un Cerchio o una Sagoma, quindi il down-cast è piuttosto rischioso, se non si sa bene con che cosa si ha a che fare.Il rischio, però, è minimo, perché se col downcast arrivate alla cosa sbagliata generate unerrore di run time chiamato eccezione, che descriveremo fra breve. Quando acquisite rife-rimenti a oggetti da un contenitore, comunque, vi serve un modo per ricordare esatta-mente che cosa sono, in modo da poter fare un downcast corretto.Il downcasting e i controlli in fase di esecuzione impongono tempi addizionali al pro-gramma in esecuzione e impegnano maggiormente il programmatore. Non sarebbe piùragionevole creare il contenitore in modo che conosca i tipi che contiene, eliminando lanecessità di ricorrere al downcast e i possibili errori che questo comporta? La soluzioneconsiste nell’avere tipi parametrizzati, che sono classi che il compilatore può personaliz-zare automaticamente per lavorare con tipi particolari. Per esempio, avendo un conteni-tore parametrizzato il compilatore potrebbe personalizzarlo in modo che accetti soltantoSagome e restituisca soltanto Sagome.I tipi parametrizzati sono un componente importante del C++, in parte perché il C++ nonha una gerarchia a radice unica. Nel C++, la parola chiave che implementa i tipi parame-trizzati è “template”. Java al momento non dispone di tipi parametrizzati perché riesce co-munque ad andare avanti – sebbene in modo un po’ contorto – utilizzando la gerarchia aradice unica. Tuttavia, è all’esame una proposta per utilizzare i tipi parametrizzati me-diante una sintassi che assomiglia notevolmente ai modelli (template) del C++.

001ThIJava01.fm Page 18 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 19

Il dilemma del riordino: chi deve fare le pulizie?Ciascun oggetto ha bisogno di risorse per poter esistere, in modo particolare ha bisognodi memoria. Quando un oggetto non serve più deve essere eliminato in modo che le suerisorse vengano liberate e si possano riutilizzare. Nelle situazioni di programmazione piùsemplici la questione di come eliminare un oggetto non è particolarmente ardua: createl’oggetto, lo utilizzate per tutto il tempo che vi occorre e poi dovrebbe essere distrutto.Non è inconsueto, però, trovarsi in situazioni nelle quali le cose sono più complesse.Supponete, per esempio, di star progettando un sistema per gestire il traffico aereo di unaeroporto. (Lo stesso modello può andar bene anche per la gestione delle casse in un ma-gazzino, per un sistema di noleggio di videocassette o per un pensionato per animali do-mestici.) A prima vista sembra semplice: create un contenitore per conservarvi gli aeropla-ni, poi create un nuovo aeroplano e lo mettete nel contenitore per ciascun aeroplano cheentra nella zona del controllo del traffico aereo. Per ripulire, non dovete fare altro che eli-minare l’opportuno oggetto aeroplano quando un aereo esce dalla zona.Però avete forse qualche altro sistema per registrare dati sugli aerei; magari dati che nonrichiedono la stessa attenzione immediata delle funzione di controllo principale. Potrebbeessere una registrazione dei piani di volo di tutti gli aerei piccoli che lasciano l’aeroporto.Quindi avete un secondo contenitore per gli aerei piccoli e tutte le volte che create un og-getto aereo lo mettete anche in questo contenitore se si tratta di un aereo piccolo. In se-guito un qualche processo che opera in secondo piano svolge operazioni sugli oggetti inquesto contenitore nei momenti di pausa.Adesso il problema si è fatto più difficile: come potrete mai sapere quando distruggere glioggetti? Quando avete finito di lavorare con un oggetto qualche altra parte del sistemapotrebbe averne ancora bisogno. Lo stesso problema potrebbe manifestarsi in numerosealtre situazioni e nei sistemi di programmazione (tipo il C++), nei quali dovete eliminareesplicitamente un oggetto quando non vi serve più, le cose possono diventare davverocomplesse.In Java è il raccoglitore di rifiuti che ha il compito di liberare la memoria (anche se questaoperazione non comprende altri aspetti della distruzione di un oggetto). Il raccoglitore dirifiuti “sa” quando un oggetto non è più in uso e quindi libera automaticamente la me-moria occupata da quell’oggetto. Questo (combinato col fatto che tutti gli oggetti sonoereditati dalla classe Object a radice unica e che potete creare oggetti in un solo modo,nell’heap) rende il processo di programmazione in Java molto più semplice che nel C++.Avete meno decisioni da prendere e meno ostacoli da superare.

I raccoglitori di rifiuti, l’efficienza e la flessibilitàSe questa è un’idea così buona, come mai non hanno fatto la stessa cosa nel C++? Natu-ralmente c’è un prezzo da pagare per ottenere tutte queste comodità in programmazione,e il prezzo si paga in termini di sovraccarico in fase di esecuzione. Come abbiamo accen-nato prima, nel C++ potete creare oggetti nello stack, e in questo caso vengono eliminatiautomaticamente (però non avete la flessibilità di crearne quanti ne volete in fase di ese-cuzione). Creare oggetti nello stack è il modo più efficiente per assegnare spazio di imma-gazzinamento agli oggetti e per liberare quello spazio. Creare oggetti nello heap può esseremolto più costoso. Anche ereditare sempre da una classe base e rendere polimorfiche tuttele chiamate di funzione impone il pagamento di un piccolo pedaggio. La raccolta dei ri-fiuti, però, è un problema particolare, perché non si sa mai quando verrà avviata né quan-to tempo impiegherà. Questo significa che c’è un’incoerenza nella velocità di esecuzionedi un programma Java, per cui non potete usarlo in determinate situazioni, nelle quali lavelocità di esecuzione di un programma è uniformemente critica. (Questi si chiamano in

001ThIJava01.fm Page 19 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 120

generale programmi in tempo reale, però non tutti i problemi della programmazione intempo reale sono così vincolanti.)I progettisti del linguaggio C++, puntando a sedurre i programmatori C (e con molto suc-cesso, se è per questo) non hanno voluto aggiungere a quel linguaggio alcuna funzionalitàche avrebbe potuto influire sulla velocità o sull’utilizzo del C++ in qualunque situazionenella quale i programmatori avrebbero altrimenti scelto il C. Questo obiettivo è stato re-alizzato, ma al prezzo di una maggiore complessità della programmazione in C++. Java èpiù semplice del C++, ma è una semplificazione che comporta una certa riduzione di ef-ficienza e talvolta di applicabilità. Per una parte significativa dei problemi di programma-zione, però, Java è la scelta migliore.

La gestione delle eccezioni: affrontare gli erroriFin dagli inizi dei linguaggi di programmazione, la gestione degli errori è stata una dellequestioni difficili. Siccome è così difficile concepire un buon meccanismo per la gestionedegli errori, molti linguaggi ignorano semplicemente la questione, passando il problemaai creatori di librerie, che se la cavano con mezze misure, che possono funzionare in moltesituazioni, ma che è facile aggirare semplicemente ignorandole. Il problema principaleche affligge la maggior parte dei meccanismi per la gestione degli errori deriva dal fattoche contano sulla vigilanza del programmatore nell’attenersi a una convenzione che nonviene imposta dal linguaggio. Se il programmatore non è sufficientemente vigile – il cheaccade spesso se lavora sotto pressione – questi meccanismi possono essere facilmente di-menticati.La gestione delle eccezioni integra la gestione degli errori direttamente nel linguaggio diprogrammazione e talvolta persino nel sistema operativo. Una eccezione è un oggetto cheviene “lanciato” dal sito dell’errore e può essere “intercettato” da un adeguato gestore dieccezioni concepito per gestire quel particolare tipo di errore. Ne deriva che la gestionedelle eccezioni è un percorso di esecuzione diverso, parallelo, che può essere imboccatoquando le cose vanno male. E siccome si serve di un percorso di esecuzione separato, nonha bisogno di interferire con l’esecuzione normale del codice. Ciò rende il codice più sem-plice da scrivere, perché non siete continuamente costretti a controllare possibili errori.Inoltre, una eccezione lanciata è diversa da un valore di errore restituito da una funzioneo da un flag impostato da una funzione per indicare una condizione di errore: queste se-gnalazioni possono essere ignorate. Una eccezione non può essere ignorata, quindi è ga-rantito che in qualche punto verrà affrontata. Infine, le eccezioni forniscono un modo perriprendersi in modo affidabile da una cattiva situazione. Invece di uscire e basta, spessopotete rimettere le cose a posto e ripristinare l’esecuzione di un programma, ottenendoper questa via programmi più robusti.La gestione delle eccezioni in Java spicca fra i linguaggi di programmazione perché in que-sto caso è stata integrata nel linguaggio fin dall’inizio e siete costretti a utilizzarla. Se nonscrivete il vostro codice in modo da gestire correttamente le eccezioni, vi arriva un mes-saggio di errore in fase di compilazione. Questa coerenza garantita rende molto più facilela gestione degli errori.Val la pena osservare che la gestione delle eccezioni non è una caratteristica orientata aglioggetti, sebbene in un linguaggio orientato agli oggetti l’eccezione sia di norma rappre-sentata con un oggetto. La gestione delle eccezioni esisteva prima dei linguaggi orientatiagli oggetti.

001ThIJava01.fm Page 20 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 21

MultithreadingUn concetto fondamentale nella programmazione dei computer è quello di gestire più diun’attività alla volta. Molti problemi di programmazione esigono che un programma siain grado di interrompere quello che sta facendo, affrontare qualche altro problema e poitornare al processo principale. La soluzione è stata affrontata in vari modi. Inizialmente,i programmatori che conoscevano i meccanismi a basso livello della macchina scrivevanoroutine di gestione degli interrupt e la sospensione del processo principale veniva attivatada un interrupt hardware. Sebbene funzionasse bene, era un modo di operare difficoltosoe non trasferibile, che rendeva lento e costoso il trasferimento di un programma a un nuo-vo tipo di macchina.Talvolta gli interrupt sono necessari per gestire operazioni temporalmente critiche, peròesiste una vasta famiglia di problemi in cui si cerca semplicemente di suddividere il pro-blema in segmenti eseguiti separatamente, in modo che l’intero programma nel suo com-plesso sia più reattivo. All’interno di un programma, questi segmenti eseguiti separata-mente si chiamano thread (fili) e il concetto in generale prende il nome di multithreading.Un esempio molto diffuso di multithreading è l’interfaccia utente. Grazie ai thread, unutente può premere un pulsante e ottenere subito una risposta, invece di essere costrettoad aspettare che il programma termini l’attività che sta eseguendo in quel momento.Di solito i thread sono soltanto un modo per assegnare il tempo di un unico processore.Però se il sistema operativo supporta più processori, ciascun thread può essere assegnatoa un processore diverso ottenendo un’autentica esecuzione in parallelo dei thread stessi.Una delle caratteristiche più comode del multithreading a livello del linguaggio è il fattoche il programmatore non deve chiedersi se vi siano più processori o uno solo. Il program-ma viene suddiviso logicamente in thread e, se la macchina ha più di un solo processore,il programma gira più velocemente, senza alcun adattamento particolare.Tutto questo induce a pensare che il ricorso ai thread sia molto semplice. Ma c’è un’insi-dia: le risorse condivise. Se avete più thread in esecuzione che attendono di accedere allastessa risorsa, avete un problema. Per esempio, due processi non possono inviare informa-zioni simultaneamente a una stampante. Per risolvere il problema, le risorse che possonoessere condivise, come per esempio la stampante, devono essere bloccate mentre sono uti-lizzate. Quindi un thread blocca una risorsa, porta a termine la sua attività e poi toglie ilblocco, in modo che qualcun altro possa utilizzare le risorsa.L’utilizzo dei thread in Java è integrato nel linguaggio, il che rende molto più semplice unmeccanismo che altrimenti sarebbe complicato. I thread sono supportati a livello deglioggetti, per cui un thread di esecuzione è rappresentato da un oggetto. Java mette a dispo-sizione anche un meccanismo limitato per il blocco delle risorse. Può bloccare la memoriadi un qualunque oggetto (che è, dopo tutto, un tipo di risorsa condivisa) in modo che unsolo thread per volta possa utilizzarla. Questo risultato si ottiene tramite la parola chiavesynchronized. Altri tipi di risorse vanno bloccati esplicitamente dal programmatore, disolito creando un oggetto che rappresenta il blocco e che tutti i thread devono controllareprima di accedere a quella risorsa.

PersistenzaQuando create un oggetto, questo esiste per tutto il tempo in cui vi serve, ma per nessunaragione al mondo continua a esistere quando il programma finisce. Sebbene la cosa abbiaun suo senso, almeno a prima vista, esistono situazioni nelle quali sarebbe davvero utile seun oggetto potesse esistere e conservasse le sue informazioni anche quando il programmanon è in esecuzione. In questo caso, la prossima volta che avviate il programma l’oggetto

001ThIJava01.fm Page 21 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 122

sarebbe già lì e avrebbe le stesse informazioni che aveva la volta precedente in cui il pro-gramma era in esecuzione. Naturalmente potete ottenere un effetto del genere scrivendole informazioni su un file o in un database, ma nello spirito di far sì che tutto sia un og-getto sarebbe molto comodo poter dichiarare persistente un oggetto e avere un meccani-smo che provveda a tutti i dettagli del caso.Java fornisce il supporto per una “persistenza leggera”, il che vuol dire che potete agevol-mente salvare oggetti su disco e reperirli in un secondo tempo. È considerata “leggera”perché siete tuttora costretti a fare chiamate esplicite per il salvataggio e il reperimento.Oltre a questa, le Java Server Pages (descritte nel Capitolo 15) mettono a disposizione uncerto tipo di immagazzinamento persistente degli oggetti. In qualche versione futura po-trebbe essere disponibile un supporto più completo della persistenza.

Java e InternetSe Java è, di fatto, ancora un altro linguaggio di programmazione per computer, potrestechiedervi come mai è così importante e perché viene promosso come un passo avanti ri-voluzionario nella programmazione dei computer. La risposta non è immediatamente ov-via se provenite da una prospettiva tradizionale della programmazione. Sebbene Java siamolto utile per realizzare programmi di tipo tradizionale, concepiti per lavorare in auto-nomia, è importante anche perché risolverà i problemi di programmazione del WorldWide Web.

Che cos’è il Web?Il Web, a prima vista, può apparire un po’ misterioso, con tutto quel gran parlare di “na-vigazione”, “presenza” e “home page”. Si è sviluppata persino una crescente reazione av-versa alla “mania Internet”, che mette in discussione il valore economico e i risultati diquesta moda dilagante. Può essere utile fare un passo indietro ed esaminare che cosa è inrealtà, ma per farlo dovete prima capire i sistemi client/server, un altro aspetto dell’infor-matica nel quale c’è parecchia confusione.

Elaborazione client/serverL’idea base di un sistema client/server è che abbiate un magazzino centrale di informazio-ni – dati di un certo tipo, spesso in forma di database – che volete distribuire a richiesta aun determinato insieme di persone o di macchine. Un elemento chiave del concettoclient/server è che il magazzino delle informazioni sia localizzato centralmente, in modoche sia possibile modificarlo facendo propagare le modifiche all’esterno, verso i consuma-tori delle informazioni. Considerati nel loro insieme, il magazzino delle informazioni, ilsoftware che distribuisce tali informazioni e la macchina (o le macchine) dove le informa-zioni e il software risiedono costituiscono il server. Il software che risiede nelle macchineremote, comunica con il server, acquisisce le informazioni, le elabora e quindi le visualizzasulle macchine remote viene chiamato client.Il concetto base dell’elaborazione client/server, quindi, non è poi così complicato. I pro-blemi nascono perché c’è un solo server che tenta di servire molti client contemporanea-mente. In generale, si ricorre a un sistema per la gestione di database, in modo che il pro-gettista possa distribuire i dati in tabelle per consentirne un utilizzo ottimale. Inoltre, i si-stemi spesso consentono a un client di inserire nuove informazioni in un server. Questovuol dire che bisogna garantire che i nuovi dati di un determinato client non vadano asovrapporsi ai nuovi dati di un altro client o che non si perdano dati mentre li si aggiungo-no al database. (Questo si chiama elaborazione per transazioni.) Quando il software clientdeve essere modificato, è necessario compilarlo, collaudarlo, eliminare gli errori e installar-

001ThIJava01.fm Page 22 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 23

lo sulle macchine client, cosa che si dimostra molto più complicata e costosa di quanto sipotrebbe pensare. È particolarmente problematico supportare più tipi di computer e disistemi operativi. Infine, c’è la questione importantissima delle prestazioni: potreste averecentinaia di client che fanno richieste al vostro server in un momento determinato, per cuianche un piccolo ritardo diventa importante. Per ridurre al minimo i tempi di attesa, iprogrammatori lavorano duramente a trasferire altrove le attività di elaborazione, spessosulle macchine client, ma a volte anche su altre macchine che si trovano presso il sito delserver, utilizzando quello che viene chiamato middleware. (Il middleware si utilizza ancheper migliorare la manutenibilità.)L’idea, in sé semplice, di distribuire informazioni alle persone ha un tal numero di stratidi complessità all’atto della sua implementazione da far pensare che l’intero problema siairrimediabilmente enigmatico. Eppure è di importanza cruciale: l’elaborazione client/ser-ver costituisce grosso modo metà di tutte le attività di programmazione. È responsabile ditutto, dall’acquisizione degli ordini alla gestione delle transazioni effettuate tramite cartedi credito alla distribuzione di dati di qualunque genere: informazioni sul mercato azio-nario, scientifiche, generate dalla pubblica amministrazione e quant’altro. Nel passato cisiamo dovuti arrangiare con soluzioni individuali per problemi specifici, inventando ognivolta una nuova soluzione. Era difficile crearle e faticoso utilizzarle e gli utenti dovevanoimparare una nuova interfaccia per ciascuna soluzione. L’intero problema client/server habisogno di essere risolto alla grande.

Il Web come un gigantesco serverIl Web è in realtà un unico gigantesco sistema client/server. Anzi, è persino un po’ peggio,perché tutti i server e i client coesistono contemporaneamente in un’unica rete. Non sietetenuti a tener conto di questa situazione, perché tutto quello che vi interessa è collegarvicon un server per volta e interagire con esso (anche se magari siete costretti a saltellare ingiro per il mondo alla ricerca del server giusto.)Agli inizi, si trattava di un semplice processo unidirezionale. Facevate una richiesta a unserver e questo vi passava un file, che il software della vostra macchina (il client) interpre-tava formattandolo sulla macchina locale. Però, dopo poco tempo, la gente ha cominciatoa volere dal server qualcosa di più della semplice distribuzione di pagine. Volevano fun-zionalità client/server complete, che consentissero, per esempio, al client di rimandare in-formazioni al server, di consultare database sul server, di inserirvi nuove informazioni o diemettere ordini (il che richiede livelli di sicurezza superiori a quelli offerti dal sistema ori-ginale). Sono questi i cambiamenti che si sono manifestati nel corso dello sviluppo delWeb.Il browser Web è stato un grande passo avanti: il concetto che un dato elemento di infor-mazione possa essere visualizzato su qualunque tipo di computer senza che sia necessariomodificarlo. I browser, però, erano ancora piuttosto primitivi e finivano per impantanarsirapidamente sotto il peso delle richieste che erano chiamati a soddisfare. Non erano par-ticolarmente interattivi e tendevano a intasare sia il server sia Internet, perché tutte le vol-te che dovevate fare qualcosa che avesse bisogno di programmazione dovevate rimandarele informazioni al server per fargliele elaborare. Potevate metterci parecchi secondi o an-che minuti per scoprire che avevate commesso qualche errore di battitura nella vostra ri-chiesta. Siccome il browser non era altro che un visualizzatore, non poteva svolgere nep-pure la più semplice attività di elaborazione. (Per contro, era anche uno strumento sicuro,perché non poteva eseguire sulla vostra macchina alcun programma che contenesse errorio virus.)Per risolvere questo problema si sono tentati vari approcci. Per cominciare, sono stati po-tenziati gli standard della grafica per consentire la visualizzazione di animazioni e filmati

001ThIJava01.fm Page 23 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 124

nei browser. Il resto del problema può essere risolto soltanto incorporando la capacità dieseguire programmi dal lato client, sotto il browser. Questa si chiama programmazionedal lato client.

Programmazione dal lato clientL’impostazione originaria di server e browser nel Web rendeva disponibili contenuti in-terattivi, ma l’interattività veniva fornita per intero dal server. Il server produceva paginestatiche per il browser client, il cui compito era solo quello di interpretarle e a visualizzar-le. Il linguaggio HTML contiene semplici meccanismi per acquisire dati: caselle di testo,caselle di controllo, pulsanti di opzione, caselle di riepilogo e caselle combinate, oltre apulsanti che si possono programmare per eliminare i dati da un form o per “inoltrare” alserver i dati di un form. Questo inoltro passa attraverso la Common Gateway Interface(CGI) disponibile in tutti i server Web. Il testo inoltrato dice alla CGI che cosa deve fare.L’azione più comune è l’esecuzione di un programma che si trova sul server in una di-rectory che di solito si chiama “cgi-bin”. (Se tenete d’occhio la casella dell’indirizzo nellaparte superiore del vostro browser quando premete un pulsante su una pagina Web, a vol-te potrete individuare “cgi-bin” nel guazzabuglio di caratteri che vengono visualizzati.)Questi programmi si possono scrivere in molti linguaggi. Il Perl è quello utilizzato piùspesso, perché è concepito per manipolare testi ed è interpretato, per cui può essere instal-lato su qualunque server, indipendentemente dal processore o dal sistema operativo.Al giorno d’oggi molti siti Web particolarmente potenti sono costruiti esclusivamentesulla CGI, con la quale in effetti si può fare quasi qualunque cosa. Tuttavia, la manuten-zione dei siti Web costruiti su programmi CGI può diventare rapidamente troppo com-plicata, e c’è inoltre il problema dei tempi di risposta. La risposta di un programma CGIdipende dalla quantità di dati da inviare e dal carico sia del server sia di Internet. (A que-sto va aggiunto il fatto che l’avvio di un programma CGI tende a essere lento.) I primiprogettisti del Web non avevano previsto la rapidità con la quale questa banda passante sisarebbe esaurita a causa del tipo di applicazioni che si venivano sviluppando. Per esempio,qualunque tipo di rappresentazione grafica dinamica è quasi impossibile da eseguire inmodo coerente, perché si deve creare un file GIF e trasferirlo dal server al client per cia-scuna variazione della grafica. E avrete senza dubbio sperimentato direttamente qualcosadi molto semplice come la convalida dei dati in una maschera di input. Voi premete ilpulsante di invio su una pagina; i dati vengono spediti al server; il server avvia un pro-gramma CGI che scopre un errore, formatta una pagina HTML per segnalarvi l’errore equindi vi rimanda la pagina; voi a questo punto dovete tornare indietro e provare di nuo-vo. Tutto questo non è soltanto lento, ma è anche poco elegante.La soluzione sta nella programmazione dal lato client. La maggior parte delle macchinesulle quali si eseguono browser Web sono potenti motori in grado di fare parecchio lavoroe con l’impostazione originale basata su pagine HTML statiche restano lì, fermi, aspet-tando oziosamente che il server spiattelli la pagina successiva. Programmazione dal latoclient vuol dire impegnare il browser Web, facendogli fare tutto quello che gli è possibile,facendo vivere all’utente un’esperienza più interattiva e più veloce col sito Web.Quando si parla di programmazione dal lato client, i problemi non sono molto diversi daquelli della programmazione in generale. I parametri sono quasi gli stessi, ma la piattafor-ma è diversa: un browser Web è come un sistema operativo con alcune limitazioni. In findei conti, dovete comunque programmare e questo spiega la sconcertante quantità di pro-blemi e di soluzioni generati dalla programmazione dal lato client. Nelle pagine che se-guono diamo una panoramica dei temi e degli approcci presenti nella programmazionedal lato client.

001ThIJava01.fm Page 24 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 25

Plug-inUno dei passi avanti più significativi compiuti nella programmazione dal lato client è sta-to lo sviluppo dei plug-in. Si tratta di strumenti che consentono a un programmatore diaggiungere nuove funzionalità al browser scaricando un frammento di codice che va a in-serirsi in un punto opportuno del browser. Un plug-in dice al browser “da ora in poi puoieseguire questa nuova attività”. (Il plug-in va scaricato una sola volta.) Tramite i plug-insi possono aggiungere comportamenti veloci e potenti a un browser, però scrivere unplug-in non è un’operazione banale e non è qualcosa che conviene fare quando si costru-isce un determinato sito. Il valore del plug-in per la programmazione dal lato client sta nelfatto che consente a un programmatore esperto di sviluppare un nuovo linguaggio e diaggiungerlo al browser senza il permesso del costruttore del browser. In questo modo iplug-in forniscono una “porta posteriore” che permette di creare nuovi linguaggi di pro-grammazione dal lato client (sebbene non tutti i linguaggi vengano implementati comeplug-in).

Linguaggi di scriptingI plug-in hanno determinato una gran fioritura di linguaggi di scripting. Con un linguag-gio di scripting si incorpora direttamente nella pagina HTML il codice sorgente di unprogramma dal lato client e il plug-in che interpreta quel linguaggio viene attivato auto-maticamente quando si visualizza la pagina HTML. I programmi scritti con un linguag-gio di scripting sono in genere piuttosto facili da capire e, siccome sono semplicementetesti che fanno parte di una pagina HTML, si caricano rapidamente durante quell’unicoaggancio col server che è necessario per acquisire quella pagina. C’è un prezzo da pagare:il codice del programma può essere letto (e rubato) da chiunque. In generale, però, nonsi creano cose eccezionalmente raffinate con i linguaggi di scripting, quindi non è un in-conveniente particolarmente grave.A questo proposito, ricordiamo che i linguaggi di scripting utilizzati all’interno dei brow-ser Web hanno in realtà lo scopo di risolvere problemi di tipo specifico, soprattutto la cre-azione di interfacce utente grafiche (Graphical User Interface: GUI) più ricche e più in-terattive. Tuttavia, un linguaggio di scripting potrebbe risolvere l’ottanta per cento deiproblemi che si presentano nella programmazione dal lato client. I vostri problemi po-trebbero rientrare pienamente in questo ottanta per cento e, siccome con i linguaggi discripting lo sviluppo è più facile e più veloce, probabilmente farete bene a prenderne inconsiderazione uno prima di impegnarvi in soluzioni più involute quali la programmazio-ne in Java o con gli ActiveX.I linguaggi di scripting più diffusi sono JavaScript (che non ha nulla a che fare col Java;gli hanno dato questo nome per sfruttare il richiamo commerciale di Java), VBScript (cheassomiglia al Visual Basic) e il Tcl/Tk, che deriva dal diffuso linguaggio per costruire GUIsu più piattaforme. Ve ne sono altri in circolazione, e sicuramente altri ancora sono incorso di sviluppo.JavaScript è probabilmente quello più supportato. Viene distribuito integrato sia in Net-scape Navigator sia in Microsoft Internet Explorer. Per di più, sono in circolazione pro-babilmente più libri su JavaScript che su qualunque altro linguaggio per browser e alcunistrumenti creano automaticamente pagine utilizzando JavaScript. Tuttavia, se avete giàmolta pratica di Visual Basic o di Tcl/Tk, sarete forse più produttivi con quei linguaggidi scripting che imparandone uno nuovo. (Avrete già il vostro bel da fare ad affrontare lequestioni del Web.)

001ThIJava01.fm Page 25 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 126

JavaSe un linguaggio di scripting può risolvere l’ottanta per cento dei problemi della pro-grammazione dal lato client. che dire del restante venti per cento, la “roba davvero ar-dua”? La soluzione oggi più diffusa è Java. Non soltanto si tratta di un potente linguaggiodi programmazione creato per garantire la sicurezza, operare su più piattaforme e interna-zionale, ma viene costantemente ampliato per fornire funzionalità di linguaggio e librerieche gestiscono con eleganza problemi che nei linguaggi di programmazione tradizionalisono difficili, come per esempio il multithreading, l’accesso ai database, la programmazio-ne delle reti e l’elaborazione distribuita. Java consente la programmazione dal lato clientetramite le applet.Un’applet è un mini programma che viene eseguito soltanto entro un browser Web. L’ap-plet viene scaricata automaticamente come parte di una pagina Web (nello stesso modoin cui, per esempio, viene scaricata una grafica) Quando viene attivata, l’applet esegue unprogramma. In questa sta, in parte, il suo bello: vi mette a disposizione un modo per di-stribuire automaticamente il software client. L’utente riceve immediatamente il softwareclient, senza difficoltà né problemi di installazione. Per il modo in cui Java è stato conce-pito, il programmatore deve creare soltanto un unico programma, che funziona automa-ticamente su tutti i computer che hanno browser nei quali è integrato un interprete di Ja-va. (In pratica, la gran maggioranza delle macchine.) Dal momento che Java è un linguag-gio di programmazione a pieno titolo, potete eseguire sul client tutto il lavoro che vi èpossibile prima e dopo aver inviato richieste al server. Per esempio, non avete bisogno diinviare un form di richiesta attraverso la rete Internet per scoprire che avete inserito unadata sbagliata o qualche altro parametro scorretto e il vostro computer client può eseguirerapidamente il lavoro di tracciare graficamente i dati invece di aspettare che il server creiun diagramma e ve ne restituisca l’immagine grafica. Non soltanto guadagnate subito invelocità e reattività, ma si riduce anche il traffico di rete in generale e il carico sui server,evitando il rallentamento dell’intera Internet.Uno dei vantaggi di un’applet Java rispetto a un programma script è il fatto che è in formacompilata, quindi il codice sorgente non è aperto al client. Per contro, un’applet Java puòessere decompilata senza troppe difficoltà, però nascondere il codice spesso non è impor-tante. Sono altri due fattori che possono essere importanti. Come vedrete più avanti nellibro, un’applet Java compilata può essere articolata in molti moduli e richiedere più ac-cessi al server per essere scaricata. (Nelle versioni 1.1 e superiori di Java questo meccani-smo è ridotto al minimo da archivi Java, chiamati file JAR, che permettono di impaccareinsieme tutti i moduli necessari e comprimerli in un unico scaricamento.) Un programmascript viene semplicemente integrato nella pagina Web come parte del suo testo (e risul-terà di solito più piccolo, con un minor numero di accessi al server). Questo potrebbe es-sere importante per la reattività del vostro sito Web. Un altro fattore è dato dalla curva diapprendimento, che ha una sua notevole importanza. Nonostante quello che potresteaver sentito dire, Java non è un linguaggio banale da apprendere. Se siete programmatoriVisual Basic, passare a VBScript potrebbe essere la soluzione più veloce e, siccome questolinguaggio può risolvere probabilmente la maggior parte dei più tipici problemi client/server, vi sarebbe difficile giustificare l’apprendimento di Java. Se avete qualche esperienzacon i linguaggi di scripting, vi converrà senz’altro esaminare a fondo JavaScript o VB-Script prima di impegnarvi col Java, dato che quei linguaggi potrebbero soddisfare age-volmente le vostre necessità e diventereste più produttivi in minor tempo.

001ThIJava01.fm Page 26 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 27

ActiveXIn una certa misura, il concorrente di Java è l’ActiveX della Microsoft, anche se segue unapproccio completamente diverso. ActiveX in origine era una soluzione limitata ai soliambienti Windows, però attualmente se ne sta sviluppando una versione per più piatta-forme a cura di un consorzio indipendente. In sostanza, ActiveX dice “se il vostro pro-gramma si connette col suo ambiente in questo modo, può essere inserito in una paginaWeb ed eseguito sotto un browser che supporta ActiveX”. (Internet Explorer supporta di-rettamente ActiveX, mentre Netscape lo fa tramite un plug-in.) Di conseguenza, ActiveXnon vi vincola a un particolare linguaggio. Se, per esempio, siete già programmatoriesperti in ambiente Windows e utilizzate linguaggi tipo C++, Visual Basic o Delphi, po-tete creare componenti ActiveX praticamente senza modificare le vostre conoscenze diprogrammazione. ActiveX fornisce anche un percorso per utilizzare nelle pagine Web co-dice legacy, creato in vecchi ambienti.

SicurezzaScaricare ed eseguire automaticamente programmi tramite Internet può sembrare il so-gno di un creatore di virus. ActiveX in particolare solleva la spinosa questione della sicu-rezza nella programmazione dal lato client. Se fate clic su un sito Web, potreste scaricareautomaticamente un numero qualunque di cose insieme con la pagina HTML: file GIF,codice script, codice Java compilato e componenti ActiveX. Alcune di queste cose sonobenigne; i file GIF non fanno alcun danno e i linguaggi di scripting in genere hanno pre-cisi limiti in quel che possono fare. Anche Java è stato concepito per eseguire le sue appletentro una barriera protettiva di sicurezza, che impedisce loro di scrivere su disco o di ac-cedere a spazi di memoria esterni alla barriera.ActiveX si colloca all’estremo opposto dello spettro. Programmare con ActiveX è comeprogrammare con Windows: potete fare tutto quello che vi pare. Per cui, se fate clic suuna pagina che scarica un componente ActiveX, quel componente potrebbe danneggiarei file sul vostro disco. Naturalmente, i programmi che caricate sul vostro computer e chenon sono vincolati a essere eseguiti entro un browser Web possono fare la stessa cosa. Ivirus scaricati dai Bulletin-Board System (BBS) sono un problema da parecchio tempo,però la velocità di Internet amplifica le difficoltà.La soluzione potrebbe derivare dalle “firme digitali”, tramite le quali il codice viene veri-ficato per mostrare chi ne è l’autore. L’idea è che un virus funziona perché il suo creatorepuò essere anonimo, per cui se si elimina l’anonimato le singole persone saranno costrettead assumersi le responsabilità delle loro azioni. Il concetto sembra buono perché consenteai programmi di essere molto più funzionali e ritengo che potrebbe eliminare certe azionideliberatamente dannose. Se, però, un programma contiene un baco distruttivo non in-tenzionale, continuerà a creare problemi.L’approccio di Java consiste nel prevenire il manifestarsi di questi problemi tramite la bar-riera protettiva. L’interprete Java che risiede nel vostro browser Web locale esamina l’ap-plet quando viene caricata per controllare che non contenga istruzioni dannose. In parti-colare, un’applet non può scrivere file su disco e neppure eliminarli (uno dei capisaldi deivirus). In genere le applet sono considerate sicure e siccome questo è essenziale per i siste-mi client/server affidabili, eventuali bachi nel linguaggio Java che danno spazio ai virusvengono rapidamente eliminati. (Val la pena ricordare che il software dei browser imponematerialmente il rispetto di questi vincoli di sicurezza e alcuni browser consentono di se-lezionare diversi livelli di sicurezza per consentire vari gradi di accesso al vostro sistema.)Potreste non essere d’accordo con questa misura piuttosto draconiana che impedisce discrivere file sul vostro disco locale. Per esempio, potreste aver bisogno di costruire un da-

001ThIJava01.fm Page 27 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 128

tabase locale o di salvare dati da utilizzare in un secondo tempo fuori linea. Il concettoiniziale era che alla fine tutti sarebbero stati online per fare qualunque cosa di una certaimportanza, ma la cosa si è rivelata ben presto poco pratica (sebbene in un futuro appli-cazioni Internet a basso costo potrebbero soddisfare le esigenze di un segmento significa-tivo di utenti). La soluzione sta nella “applet firmata” che utilizza la crittografia a chiavepubblica per verificare che un’applet venga davvero da dove dice di venire. Un’applet fir-mata può ancora devastarvi il disco, però la teoria è che, siccome potete attribuire la re-sponsabilità al creatore dell’applet, questa non farà cose malvagie. Java mette a disposizio-ne un meccanismo per le firme digitali, in modo che possiate, volendo, consentire aun’applet di scavalcare la barriera protettiva, se necessario.Le firme digitali non tengono conto di una questione importante, che è la velocità con laquale le persone si spostano in Internet. Se scaricate un programma difettoso e questo vifa qualche danno, quanto tempo ci metterete a scoprire l’inconveniente? Potrebbero pas-sare giorni o addirittura settimane. In quel momento, come potrete risalire al programmache ha fatto il danno? E a quel punto, a che cosa vi servirà saperlo?

Internet rispetto a intranetIl Web è la soluzione più generale del problema client/server, quindi è ragionevole pensa-re di ricorrere alla stessa tecnologia per risolvere un sottoinsieme del problema, in parti-colare il classico problema client/server all’interno di una società. Quando si impostanosistemi client/server tradizionali bisogna tener conto delle possibili diversità fra i compu-ter client e delle difficoltà che comporta l’installazione di nuovo software client, tutti pro-blemi che si risolvono comodamente con browser Web e programmazione dal lato client.Quando si ricorre alla tecnologia Web per una rete di informazioni circoscritta a una de-terminata azienda si parla di intranet. Le intranet garantiscono una sicurezza molto supe-riore a quella di Internet, dal momento che è possibile controllare fisicamente l’accesso aiserver che si trovano all’interno dell’azienda. In termini di addestramento, risulta chequando le persone hanno capito il concetto generale di browser è per loro molto più facileaffrontare le differenze fra gli aspetti delle pagine e delle applet, per cui la pendenza dellacurva di apprendimento per i nuovi tipi di sistemi si riduce.Il problema della sicurezza ci porta a una delle divisioni che sembrano formarsi automa-ticamente nel mondo della programmazione dal lato client. Se un vostro programma girain Internet, non sapete sotto quale piattaforma verrà eseguito e dovrete essere estrema-mente cauti per non disseminare codice difettoso. Vi serve qualcosa che operi su più piat-taforme e sia sicuro, come per esempio un linguaggio di scripting oppure Java.Se i vostri programmi vengono eseguiti entro una intranet, potreste avere a che fare conun diverso insieme di vincoli. È molto probabile che le vostre macchine siano tutte piat-taforme Intel/Windows. In una intranet, siete responsabili della qualità del vostro codicee potete rimediare ai bachi quando vengono scoperti. Inoltre, potrete avere già un certovolume di codice legacy, derivato da vecchie applicazioni, che utilizzate secondo una im-postazione client/server più tradizionale, nel cui contesto dovete installare fisicamenteprogrammi client ogni volta che fate un potenziamento. Il tempo che viene sprecatonell’installare i potenziamenti è una delle ragioni più forti che spingono a passare ai brow-ser, perché i potenziamenti sono invisibili e automatici. Se siete coinvolti in un’intranetdi questo tipo, la strada più ragionevole da seguire è il percorso più breve che vi consentedi utilizzare il vostro codice base esistente, invece di cercare di ricodificare i vostri pro-grammi in un nuovo linguaggio.Quando ci si trova ad affrontare questa sconcertante gamma di soluzioni per il problemadella programmazione dal lato client, il miglior piano di attacco consiste nell’eseguire

001ThIJava01.fm Page 28 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 29

un’analisi costi/benefici. Prendete in considerazione i vincoli che il vostro problema vipone e quale potrebbe essere il percorso più breve per arrivare a una soluzione. Dal mo-mento che la programmazione dal lato client è pur sempre programmazione, vi convienesempre scegliere l’impostazione più veloce per la vostra situazione specifica. In questomodo sarete meglio preparati per affrontare gli inevitabili problemi dello sviluppo deiprogrammi.

Programmazione dal lato serverTutta questa rassegna ha trascurato la programmazione dal lato server. Che cosa accadequando fate una richiesta a un server? Nella maggior parte dei casi la richiesta si riassumesemplicemente nel dire “mandami questo file”. Sarà poi il vostro browser a interpretare ilfile nella maniera più opportuna: come una pagina HTML, un’immagine grafica, un’ap-plet Java, un programma script ecc.. Una richiesta più complicata rivolta a un server disolito comporta una transazione su database. Lo scenario più comune è l’esecuzione diuna complessa ricerca su un database, che il server successivamente formatta in una pagi-na HTML che invia come risultato. (Naturalmente se sul client è disponibile una mag-giore intelligenza, tramite Java o un linguaggio di scripting, i dati grezzi possono essereinviati e poi formattati dal lato client, in tempi più brevi e con minore carico sul server.)Oppure potreste aver bisogno di registrare il vostro nome in un database, quando entratea far parte di un gruppo o emettete un ordine, eventi che comportano modifiche su queldatabase. Richieste di questo genere inviate a un database devono essere elaborate tramitedel codice che opera dal lato server, costituendo quella che si usa chiamare programma-zione dal lato server. Per tradizione, la programmazione dal lato server è sempre stata ese-guite mediante script Perl e CGI, però sono comparsi sistemi più raffinati. Fra questi tro-viamo server Web basati su Java, che vi consentono di eseguire tutte le operazioni di pro-grammazione dal lato server in Java scrivendo programmi chiamati servlet. Le servlet e iloro derivati, le JSP, sono due delle ragioni più importanti che spingono le società che svi-luppano server Web a passare a Java, soprattutto perché in questo modo eliminano i pro-blemi che derivano dal dover trattare con browser provvisti di abilitazioni differenti.

Un’area separata: le applicazioniIl gran chiacchiericcio che accompagna Java è prevalentemente concentrato sulle applet.Java, però, è in realtà un linguaggio di programmazione di tipo generale, che può risolverequalunque problema, almeno in teoria. E come abbiamo già fatto notare, potrebbero esi-stere modi molto più efficaci per risolvere la maggior parte dei problemi client/server.Quando uscite dall’area delle applet (e al tempo stesso lasciate cadere i vincoli, come quel-lo che impedisce la scrittura su disco) entrate nel mondo delle applicazioni di tipo gene-rale, che vengono eseguite per conto loro, senza un browser Web, come fa qualunque al-tro programma normale. Qui, la forza di Java non sta soltanto nella sua portabilità, maanche nella sua programmabilità. Come potrete vedere nel resto di questo libro, Java hamolte caratteristiche che vi consentono di creare programmi robusti in tempi più brevi diquelli che sarebbero necessari con i linguaggi di programmazione precedenti.Tenete presente che questi vantaggi non sono esenti da costi. I miglioramenti si paganoin termini di minori velocità di esecuzione (sebbene si lavori in modo significativo su que-sto aspetto: JDK 1.3, in particolare, introduce i cosiddetti “hotspot” per migliorare le pre-stazioni). Come qualunque altro linguaggio Java ha limiti intrinseci che potrebbero ren-derlo poco adatto per risolvere determinati problemi di programmazione. Java è un lin-guaggio in rapida evoluzione, però, e a mano a mano che escono nuove versioni diventasempre più attraente per risolvere insiemi sempre più ampi di problemi.

001ThIJava01.fm Page 29 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 130

Analisi e progettazioneIl paradigma dell’orientamento agli oggetti è un modo nuovo e diverso di pensare allaprogrammazione. Molte persone hanno qualche difficoltà iniziale ad affrontare un pro-getto OOP. Una volta, però, che uno sa che tutte le cose devono essere considerate ogget-ti e impara a pensare secondo uno stile più orientato agli oggetti, può cominciare a creare“buone” progettazioni che sfruttano tutti i vantaggi offerti dalla OOP.Un metodo (spesso chiamato anche metodologia) è un insieme di processi e di meccanismieuristici utilizzati per scomporre la complessità di un problema di programmazione. Findagli albori della programmazione orientata agli oggetti sono stati formulati molti meto-di. In questi paragrafi cercheremo di farvi intuire che cosa si cerca di ottenere quando siutilizza un metodo.In modo particolare nella OOP, la metodologia è un campo molto sperimentale, quindiè importante capire quale problema il metodo sta cercando di risolvere, prima di pensaredi adottarne uno. Questo vale in modo particolare per Java, nel quale il linguaggio di pro-grammazione ha lo scopo di ridurre la complessità (rispetto al C) che comporta l’espres-sione di un programma. Ciò potrebbe in effetti ridurre il fabbisogno di metodologie sem-pre più complesse. In effetti, metodologie semplici possono bastare in Java per classi diproblemi molto più ampie di quelle che si potrebbero affrontare utilizzando metodologiesemplici con linguaggi procedurali.È anche importante rendersi conto che il termine “metodologia” è spesso troppo magni-loquente e promette troppo. Tutto ciò che fate ora quando progettate e scrivete un pro-gramma è un metodo. Potrebbe essere un vostro metodo personale, che applicate in mo-do inconscio, però è un processo che percorrete mentre create. Se è un processo efficace,potrebbe bastare soltanto una piccola messa a punto perché funzioni anche in Java. Senon siete soddisfatti della vostra produttività e del modo in cui vengono fuori i vostri pro-grammi, potreste prendere in considerazione l’idea di adottare un metodo formale, oppu-re scegliere elementi dai vari metodi formali esistenti.Mentre seguite il processo di sviluppo, la questione più importante è: non perdetevi.È facile farlo. La maggior parte dei metodi di analisi e progettazione ha lo scopo di risol-vere il maggiore dei problemi. Ricordate che la maggior parte dei progetti non rientra inquesta categoria, per cui di solito avrete successo nell’analisi e nella progettazione utiliz-zando soltanto un piccolo sottoinsieme di ciò che un metodo raccomanda7. Tuttavia unprocesso di un qualche tipo, non importa quanto limitato, in generale vi metterà sul giu-sto cammino molto meglio di quanto non fareste cominciando subito a codificare.È anche facile restare impantanati, cadere nella “paralisi da analisi”, nella quale avete lasensazione di non poter proseguire perché non avete ancora sistemato ogni più piccoloparticolare dello stadio corrente. Ricordate, per quanto approfondita possa essere la vostraanalisi, vi sono sempre cose di un sistema che non si lasciano rivelare fino al momentodella progettazione e altre ancora, in maggior quantità, che non si manifestano fino aquando non vi mettete a codificare o addirittura fino a quando il programma non è finitoed è in esecuzione. Per queste ragioni, è essenziale percorrere velocemente le fasi di analisie progettazione e implementare un collaudo del sistema in sviluppo.C’è un punto che merita di essere sottolineato. Per via della storia che abbiamo vissutocon i linguaggi procedurali, è encomiabile una squadra che intenda procedere con cautelae capire ogni minimo particolare prima di passare alla progettazione e all’implementazio-

7 Un ottimo esempio a questo riguardo è UML Distilled, seconda edizione, di Martin Fowler (Addison-Wesley 2000), che riduce il processo ULM, che talvolta è soffocante, a un sottoinsieme gestibile.

001ThIJava01.fm Page 30 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 31

ne. Certo, quando si crea un DBMS c’è tutto da guadagnare a capire in modo esaurientei fabbisogni di un cliente. Ma un DBMS è una classe di problemi che sono ben formulatie ben capiti; in molti programmi di questo genere la struttura del database è il problemada affrontare. La classe di problemi di programmazione di cui ci occupiamo in questo ca-pitolo appartiene alla famiglia dei “jolly” (termine mio), nella quale la soluzione non puòessere trovata semplicemente ricreando una soluzione ben conosciuta, ma coinvolge inve-ce uno o più “fattori jolly”: elementi per i quali non esiste una soluzione precedente bencapita e per i quali è necessario effettuare delle ricerche8. Tentare di analizzare in modoesauriente un problema jolly prima di passare alla progettazione e all’implementazioneporta alla paralisi da analisi, perché non si hanno sufficienti informazioni per risolverequesto genere di problemi durante la fase di analisi. Per risolvere questo tipo di problemioccorre ripercorrere più volte l’intero ciclo, e questo esige un comportamento inclineall’assunzione di rischi (cosa che ha un suo senso, perché si cerca di fare qualcosa di nuovoe il compenso potenziale è più elevato). Potrebbe sembrare che il rischio aumenti “affret-tando” una implementazione preliminare, ma ciò potrebbe invece ridurre il rischio in unprogetto jolly, perché si scopre con anticipo se un determinato approccio al problema èplausibile. Sviluppare un prodotto significa gestire il rischio.Spesso si propone di “costruirne uno da buttare”. Con la OOP, vi troverete certo a but-tarne via una parte, ma siccome il codice è incapsulato nelle classi, durante la prima passataprodurrete inevitabilmente qualche schema di classe utile e svilupperete valide idee in me-rito al progetto del sistema che non saranno da buttare. Di conseguenza, una prima rapidapassata sul problema non soltanto produce informazioni importanti per la successiva pas-sata di analisi, progettazione e implementazione, ma crea anche una base di codice.Detto questo, se andate in cerca di una metodologia che contenga enormi volumi di det-tagli e imponga molti passi e documenti, è difficile stabilire dove fermarsi. Tenete benchiaro in mente quel che state cercando di scoprire:

1. Quali sono gli oggetti? (Come scomponete il vostro progetto in parti?)2. Quali sono le loro interfacce? (Quali messaggi dovete mandare a ciascun oggetto?)

Se venite fuori con nient’altro che gli oggetti e le loro interfacce, potete cominciare a scri-vere un programma. Per varie ragioni potreste aver bisogno di una maggior quantità diinformazioni e di documenti, ma non potete cavarvela con meno di questo.Il processo può essere articolato in cinque fasi, più una Fase 0 che è semplicemente l’im-pegno iniziale a utilizzare un qualche tipo di struttura.

Fase 0: fare un pianoCome prima cosa dovete decidere in quali passi si articolerà il vostro processo. Sembrasemplice (e in effetti tutto questo sembra semplice) eppure la gente spesso non prendequesta decisione prima di mettersi a codificare. Se il vostro piano è “diamoci sotto e co-minciamo a scrivere il codice”, bene. (A volte è quello giusto, quando il problema è benchiaro.) Almeno accettate l’idea che il piano è questo.Potreste anche decidere in questa fase che il processo va strutturato ancora un po’, ma sen-za impegnarvi eccessivamente. Si può capire che a certi programmatori piaccia lavorarecon “spirito vacanziero”, senza una struttura che ingabbi il processo di sviluppo del loro

8 La mia regola a spanne per stimare progetti di questo genere: se c’è più di un solo jolly, non tentateneppure di pianificare quanto tempo ci vorrà o quanto vi verrà a costare fino a quando non avrete creatoun prototipo funzionante. I gradi di libertà sono troppi.

001ThIJava01.fm Page 31 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 132

lavoro; “Sarà fatto quando sarà fatto”. La cosa può anche essere divertente, per un po’, mami sono accorto che avere qualche obiettivo intermedio, le pietre miliari (o milestone co-me vengono chiamate nel gergo dei pianificatori), aiuta a focalizzare e a stimolare gli im-pegni riferendoli a quelle pietre miliari invece di ritrovarsi con l’unico obiettivo di “finireil progetto”. Inoltre, così si suddivide il progetto in segmenti più agevoli da afferrare, fa-cendolo diventare meno minaccioso (e poi le tappe intermedie sono ottime occasioni perfesteggiamenti).Quando cominciai a studiare la struttura narrativa (così una volta o l’altra scriverò un ro-manzo) provavo una certa riluttanza nei confronti del concetto di struttura, perché misembrava di scrivere meglio quando buttavo giù le pagine direttamente. In seguito, però,mi resi conto che, quando scrivo di computer, la struttura mi è chiara al punto che nonci devo pensare più di tanto. Ma strutturo comunque mentalmente il mio lavoro, seppurein modo non del tutto consapevole. Anche se siete convinti che il vostro piano sia di met-tervi subito a codificare, finirete comunque col percorrere le fasi che seguono mentre vifarete certe domande e vi darete le risposte.

Dichiarare la missioneQualunque sistema andrete a costruire, per quanto complicato, ha uno scopo fondamen-tale; il contesto nel quale si trova, il fabbisogno base che deve soddisfare. Se riuscite a guar-dare al di là dell’interfaccia utente, dei particolari specifici dell’hardware o del software,degli algoritmi di codifica e dei problemi di efficienza, finirete per scoprire il nucleo es-senziale del sistema: semplice e diretto. Come la cosiddetta idea base di un film di Hol-lywood, potrete descriverlo con una o due frasi. Questa descrizione pura è il punto di par-tenza. L’idea base è molto importante perché dà il tono a tutto il vostro progetto; è la di-chiarazione della missione. Non riuscirete a coglierla con esattezza fin dalla prima volta(potreste trovarvi in una fase successiva del progetto prima che diventi del tutto chiara),però insistete finché non vi sembra giusta. Per esempio, in un sistema per il controllo deltraffico aereo potreste cominciare con un’idea base focalizzata sul sistema che state co-struendo: “Il programma della torre tiene traccia degli aerei”. Pensate, però, a quel che ac-cade se riducete il sistema alla dimensione di un aeroporto molto piccolo; magari c’è unsolo controllore del traffico, o addirittura nessuno. Un modello più utile, invece di riferirsialla soluzione che state creando, descrive il problema: “Arrivano aeroplani, scaricano, siriforniscono, ricaricano e ripartono”.

Fase 1: che cosa stiamo facendo?Nella progettazione procedurale, come veniva chiamato il metodo di progettazione dei pro-grammi della generazione precedente, questa fase era dedicata a “creare l’analisi dei requi-siti e la specifica del sistema”. Si trattava di attività nelle quali era facile smarrirsi; certi do-cumenti, i cui soli nomi già mettevano soggezione, finivano per diventare a loro voltagrossi progetti. Le intenzioni erano buone, però. L’analisi dei requisiti dice “Fate un elen-co delle linee guida che utilizzerete per sapere quando il lavoro è concluso e il cliente èsoddisfatto”. La specifica del sistema dice “Questa è la descrizione di ciò che il programmafarà (non come) per soddisfare i requisiti”. L’analisi dei requisiti è in realtà un contrattofra voi e il cliente (che può anche essere qualcuno che lavora nella vostra stessa aziendaoppure qualche altro oggetto o sistema). La specifica del sistema è una panoramica gene-rale del problema e in un certo senso la verifica che si possa risolvere e quanto ci vorrà perrisolverlo. Siccome richiedono entrambi un accordo fra persone (e siccome queste di so-lito cambiano col passare del tempo), penso sia meglio produrre documenti brevi ed es-senziali – idealmente, puri elenchi e diagrammi schematici – per risparmiare tempo. Po-

001ThIJava01.fm Page 32 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 33

trebbero esservi altri vincoli che vi costringono ed ampliarli in documenti più voluminosi,però se ci si impegna a dare al documento iniziale un contenuto breve e conciso, è possi-bile crearlo in poche sedute di discussione in gruppo, con un leader che crea dinamica-mente la descrizione. In questo modo non soltanto si stimola il contributo di tutti, ma sifavorisce l’adesione iniziale e l’accordo di tutti i componenti della squadra. Quel che è piùimportante, forse, è il fatto che così si può avviare un progetto con molto entusiasmo.È necessario non perdere mai di vista quel che si vuole ottenere in questa fase: stabilire checosa il sistema dovrebbe fare. A questo scopo, lo strumento che più si dimostra utile è unaraccolta di “casi di utilizzo”, come si suole chiamarli. I casi di utilizzo identificano carat-teristiche chiave del sistema che metteranno in evidenza alcune delle classi fondamentaliche andrete a utilizzare. Sono sostanzialmente risposte descrittive a domande del tipo9:

“Chi utilizzerà questo sistema?”“Che cosa possono fare col sistema questi attori?”“Come fa questo attore a fare quello con questo sistema?”“In quale altro modo questo potrebbe funzionare se lo facesse qualcun altro oppure se lo stesso attore avesse un obiettivo diverso?” (per mettere in luce le variazioni)“Quale problema potrebbe manifestarsi mentre si fa questo col sistema?” (per far venir fuori le aspettative)

Se, per esempio, state progettando uno sportello bancario automatico, il caso di utilizzodi un aspetto particolare della funzionalità è in grado di descrivere quello che lo sportelloautomatico fa in ogni possibile situazione. Ciascuna di queste “situazioni” prende il nomedi scenario e un caso di utilizzo può essere considerato una raccolta di scenari. Potete pen-sare a uno scenario come una domanda che comincia con: “Che cosa fa il sistema se. . .?”.Per esempio, “Che cosa fa lo sportello automatico se un cliente ha versato un assegno nelcorso delle ultime 24 ore e non c’è sufficiente disponibilità nel conto per soddisfare unarichiesta di prelievo se l’assegno non è stato ancora accettato?”I diagrammi dei casi di utilizzo sono deliberatamente semplici, per evitarvi di restare im-pantanati prematuramente nei particolari dell’implementazione del sistema.

9 Ringrazio per l’aiuto che ho ricevuto da James H. Jarrett.

Versa

Preleva

Chiede saldo conto

Effettua giroconto

Cliente

Sportellista

Terminale

Utilizzi

Banca

001ThIJava01.fm Page 33 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 134

Ciascun pupazzetto rappresenta un “attore”, che di solito è un essere umano o qualchealtro tipo di agente libero. (Questi possono essere anche altri sistemi computerizzati, co-me nel caso di “Terminale”.) Il riquadro rappresenta i confini del vostro sistema. Gli ovalirappresentano i casi di utilizzo, che sono descrizioni di lavori utili che si possono eseguirecon il sistema. Le linee che collegano attori e casi di utilizzo rappresentano le interazioni.Non importa come il sistema sia effettivamente implementato, basta che si presenti inquesto modo all’utente.Un caso di utilizzo non deve essere terribilmente complesso, anche se il sistema sottostan-te è complesso. Serve soltanto per far vedere il sistema nel modo in cui appare all’utente.Per esempio:

I casi di utilizzo producono le specifiche dei requisiti stabilendo le interazioni che l’utentepuò avere col sistema. Tentate di scoprire un insieme completo di casi di utilizzo per il vo-stro sistema e, quando ci sarete riusciti, avrete il nucleo di quello che il sistema dovrebbe fa-re. Il bello dei casi di utilizzo sta nel fatto che vi riconducono sempre all’essenziale e vi im-pediscono di sconfinare su questioni che non sono essenziali ai fini del risultato del lavoro.In altri termini, se avete un insieme completo di casi di utilizzo, siete in grado di descrivereil vostro sistema e di passare alla fase successiva. Probabilmente non vi riuscirà di avere ilquadro completo fin dal primo tentativo, ma va bene così. Tutto salterà fuori, col passaredel tempo e se pretendete di avere in questa fase una specifica perfetta del sistema finireteimpantanati.Se vi impantanate per davvero, potete rilanciare questa fase ricorrendo a un grossolanostrumento di approssimazione: descrivete il sistema con poche frasi e cercate nomi e verbi.I nomi possono suggerire attori, contesto del caso di utilizzo (per esempio “magazzino”)oppure manufatti manipolati nel caso di utilizzo. I verbi possono suggerire interazioni fraattori e casi di utilizzo e specificare passi entro il caso di utilizzo. Scoprirete anche che no-mi e verbi producono oggetti e messaggi durante la fase di progettazione (e tenete presen-te che i casi di utilizzo descrivono interazioni fra sottosistemi, quindi la tecnica “nomi everbi” può essere utilizzata soltanto come strumento per una seduta di brainstorming,perché non produce casi di utilizzo)10. La linea di demarcazione fra un caso di utilizzo e un attore può segnalare l’esistenza diun’interfaccia utente, ma non la definisce. Per approfondire il processo col quale si defi-niscono e si creano interfacce utente, consultate Software for Use di Larry Constantine eLucy Lockwood (Addison-Wesley Longman, 1999) oppure visitate il sito www.ForU-se.com.

10 Ulteriori informazioni sui casi di utilizzo si possono trovare in Applying Use Cases di Schneider e Win-ters (Addison-Wesley, 1998) e Use Case Driven Object Modeling with UML di Rosenberg (Addison-We-sley, 1999).

Conservare la temperatura

di crescitaGiardiniere

Serra

001ThIJava01.fm Page 34 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 35

Per quanto sia un po’ un’arte magica, a questo punto è importante tracciare un piano dimassima. Avete a disposizione una panoramica di quello che intendete costruire, quindidovreste riuscire a farvi un’idea del tempo che ci vorrà. Qui entrano in gioco parecchi fat-tori. Se dalla vostra stima emerge un piano molto lungo. la società potrebbe decidere dinon realizzarlo (e quindi utilizzare le risorse su qualcosa di più ragionevole: e questa èun’ottima cosa). Oppure un dirigente potrebbe aver già deciso per conto suo quanto tem-po ci vorrà per realizzare il progetto e cercherà di influenzare in questo senso le vostre sti-me. La cosa migliore, però, è definire onestamente un piano fin dall’inizio e affrontarequanto prima le decisioni più ardue. Sono stati fatti un sacco di tentativi per individuaretecniche di pianificazione accurate (non molto diverse da quelle impiegate per fare previ-sioni sul mercato azionario), però la cosa migliore è fidarvi della vostra esperienza e delvostro intuito. Fate una stima a braccio di quanto ci metterete per davvero, poi raddop-piatela e sommate un dieci per cento. La vostra stima a braccio sarà probabilmente cor-retta; riuscirete a ottenere qualcosa che funziona entro quel periodo di tempo. Raddop-piandola, trasformerete la stima in qualcosa di plausibile e il dieci per cento servirà per iritocchi finali e i dettagli11. Comunque vogliate spiegarlo, e indipendentemente dalle la-mentazioni e dalle manovre che salteranno fuori quando rivelerete un piano del genere,sembra che le cose vadano comunque in questo modo.

Fase 2: come lo costruiremo?In questa fase dovete produrre un progetto che descriva l’aspetto delle classi e il modo incui interagiscono. Una tecnica eccellente per determinare classi e interazioni è la schedaClasse-Responsabilità-Collaborazione (CRC). La validità di questo strumento deriva in par-te dal fatto che si basa su un tecnologia molto povera: si comincia con un pacchetto dischede di cartoncino bianche in formato 7 × 12 e vi si scrive sopra. Ciascuna scheda rap-presenta una sola classe e sulla scheda si scrive:

1. Il nome della classe. È importante che questo nome colga l’essenza di quello che laclasse fa, in modo che ne esprima il senso a prima vista.

2. Le “responsabilità” della classe: che cosa dovrebbe fare. Di solito questo si può riepi-logare enunciando semplicemente i nomi delle funzioni membro (perché questi no-mi, in un progetto ben fatto, devono essere descrittivi), ma non sono escluse altre an-notazioni. Se avete bisogno di innescare il processo, considerate il problema dal pun-to di vista di un programmatore pigro: quali oggetti vi piacerebbe che comparisseromagicamente per risolvere il vostro problema?

3. Le “collaborazioni” della classe: con quali altre classi interagisce? “Interagire” è un ter-mine deliberatamente molto ampio; potrebbe significare aggregazione o semplice-mente che esiste qualche altro oggetto che eseguirà servizi per un oggetto della classe.Le collaborazioni devono anche considerare l’uditorio di questa classe. Per esempio,se create una classe FuocoArtificio, chi la osserverà, un Chimico o uno Spettatore?Il primo vorrà sapere quali sostanze chimiche sono impiegate nella costruzione, men-tre il secondo reagirà ai colori e alle forme che si producono quando il fuoco d’artifi-cio esplode.

11 Su questo tema ho cambiato idea, di recente. Raddoppiando e aggiungendo un dieci per cento otter-rete una stima ragionevolmente accurata (supponendo che non vi siano troppi fattori jolly), però dovretecomunque lavorare di lena per arrivare a finire entro quel periodo di tempo. Se volete il tempo necessarioper fare le cose in modo davvero elegante e godervi il processo, il moltiplicatore giusto, secondo me, èpiù vicino a tre o a quattro volte.

001ThIJava01.fm Page 35 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 136

Potreste pensare che le schede dovrebbero essere più grandi per contenere tutte le infor-mazioni che vi piacerebbe scrivervi sopra, ma sono deliberatamente piccole, non soltantoper far sì che le classi siano piccole, ma anche per impedirvi di impegnarvi troppo prestoin troppi particolari. Se non riuscite a far stare in una piccola scheda tutto quello che do-vete sapere su una classe, la classe è troppo complessa (o state scendendo troppo nei par-ticolari, o dovreste creare più di una sola classe). La classe ideale deve essere capita a primavista. Le schede CRC sono concepite per aiutarvi a produrre una prima stesura del pro-getto, in modo da avere il quadro di massima per poi raffinarlo.Le schede CRC si dimostrano particolarmente vantaggiose ai fini della comunicazione.È qualcosa che è meglio fare in tempo reale, in un gruppo, senza computer. Ogni personasi prende la responsabilità di svariate classi (che dapprima non hanno nomi né altre infor-mazioni). Si esegue una simulazione dal vivo risolvendo un dato scenario per volta, stabi-lendo quali messaggi vengono inviati ai vari oggetti per soddisfare ciascun scenario. Men-tre si percorre questo processo si scoprono le classi di cui si ha bisogno insieme con le lororesponsabilità e collaborazioni, e si riempiono le schede a mano a mano che si procede.Quando sono stati percorsi tutti i casi di utilizzo, dovrebbe essere pronto un primo sche-ma generale del progetto, ragionevolmente completo.Prima di cominciare a usare le schede CRC, la mia esperienza di consulente di maggiorsuccesso è stata quando mi sono trovato a dover lavorare sulla fase iniziale di progettazionedisegnando oggetti su una lavagna con una squadra che non aveva mai costruito prima unprogetto OOP. Parlavamo di come gli oggetti avrebbero dovuto comunicare fra loro, necancellavamo alcuni e li sostituivamo con altri oggetti. In pratica, gestivo tutte le “schedeCRC” sulla lavagna. Era la squadra (che sapeva che cosa il progetto avrebbe dovuto fare)che creava materialmente il disegno generale; erano loro i “proprietari” del disegno, nonera qualcosa che gli veniva dato da altri. Io non facevo altro che orientare il processo po-nendo le domande giuste, mettendo alla prova le ipotesi e ricevendo le reazioni della squa-dra per modificare quelle ipotesi. Il bello del processo era nel fatto che la squadra imparavaa fare progettazione orientata agli oggetti non studiando esempi astratti, ma lavorandosull’unico disegno che per loro era il più interessante in quel momento: il loro.Quando avrete messo insieme un pacchetto di schede CRC, potrebbe esservi utile creareuna descrizione più formale del progetto utilizzando l’UML12. Non è obbligatorio servir-sene, però può essere d’aiuto, specialmente se volete appendere un diagramma sul muro,in modo che tutti possano pensarci sopra, che è un’ottima idea. Un’alternativa all’ULMè una descrizione testuale degli oggetti e delle loro interfacce, oppure, a seconda del lin-guaggio di programmazione utilizzato, il codice stesso13. Con l’ULM si dispone anche di una ulteriore notazione grafica per descrivere il modellodinamico di un sistema. Questo aiuta in situazioni nelle quali le transizioni di stato di unsistema o di un sottosistema sono dominanti al punto da aver bisogno di propri diagram-mi (come nel caso di un sistema di controllo). Può anche essere necessario descrivere lastruttura dei dati, per sistemi o sottosistemi nei quali i dati sono un fattore dominante(come nel caso di un database).Capirete di aver concluso la Fase 2 quando avrete descritto gli oggetti e le loro interfacce.Beh, non proprio tutti, la maggior parte: ce ne sono sempre alcuni che sfuggono e non simanifestano fino alla Fase 3. Ma non è un problema. L’unica cosa che vi interessa davveroè arrivare a scoprire tutti i vostri oggetti, alla fine. Fa certo piacere scoprirli all’inizio del

12 Per cominciare, raccomando il libro citato in precedenza, ULM Distilled, seconda edizione.13 Il Python (www.python.org) viene spesso utilizzato come “pseudocodice eseguibile”.

001ThIJava01.fm Page 36 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 37

processo, ma la struttura dell’OOP fa sì che non sia grave scoprirli in tempi successivi. Ineffetti, la progettazione di un oggetto tende ad articolarsi in cinque stadi, nell’arco dellosviluppo del programma.

Cinque stadi di progettazione degli oggettiL’attività di progettazione di un oggetto non si limita al solo periodo nel quale si scrive ilprogramma. Invece, si manifesta in una successione di stadi. Vi può essere utile averechiara questa prospettiva, perché così smettete di aspettarvi di arrivare subito alla perfe-zione; vi rendete conto, invece, che per capire che cosa fa un oggetto e che aspetto dovreb-be avere deve passare un po’ di tempo. Questo punto di vista vale anche per la progetta-zione di vari tipi di programmi; l’ossatura di un particolare tipo di programma emerge dalmisurarsi più e più volte con un problema specifico. (Di questa esperienza parlo inThinking in Patterns with Java, scaricabile da www.BruceEckel.com.) Anche gli oggettihanno proprie ossature, che emergono dall’utilizzo ripetuto.

1. Scoperta degli oggetti. Questo stadio si manifesta durante l’analisi iniziale di unprogramma. Gli oggetti si possono scoprire cercando fattori esterni e linee di confine,duplicazione di elementi nel sistema e le più piccole unità concettuali. Alcuni oggettisono ovvi se avete già un insieme di librerie di classi. La comunanza fra classi, che fapensare a classi base e all’ereditarietà, può comparire fin dal primo momento oppurepiù avanti nel corso della progettazione.

2. Assemblaggio degli oggetti. Nel costruire un oggetto scoprirete la necessità di nuovimembri che non erano manifesti al momento della scoperta. Le necessità internedell’oggetto possono richiedere altre classi per supportarlo.

3. Costruzione del sistema. Ancora una volta, ulteriori requisiti per un oggetto posso-no venir fuori in uno stadio successivo. Mentre voi imparate, fate evolvere i vostri og-getti. Il fabbisogno di comunicazione e di interconnessione con altri oggetti nel siste-ma può modificare le necessità delle vostre classi o richiedere nuove classi. Per esem-pio, poteste scoprire che occorrono classi facilitatrici o di guida, tipo liste concatena-te, che contengono poche (o nessuna) informazioni di stato, ma che aiutano altreclassi a funzionare.

4. Estensione del sistema. Nell’aggiungere nuove funzionalità a un sistema potrestescoprire che il progetto precedente non supporta un’agevole estensione del sistema.Con queste nuove informazioni potete ristrutturare parti del sistema, eventualmenteaggiungendo nuove classi o nuove gerarchie di classi.

5. Riutilizzo degli oggetti. Questo è il vero collaudo strutturale di una classe. Se qual-cuno prova a riutilizzarla in una situazione interamente nuova, probabilmente ne sco-prirà alcune limitazioni. Mentre modificate una classe per adattarla a un maggior nu-mero di nuovi programmi, i principi generali di quella classe vi diventeranno piùchiari, fintanto che arriverete a un tipo davvero riutilizzabile. Non vi aspettate, però,che la maggior parte degli oggetti del progetto di un sistema sia riutilizzabile: è per-fettamente accettabile che il grosso dei vostri oggetti sia specifico del sistema. I tipiriutilizzabili tendono a essere meno comuni e devono risolvere problemi di caratterepiù generale per poter essere riutilizzabili.

Linee guida per lo sviluppo degli oggettiQuesti stadi suggeriscono alcune linee guida quando pensate a sviluppare le classi:

1. Fate in modo che un problema specifico generi una classe, quindi lasciate che la classecresca e maturi durante la soluzione di altri problemi.

001ThIJava01.fm Page 37 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 138

2. Ricordate che scoprire le classi di cui avete bisogno (e le loro interfacce) rappresentala parte maggiore della progettazione del sistema. Se quelle classi fossero già disponi-bili, si tratterebbe di un progetto facile.

3. Non sforzatevi di conoscere tutto fin dall’inizio; imparate strada facendo. Questosuccederà comunque.

4. Cominciate a programmare; ottenete qualcosa di funzionante che possa dimostrare ilvostro progetto o farlo cadere. Non abbiate paura di ritrovarvi con un codice spaghet-ti tipico della programmazione in stile procedurale: le classi suddividono il problemae aiutano a tenere sotto controllo anarchia ed entropia. Le classi cattive non romponoquelle buone.

5. Tenete le cose semplici. Oggetti piccoli e puliti con un’ovvia utilità sono meglio digrandi interfacce complicate. Quando si tratta di decidere, attenetevi al rasoio di Oc-cam: valutate le alternative e selezionate la più semplice, perché le classi semplici sonoquasi sempre le migliori. Cominciate con cose piccole e semplici, tanto potrete espan-dere l’interfaccia della classe quando la capirete meglio. Col passare del tempo, è dif-ficile eliminare elementi da una classe.

Fase 3: costruire il nucleoQuesta è la conversione iniziale dalla prima bozza in un insieme di codice che si compilae si esegue e può essere collaudato, ma soprattutto che dimostra la validità o la non vali-dità della vostra architettura. Non si tratta di un processo da svolgere in una sola passata,ma è piuttosto l’inizio di una serie di passi che costruiranno iterativamente il sistema, co-me vedrete nella Fase 4.Il vostro obiettivo è trovare il nucleo dell’architettura da implementare per generare unsistema funzionante, non importa quanto quel sistema sia incompleto in questa passatainiziale. State creando uno schema di riferimento sul quale continuare a costruire con ul-teriori iterazioni. State anche eseguendo la prima di molte operazioni di integrazione disistema e di collaudo, facendo sapere a tutti gli interessati come si presenterà il loro siste-ma e come sta progredendo. Idealmente, vi state anche esponendo a qualche rischio criti-co. Scoprirete anche, molto probabilmente, modifiche e miglioramenti che si potrebberoapportare all’architettura originale: cose che non avreste potuto capire senza implementa-re il sistema.Fa parte del processo di costruzione di sistema il confronto con la realtà che ottenete col-laudandolo a fronte dell’analisi dei requisiti e delle specifiche del sistema (in qualunqueforma queste esistano). Assicuratevi che i vostri test verifichino i requisiti e i casi di utiliz-zo. Quando il nucleo del sistema è stabile, siete pronti a procedere oltre e ad aggiungerenuove funzionalità.

Fase 4: iterare i casi di utilizzoUna volta che il nucleo base gira, ciascuna funzionalità che aggiungete è di per sé un pic-colo progetto. Un insieme di funzionalità si aggiunge durante un’iterazione, un periododi sviluppo relativamente breve. Quanto dura un’iterazione? Idealmente, da una a tre settimane (può variare in funzionedel linguaggio di implementazione). Alla fine di quel periodo, avrete un sistema integratoe collaudato, con più funzionalità di quelle che aveva prima. Quello, però, che è partico-larmente interessante è la base dell’iterazione: un solo caso di utilizzo. Ciascun caso di uti-lizzo è un pacchetto di funzionalità correlate che integrate nel sistema in un colpo solo,durante una sola iterazione. Non soltanto questo modo di operare vi dà un’idea migliore

001ThIJava01.fm Page 38 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 39

di quel dovrebbe essere la portata di un caso di utilizzo, ma conferisce maggior validitàall’idea di ricorrere a un caso di utilizzo, dal momento che il concetto non viene scartatodopo l’analisi e la progettazione, ma è invece un’unità di sviluppo fondamentale nel corsodell’intero processo di costruzione del software.Le iterazioni finiscono quando arrivate alla funzionalità prefissata oppure matura una sca-denza esterna e il cliente può venir soddisfatto dalla versione corrente. (Ricordate, quellodel software è un mercato per abbonamenti.) Siccome il processo è iterativo, avete molteopportunità per consegnare un prodotto invece che un solo punto terminale; i progetti ditipo open source funzionano esclusivamente in un ambiente iterativo, a elevato feedback,ed è esattamente per questo che hanno tanto successo.Sono molte le ragioni che giustificano un processo iterativo. Potete rilevare e risolvere conmolto anticipo situazioni rischiose, i clienti hanno ampie possibilità di cambiare idea, lasoddisfazione dei programmatori è più elevata e il progetto può essere guidato con mag-gior precisione. Un ulteriore, importante vantaggio viene dal feedback verso gli interessa-ti, che possono rilevare dallo stato attuale del prodotto a che punto si trovano tutti gli ele-menti. Ciò può ridurre o addirittura eliminare la necessità di condurre estenuanti riunio-ni di avanzamento, aumentando la fiducia e il sostegno da parte degli interessati.

Fase 5: evoluzioneQuesto è quel punto nel ciclo dello sviluppo che si chiamava tradizionalmente “manuten-zione”, un termine ombrello che può significare qualunque cosa, da “farlo funzionare nelmodo in cui doveva davvero funzionare fin dall’inizio” a “aggiungere funzionalità che ilcliente si era dimenticato di indicare” ai più tradizionali “correggere gli errori che sonosaltati fuori” e “aggiungere nuove funzionalità quando ce n’è bisogno”. Al termine “ma-nutenzione” sono stati attribuiti un tal numero di significati distorti che ha finito coll’as-sumere una qualità leggermente ingannevole, in parte perché suggerisce che avete in realtàcostruito un programma immacolato, che basta lubrificare e tenere al riparo dalla ruggine.Forse esiste un termine migliore per descrivere come stanno le cose.Utilizzerò il termine evoluzione14. Vale a dire, “Non vi verrà giusto la prima volta, quindiconcedetevi il respiro sufficiente per imparare, tornare sui vostri passi e modificare”. Po-treste aver bisogno di fare un mucchio di modifiche a mano a mano che imparate e capitepiù a fondo il problema. L’eleganza che raggiungerete evolvendo fino a ottenere il risulta-to giusto vi ripagherà sia nel breve sia nel lungo periodo. Evoluzione è quando il vostroprogramma passa da buono a eccellente e quando diventano chiari certi aspetti che nonavevate capito bene nella prima passata. E si manifesta anche quando le vostre classi daoggetti utilizzati per un solo progetto evolvono in risorse riutilizzabili.Quando si dice “farlo giusto” non si intende soltanto che il programma funziona secondoi requisiti e i casi di utilizzo. Significa anche che la struttura interna del codice ha per voiun senso e vi dà la sensazione di essere ben integrata, senza contorcimenti sintattici, og-getti sovradimensionati o frammenti di codice esposti goffamente. Inoltre, dovete ancheavere la sensazione che la struttura del programma sopravvivrà alle modifiche alle qualisarà inevitabilmente soggetto durante la sua vita e che tali modifiche potranno essere ef-fettuate agevolmente e in modo pulito. Non è cosa da poco. Non dovete soltanto capirequello che state costruendo, ma anche come il programma evolverà (quello che io chiamoil vettore del cambiamento). Fortunatamente i linguaggi di programmazione orientati agli

14 Almeno un aspetto dell’evoluzione è trattato nel libro di Martin Fowler, Refactoring: improving thedesign of existing code (Addison-Wesley, 1999), che utilizza esclusivamente esempi Java.

001ThIJava01.fm Page 39 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 140

oggetti sono particolarmente adatti a supportare questo tipo di continue modifiche: iconfini creati dagli oggetti sono quel che impedisce alla struttura di collassare. Questi lin-guaggi vi consentono anche di effettuare modifiche – che sembrerebbero drastiche in unprogramma procedurale – senza provocare terremoti in tutto il resto del codice. In effetti,il supporto dell’evoluzione potrebbe essere il vantaggio più importante dell’OOP.Tramite l’evoluzione arrivate a creare qualcosa che almeno si avvicina a quello che pensatedi star costruendo, potete toccare con mano quel che avete ottenuto, paragonarlo conquel che volevate ottenere e vedere se manca qualcosa. A questo punto potete tornare in-dietro e rimediare, riprogettando e implementando di nuovo le parti del programma chenon funzionavano correttamente15. Potreste davvero aver bisogno di risolvere il proble-ma, o un suo aspetto, più di una volta prima di azzeccare la soluzione giusta. (Di solito inquesti casi è utile studiare i Design Patterns. Potete trovare informazioni in Thinking inPatterns with Java, scaricabile da www.BruceEckel.com)Si ha evoluzione anche quando costruite un sistema, vedete che corrisponde con i vostrirequisiti, e poi scoprite con in realtà non era quello che volevate. Quando osservate il si-stema in funzione, scoprite che in realtà volevate risolvere un problema diverso. Se ritene-te che questo tipo di evoluzione possa manifestarsi, avete il dovere verso voi stessi di co-struire la vostra prima versione il più rapidamente possibile, in modo da poter scoprire seè proprio quella che volevate.Forse la cosa più importante da ricordare è che, per default – per definizione, in realtà –se modificate una classe le sue super e sottoclassi continueranno a funzionare. Non doveteaver paura delle modifiche (specialmente se avete un insieme integrato di test unitari perverificare la correttezza delle vostre modifiche). Le modifiche non devastano necessaria-mente il programma e qualunque cambio nel risultato sarà circoscritto alle sottoclassi e/oagli specifici collaboratori della classe che avrete modificato.

Pianificare convieneNaturalmente non vi metterete mai a costruire una casa senza un bel po’ di piani di co-struzione accuratamente disegnati. Se dovete costruire una tettoia o un canile non vi ser-viranno disegni molto elaborati, ma anche in questi casi probabilmente inizierete conqualche schizzo che vi servirà per orientarvi. Lo sviluppo del software è passato da unestremo all’altro. Per molto tempo, la gente non si curava affatto della struttura quandofaceva sviluppo, ma poi i progetti di maggiori dimensioni hanno cominciato a fallire. Perreazione, ci siamo ritrovati a ricorrere a metodologie che avevano una quantità terrificantedi struttura e di dettagli, concepite soprattutto per quei progetti di grandi dimensioni.Erano metodologie troppo spaventose da utilizzare: sembrava che uno dovesse passaretutto il suo tempo a scrivere documentazione, senza mai dedicare tempo alla programma-zione. (E spesso era proprio quello che accadeva.) Spero che quello che vi ho presentatofin qui suggerisca una via di mezzo: una scala mobile. Scegliete l’approccio che meglio siadatta alle vostre necessità (e alla vostra personalità). Anche se deciderete di ridurlo a di-mensioni minime, qualche tipo di piano rappresenterà un notevole miglioramento per ilvostro progetto rispetto alla mancanza assoluta di un piano. Ricordate che, secondo le sti-

15 Questo assomiglia un po’alla tecnica dei “prototipi veloci”, con la quale si costruiva una versione gros-solana del sistema, allo scopo di capirlo meglio, per poi buttar via il prototipo e costruire quello giusto.Il guaio dei prototipi veloci è che la gente non buttava via il prototipo, ma invece vi costruiva sopra.Combinata con la mancanza di struttura della programmazione procedurale, questa maniera di operarespesso produce sistemi pasticciati, con elevati costi di manutenzione.

001ThIJava01.fm Page 40 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 41

me più diffuse, più del 50 per cento dei progetti fallisce (alcune stime arrivano fino al 70per cento!).Seguendo un piano – meglio se è uno semplice e breve – e arrivando a una struttura delprogetto prima di iniziare la codifica, scoprirete che le cose si mettono insieme molto piùagevolmente di quando vi tuffate nella mischia e cominciate a menare fendenti. Otterreteanche parecchia soddisfazione. In base alla mia esperienza, arrivare a una soluzione ele-gante è qualcosa che soddisfa profondamente a un livello interamente diverso; ci si sentepiù vicini all’arte che alla tecnologia. E l’eleganza rende sempre; non è un obiettivo frivoloda perseguire. Non soltanto vi dà un programma più facile da costruire e da correggere,ma sarà anche più facile da capire e da gestire ed è qui che si annida il suo valore econo-mico.

Programmazione estrema Ho studiato tecniche di analisi e di progettazione, a più riprese, fin dai tempi delle supe-riori. Il concetto di programmazione estrema – Extreme Programming (XP) – è il più ra-dicale, e il più delizioso, che abbia mai incontrato. Potete trovarne la cronistoria in Extre-me Programming Explained, di Kent Beck (Addison-Wesley, 2000) e nel Web all’indirizzowww.xprogramming.com.XP è al tempo stesso una filosofia sul lavoro di programmazione e un insieme di linee gui-da per farlo. Alcune di queste linee guida sono riflesse in altre metodologie recenti, ma idue contributi più importanti e specifici, secondo me, sono “scrivere prima i test” e “pro-grammare in coppia”. Sebbene sostenga con forza l’intero processo, Beck rileva che l’ado-zione di queste due sole pratiche migliorerà notevolmente la vostra produttività e la vostraaffidabilità.

Scrivere prima i testI collaudi per tradizione sono stati relegati nell’ultima fase di un progetto, quando “fun-ziona tutto, ma tanto per essere sicuri”. Per definizione, i test avevano una bassa prioritàe le persone che si specializzavano in materia avevano poco prestigio e spesso finivano iso-late in uno scantinato, lontane dai “veri programmatori”. Le squadre di collaudo hannorisposto per le rime, arrivando al punto di vestirsi di nero e sghignazzare con perfidia tuttele volte che riuscivano a far cadere qualcosa (a essere sinceri, anch’io ho avuto questa sen-sazione, quando mettevo in crisi un compilatore).XP rivoluziona completamente questa concezione del collaudo, dando ai test la stessapriorità del codice (o addirittura una più elevata). In effetti, i test si scrivono prima di scri-vere il codice da collaudare e restano permanentemente associati al codice. I test devonoessere eseguiti con successo ogni volta che si fa un’integrazione del progetto (il che avvienespesso, in certi casi più volte al giorno).Scrivere i test come prima cosa ha due effetti estremamente importanti.In primo luogo, impone una chiare definizione dell’interfaccia di una classe. Ho spessosuggerito di “immaginare la classe perfetta per risolvere un particolare problema” comestrumento da utilizzare quando si progetta il sistema. La strategia dei test di XP si spingeben oltre: specifica esattamente come la classe deve presentarsi al consumatore di quellaclasse e il modo esatto in cui la classe deve comportarsi. E questo senza ambiguità. Potetescrivere tutta la prosa o creare tutti i diagrammi che volete, per descrivere come dovrebbecomportarsi una classe e che aspetto deve avere, ma niente è reale come un insieme di test.La roba di prima sono pii desideri, ma i test sono un contratto che si stabilisce fra il com-pilatore e il programma in esecuzione: è difficile immaginare una descrizione di una classepiù concreta dei test.

001ThIJava01.fm Page 41 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 142

Quando scrivete i test, siete costretti a pensare la classe fino in fondo e spesso scopriretefunzionalità che sono necessarie ma che non erano state trovate tramite gli esperimenticon i diagrammi UML, le schede CRC, i casi di utilizzo e quant’altro.Il secondo effetto importante che si ottiene dallo scrivere prima i test deriva dalla loro ese-cuzione ogni volta che fate una compilazione del vostro software. Questa attività vi forni-sce l’altra metà del collaudo che viene eseguita del compilatore. Se osservate l’evoluzionedei linguaggi di programmazione da questa prospettiva, noterete che gli autentici miglio-ramenti nella tecnologia sono avvenuti in realtà intorno ai collaudi. Il linguaggio assem-blatore controllava soltanto la sintassi, ma il C ha imposto alcuni vincoli semantici, che viimpediscono di fare determinati tipi di errori. I linguaggi OOP impongono ulteriori vin-coli semantici che, se ci pensate, sono in realtà forme di collaudo. “Questo tipo di datoviene utilizzato in modo appropriato?” e “Questa funzione viene chiamata in modo cor-retto?” sono i tipi di test che vengono eseguiti dal compilatore o dal sistema run time. Ab-biamo visto che cosa succede quando meccanismi di collaudo di questi tipo sono incor-porati nel linguaggio: la gente è in grado di scrivere programmi più complessi e di metterliin funzione in meno tempo e con minor fatica. Mi sono spesso domandato come mai lecose stiano in questo modo, ma ora mi rendo conto che sono i test: voi fate qualcosa disbagliato e la rete di sicurezza costituita dai test incorporati vi dice che c’è un problema evi indica dove si trova.Tuttavia, le forme di collaudo intrinseco fornite dall’impostazione del linguaggio possonoarrivare solo fino a un certo punto. Arriva un momento in cui siete voi che dovete farviavanti e aggiungere i test rimanenti che producono un insieme completo (in cooperazionecon il compilatore e il sistema run time) che verifica l’intero vostro programma. E, cosìcome avete un compilatore che vi guarda le spalle, non vorreste forse che questi test vi aiu-tassero fin dall’inizio? Ecco perché dovete scrivere prima i test ed eseguirli automatica-mente a ogni nuova compilazione del vostro sistema. I vostri test diventano un’estensionedella rete di sicurezza fornita dal linguaggio.Una delle cose che ho scoperto in merito all’utilizzo di linguaggi di programmazione sem-pre più potenti è il fatto che mi sento incoraggiato a tentare esperimenti sempre più az-zardati, perché so che il linguaggio mi impedirà di sprecare tempo andando a caccia dibachi. La logica dei test di XP fa la stessa cosa per l’intero vostro progetto. Siccome sapeteche i vostri test intercetteranno sempre eventuali problemi che voi stessi creerete (e voi ag-giungerete sistematicamente nuovi test quando penserete a quei problemi), potete faremodifiche importanti, quando serve, senza preoccuparvi di mandare in crisi l’intero pro-getto. Tutto questo è davvero molto potente.

Programmare in coppiaLa programmazione in coppia è contraria al rude individualismo al quale veniamo indot-trinati fin dai nostri primi passi, tramite la scuola (dove raggiungiamo gli obiettivi o limanchiamo da soli e lavorare insieme con i compagni è considerato “copiare”) e tramitei mezzi di comunicazione, specialmente i film di Hollywood, nei quali l’eroe di solitocombatte contro il bieco conformismo16. Anche i programmatori sono considerati mo-delli di individualismo – “cowboy della codifica”, come ama dire Larry Constantine. Ep-pure, XP, che pure si batte contro il modo di pensare tradizionale, sostiene che il codiceandrebbe scritto da due persone per stazioni di lavoro. E che si dovrebbe farlo in un’areache contenga un gruppo di stazioni di lavoro, senza le barriere che piacciono tanto agli

16 Questo forse è un punto di vista molto americano, però le storie di Hollywood arrivano ovunque.

001ThIJava01.fm Page 42 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 43

arredatori degli uffici. In effetti Beck sostiene che il primo passo verso la conversione a XPconsiste nell’arrivare al lavoro muniti di cacciaviti e di chiavi a tubo e smontare tutto quel-lo che ingombra17. (Per far questo ci vuole un dirigente capace di sviare le ire del repartoche gestisce gli ambienti di lavoro.)Il valore della programmazione in coppia sta nel fatto che una sola persona scrive mate-rialmente il codice mentre l’altra ci pensa sopra. Il pensatore tiene presente il quadro ge-nerale, non soltanto il quadro del problema sul quale si lavora, ma le linee guida di XP.Se sono in due a lavorare, è meno probabile che uno di loro possa cavarsela dicendo “Nonvoglio scrivere prima i test”, per esempio. E se quello che codifica resta impantanato, ilcollega può dargli il cambio. Se restano impantanati entrambi, le loro riflessioni possonoessere udite da qualcun altro nella stessa area di lavoro, che può dare una mano. Lavorarein coppia mantiene le cose in movimento e in riga. Cosa probabilmente più importante,rende la programmazione molto più sociale e divertente.Ho cominciato a utilizzare la programmazione in coppia durante le esercitazioni in alcunimiei seminari e sembra che la cosa migliori in modo significativo l’esperienza di ognuno.

Perché Java ha successoLa ragione del successo di Java sta nel fatto che si proponeva di risolvere molti dei problemiche deve affrontare chi fa sviluppo al giorno d’oggi. L’obiettivo di Java è una maggior pro-duttività. Questa produttività si ottiene in molti modi, però il linguaggio è concepito perdarvi il massimo aiuto possibile, vincolandovi il meno possibile con regole arbitrarie o conl’onere di utilizzare un determinato insieme di caratteristiche. Java è stato concepito peressere qualcosa di pratico; nel progettare il linguaggio ci si è preoccupati di fornire i mas-simi vantaggi al programmatore.

I sistemi sono più facili da esprimere e da capireLa classi concepite per adattarsi al problema tendono a esprimerlo meglio. Questo vuoldire che, quando scrivete il codice, descrivete la vostra soluzione nei termini dello spaziodel problema (“Metti il manico al secchio”) invece che nei termini del computer, che è lospazio della soluzione (“Imposta nel circuito il bit che significa che il relè verrà chiuso”).Lavorate con concetti di livello superiore e potete fare molto di più con una sola riga dicodice. L’altro vantaggio che deriva da questa facilità di espressione sta nella manutenzione che(se possiamo credere alle statistiche) assorbe un’enorme quota dei costi durante il ciclo divita di un programma. Se un programma è più facile da capire, è anche più facile farne lamanutenzione. Il che può anche ridurre i costi della creazione e della manutenzione delladocumentazione.

Le librerie danno un forte contributoIl modo più rapido per creare un programma consiste nell’utilizzare codice già scritto:una libreria. Uno degli obiettivi principali di Java è rendere più agevole l’utilizzo delle li-brerie. Questo si ottiene facendo il casting di librerie in nuovi tipi di dati (classi), per cui

17 Includendo (soprattutto) il sistema di segnalazione con altoparlanti. Una volta ho lavorato in una so-cietà che aveva la fissa di segnalare con altoparlanti tutte le telefonate che arrivavano a tutti i dirigenti,interrompendo in continuazione la nostra produttività (però i quadri non concepivano nemmeno l’ideadi mettere a tacere un servizio così importante come gli avvisi tramite altoparlanti). Andò a finire che,quando nessuno mi stava a osservare, cominciai a recidere i fili degli altoparlanti.

001ThIJava01.fm Page 43 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 144

richiamare una libreria significa aggiungere nuovi tipi al linguaggio. Siccome è il compi-latore di Java che si prende cura del modo in cui la libreria viene utilizzata – garantendouna corretta inizializzazione e una successiva pulizia e assicurando che le funzioni venganochiamate correttamente – potete concentrarvi su quel che volete che la libreria faccia, nonsu come utilizzarla.

Gestione degli erroriLa gestione degli errori nel C è un problema ben noto e che viene spesso ignorato: di so-lito ci si limita a tenere le dita incrociate. Mentre state costruendo un programma com-plesso e di grande respiro, non c’è niente di peggio che ritrovarsi con un errore seppellitochi sa dove senza avere la minima idea sulla sua provenienza. La gestione delle eccezioni diJava è un modo per garantire che un errore venga rilevato e che in conseguenza di questoaccada qualcosa.

Programmare senza limitazioniMolti linguaggi tradizionali hanno limitazioni intrinseche per quanto riguarda dimensio-ni e complessità dei programmi. Il BASIC, per esempio, può essere ottimo per mettereassieme rapide soluzioni per determinate classi di problemi, però se il programma si allun-ga su troppe pagine o si avventura al di fuori dal dominio normale dei problemi di quellinguaggio, ci si ritrova a tentare di nuotare in un liquido che diventa sempre più vischio-so. Non c’è una chiara linea di demarcazione che vi segnali quando il linguaggio che stateutilizzando non vi va più bene e anche se ci fosse la ignorereste. Non dite “Questo pro-gramma in BASIC è diventato davvero troppo lungo; dovrò riscriverlo in C!” Invece, ten-tate di ficcarci dentro ancora un po’ di righe per aggiungere giusto una sola nuova funzio-nalità. E così i costi supplementari cominciano a prendere il sopravvento.Java è concepito per aiutare a programmare senza limitazioni: vale a dire, per cancellarequelle linee di demarcazione derivate dalla complessità che si collocano fra un programmapiccolo e uno grande. Non vi servirà certo l’OOP per scrivere un programma di servizionello stile “hello world”, però le funzionalità sono a disposizione per quando vi servono.E il compilatore è molto aggressivo quando si tratta di stanare errori che generano bachitanto nei programmi di piccole dimensioni come in quelli grandi.

Strategie per la transizioneSe fate vostro il concetto di OOP, probabilmente la domanda che vi porrete subito doposarà: “Come posso convincere il mio capo/i colleghi/il reparto/gli amici a utilizzare gli og-getti?” Riflettete su come fareste voi – programmatori indipendenti – a imparare a usareun nuovo linguaggio e un nuovo paradigma di programmazione. L’avete già fatto in pas-sato. Prima vengono la formazione e gli esempi; poi viene un progetto di prova per darviil senso dei concetti essenziali senza fare nulla che possa creare confusione. Infine viene unprogetto del “mondo reale”, che fa davvero qualcosa di utile. Nel corso dei primi progetticontinuerete nella vostra formazione leggendo, facendo domande agli esperti e scambian-do suggerimenti con gli amici. È questo l’approccio che molti programmatori esperti sug-geriscono per passare a Java. Il passaggio di un’intera azienda produrrà, naturalmente, al-cune dinamiche di gruppo, ma a ogni punto di svolta verrà utile ricordare come se l’è ca-vata una singola persona.

001ThIJava01.fm Page 44 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 45

Linee guidaQueste che seguono sono alcune linee guida da considerare quando si passa all’OOP e aJava.

1. AddestramentoIl primo passo da compiere è una qualche forma di addestramento. Tenete presente gliinvestimenti che la vostra società ha già fatto nel codice e cercate di non scardinare tuttoper sei o nove mesi mentre tutti cercano di capire come funzionano le interfacce. Sceglieteun piccolo gruppo da indottrinare, preferibilmente formato da persone dotate di curiosi-tà, che lavorano bene assieme e che possono assistersi a vicenda mentre imparano il Java.Talvolta si suggerisce un approccio alternativo, che consiste nel formare tutti i livelli dellasocietà in un colpo solo, tenendo corsi di orientamento generale per i dirigenti e corsi diprogettazione e programmazione per i capi progetto. Questo è ottimo per imprese di pic-cole dimensioni che si accingono a dare una svolta radicale al loro modo di lavorare o peril livello divisionale di società di maggiori dimensioni. Siccome, però, i costi sono più ele-vati, si preferisce di solito iniziare con un addestramento a livello di progetto, eseguire unprogetto pilota (magari con l’aiuto di un mentore esterno) e lasciare che le persone chehanno partecipato al progetto diventino i docenti per il resto della società.

2. Progetti a basso rischioCominciate con un progetto a basso rischio e prevedete che si sbaglierà qualcosa. Quandoavrete fatto un po’ di esperienza, potrete avviare altri progetti affidandoli ai componentidel primo gruppo di lavoro oppure assegnare a queste persone il compito di dare assisten-za tecnica per l’OOP. Il primo progetto potrebbe non funzionare bene la prima volta,quindi non dovrà essere un progetto di importanza critica per la società. Dovrebbe esserequalcosa di semplice, contenuto in se stesso e istruttivo; questo vuol dire che dovrebbeportare a creare classi che saranno significative per gli altri programmatori della societàquando verrà il loro turno per imparare il Java.

3. Trarre ispirazione dai progetti ben riuscitiCercate esempi di buona progettazione orientata agli oggetti prima di cominciare da zero.È molto probabile che qualcuno abbia già risolto il vostro problema e, se proprio non lohanno risolto esattamente come vi si presenta, potete probabilmente applicare quello cheavete imparato sull’astrazione per modificare uno schema esistente e adattarlo alle vostrenecessità. Questo è il principio ispiratore dei design patterns, descritti in Thinking in Pat-terns with Java, scaricabile da www.BruceEckel.com.

4. Utilizzare librerie di classi esistentiLa principale motivazione economica per passare all’OOP è la comodità con la quale sipuò utilizzare codice esistente sotto forma di librerie di classi (in particolare le librerieStandard, trattate estesamente in tutto il libro). Il ciclo di sviluppo applicativo più brevelo otterrete quando potrete creare e utilizzare oggetti ricavati da librerie bell’e pronte.Tuttavia, certi programmatori alle prime armi questo non lo sanno, non conoscono le li-brerie di classi in circolazione oppure, affascinati dal linguaggio, hanno voglia di scrivereclassi che potrebbero già esistere. Otterrete migliori risultati con l’OOP e il Java se vi im-pegnerete a cercare e a riutilizzare codice di altri fin dalle prime fasi del processo di tran-sizione.

001ThIJava01.fm Page 45 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 146

5. Non riscrivere in Java codice esistenteDi solito non è il modo migliore per utilizzare il vostro tempo prendere un codice esisten-te e funzionante e riscriverlo in Java. (Se siete obbligati a trasformarlo in oggetti, poteteinterfacciare il codice in C o in C++ utilizzando la Java Native Interface, descritta nell’Ap-pendice B.) Si possono avere vantaggi incrementali, specialmente se si prevede di riutiliz-zare il codice. Ma probabilmente non riuscirete a vedere gli spettacolari miglioramenti diproduttività sui quali contate nei vostri primi progetti se questi non saranno nuovi pro-getti. Java e OOP danno il meglio di sé quando si porta un progetto dalla sua prima con-cezione alla realtà.

Ostacoli gestionaliSe siete un capo, il vostro lavoro è quello di acquisire risorse per la vostra squadra, supe-rare le barriere che impediscono alla squadra di avere successo e in generale creare l’am-biente più produttivo e gradevole che sia possibile affinché la vostra squadra possa farequei miracoli che di solito vi si chiede. Passare al Java ha riflessi in tutte e tre queste cate-gorie e sarebbe davvero meraviglioso se non vi costasse qualcosa. Sebbene il passaggio alJava possa essere più economico – dipende dai vincoli che avete – di altre alternative OOPper una squadra di programmatori in C (e probabilmente per programmatori in altri lin-guaggi procedurali), non è del tutto gratuito e vi sono ostacoli che farete bene a conoscereprima di promuovere il passaggio al Java all’interno della vostra società e di imbarcarvi nelpassaggio vero e proprio.

Costi di avvioIl costo del passaggio al Java è molto più che la semplice acquisizione di compilatori Java(il compilatore Java della Sun è gratuito, quindi non è certo un ostacolo). I vostri costi dimedio e lungo periodo si ridurranno al minimo se investirete in addestramento (e magariin una consulenza per il primo progetto) e se inoltre individuerete e acquisterete libreriedi classi che risolvono il vostro problema, piuttosto di tentare di costruire voi stessi quellelibrerie. Questi sono costi in denaro contante, che vanno conteggiati in una proposta re-alistica. Inoltre, vi sono costi nascosti che derivano dalla perdita di produttività mentre siimpara un nuovo linguaggio ed eventualmente un nuovo ambiente di programmazione.Addestramento e consulenza possono certamente ridurre al minimo questi costi, ma icomponenti della squadra devono superare le loro difficoltà nel capire la nuova tecnolo-gia. Durante questo processo faranno un maggior numero di sbagli (e questo è un vantag-gio, perché gli sbagli riconosciuti sono la via più rapida per l’apprendimento) e sarannomeno produttivi. Anche in questi casi, almeno per determinati tipi di problemi di pro-grammazione, disponendo delle classi giuste e con un ambiente di sviluppo adeguato, èpossibile essere più produttivi mentre si impara il Java (pur tenendo conto che si fannopiù sbagli e si scrivono meno righe di codice al giorno) di quanto non sarebbe se si restassecol C.

Problemi di prestazioniUna domanda molto diffusa è: “Non è che l’OOP renda automaticamente i miei pro-grammi molto più voluminosi e più lenti?” La risposta è: “Dipende”. Le maggiori funzio-nalità per la sicurezza presenti in Java di solito lo penalizzano rispetto a un linguaggio comeil C++. Certe tecnologie di compilazione hanno migliorato in modo significativo la velo-cità nella maggior parte dei casi e si lavora senza soste per ottenere prestazioni migliori.Quando siete impegnati a costruire alla svelta un prototipo, potete assemblare i compo-nenti il più rapidamente possibile, trascurando l’efficienza. Se utilizzate qualsiasi libreria

001ThIJava01.fm Page 46 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 47

di terzi, di solito sarà già stata ottimizzata dal suo fornitore; in tutti i casi, questo non è unproblema quando lavorate in una prospettiva di sviluppo rapido. Quando avete un siste-ma che vi piace, se è sufficientemente piccolo e veloce, non vi serve altro. Altrimenti, co-minciate a ritoccarlo con uno strumento di profilatura, cercando in primo luogo possibiliaccelerazioni che si potrebbero ottenere riscrivendo piccole parti del codice. Se questonon basta, andate a cercare modifiche che si possono apportare all’implementazione sot-tostante, in modo che non si debba cambiare il codice che utilizza una classe particolare.Soltanto se nient’altro risolve il problema avrete bisogno di modificare la progettazione.Il fatto che le prestazioni siano così critiche in quella parte della progettazione vi fa capireche devono entrare a far parte dei criteri primari della progettazione. Con lo sviluppo ra-pido avete il vantaggio di accorgervi molto presto di questi problemi.Se scoprite una funzione che crea un collo di bottiglia, potrete riscriverla in C/C++ utiliz-zando i metodi nativi di Java, l’argomento dell’Appendice B.

Errori comuni di progettazioneQuando avvierete la vostra squadra a lavorare in OOP e in Java, i programmatori incor-reranno normalmente in una serie di errori comuni di progettazione. Questo spesso di-pende dal fatto che non c’è stato abbastanza feedback da parte di esperti durante la pro-gettazione e l’implementazione dei primi progetti, perché non si sono creati esperti all’in-terno della società o perché c’è stata opposizione all’ingaggio di consulenti. È facile averetroppo presto la sensazione di aver capito l’OOP e di andar via per la tangente. Certe coseche sono ovvie per chi ha già esperienza del linguaggio possono essere materia di grandidiscussioni interne per i novellini. Molti traumi di questo genere si possono evitare ricor-rendo a qualificati esperti esterni per l’addestramento e la consulenza in corso d’opera.

Java contrapposto al C++?Il linguaggio Java assomiglia parecchio al C++ e quindi sembrerebbe naturale che il C++venga rimpiazzato dal Java. Però comincio a dissentire da questa logica. Per dirne una, ilC++ continua ad avere certe funzionalità che il Java non ha e, sebbene siano state fatte unsacco di promesse in merito al Java che un giorno o l’altro diventerà veloce come il C++o ancora più veloce, abbiamo constatato dei miglioramenti costanti, ma nessun passoavanti spettacolare. Inoltre, l’interesse per il C++ continua, quindi non credo che si trattidi un linguaggio destinato a scomparire presto. (I linguaggi tendono a restare in circola-zione. Parlando in uno dei miei seminari su Java, Allen Holub ha dichiarato che i due lin-guaggi più comunemente utilizzati sono il Rexx e il COBOL, in questo ordine.)Comincio a pensare che la forza del Java risieda in un contesto leggermente diverso daquello del C++. Il C++ è un linguaggio che non cerca di adeguarsi a uno schema. Di sicu-ro è stato adattato in vari modi per risolvere problemi particolari. Certi strumenti C++combinano assieme librerie, modelli componenti e strumenti per generare codice per svi-luppare applicazioni a finestre per utenti finali (sotto Microsoft Windows). Eppure, checosa utilizza la gran maggioranza delle persone che fa sviluppo per Windows? Il Visual Ba-sic (VB) della Microsoft. E questo nonostante il fatto che il VB produca proprio quel ge-nere di codice che diventa ingestibile già quando il programma si estende su un po’ di pa-gine (e abbia una sintassi che può essere deliberatamente ingannevole). Per quanto sia dif-fuso e abbia successo, il VB non è certo un esempio di linguaggio ben progettato. Sarebbebello avere la semplicità e la potenza del VB senza il codice ingestibile che ne risulta. Ed èqui, secondo me, che Java avrà la possibilità di brillare: come il “prossimo VB”. Magari ilsolo pensiero potrebbe darvi i brividi, o forse no, ma pensateci sopra: buona parte del Javaè stata concepita per rendere facile a chi programma risolvere problemi a livello applicati-

001ThIJava01.fm Page 47 Tuesday, April 9, 2002 12:07 AM

CAPITOLO 148

vo nel campo delle reti e delle interfacce utente per piattaforme diverse, eppure ha un’im-postazione che permette di creare nuclei di codice molto grandi e flessibili. Aggiungete aquesto il fatto che Java ha i sistemi di controllo dei tipi e di gestione degli errori più solidiche io abbia mai visto in un linguaggio e avete la base per un significativo balzo in avantinella produttività della programmazione.Dovete allora utilizzare il Java invece del C++ per i vostri progetti? A parte le applet Web,due sono i fattori da considerare. Primo, se volete utilizzare molte librerie C++ esistenti (ein queste c’è parecchia produttività) oppure se avete una base consolidata di codice in Co in C++, il Java potrebbe rallentare le vostre attività di sviluppo, invece di accelerarle.Se sviluppate codice per lo più partendo da zero, allora la semplicità del Java rispetto alC++ ridurrà sensibilmente i vostri tempi di sviluppo: testimonianze raccolte da squadre diC++ che sono passate a Java fanno pensare a un raddoppio della velocità di sviluppo ri-spetto al C++. Se le prestazioni non sono un problema o se siete in grado di compensarlein qualche modo, la mera rapidità di realizzazione rende difficile scegliere il C++ rispettoal Java.La questione più rilevante è quella delle prestazioni. Il Java interpretato è sempre statolento, persino da 20 a 50 volte più lento del C con gli interpreti Java originali. Le cosesono migliorate notevolmente col passare del tempo, ma i valori restano ancora alti. Coni computer quel che conta è la velocità; se non fosse significativamente più veloce farlo colcomputer lo fareste a mano. (Ho persino sentito dire che conviene cominciare col Java persfruttare la rapidità dei tempi di sviluppo, per poi utilizzare strumenti e librerie di suppor-to e tradurre il codice in C++, se occorrono elevate velocità di esecuzione.)La chiave per rendere Java accettabile per la maggior parte dei progetti di sviluppo è lacomparsa di miglioramenti della velocità, come per esempio i cosiddetti compilatori “justin time” (JTG), la tecnologia “hotspot” della Sun e addirittura i compilatori in codice na-tivo. Naturalmente i compilatori in codice nativo elimineranno la tanto decantata esecu-zione su più piattaforme dei programmi compilati, però avvicineranno anche la velocitàdegli eseguibili a quella del C e del C++. E compilare un programma Java per più piatta-forme dovrebbe essere parecchio più facile che farlo in C o in C++. (In teoria, basta ri-compilare, ma è una promessa che è già stata fatta per altri linguaggi.)Potrete trovare confronti fra Java e C++ e osservazioni sugli aspetti effettivi di Java nelleappendici alla prima edizione di questo libro. (Si possono trovare nel sito www.bruceec-kel.com.)

RiepilogoQuesto capitolo si propone di farvi cogliere i temi essenziali della programmazione orien-tata agli oggetti e di Java, spiegando perché l’OOP è qualcosa di diverso e perché il lin-guaggio Java in particolare è diverso, presentando i concetti delle metodologie OOP e in-fine le problematiche che dovrete affrontare nel passare all’OOP e a Java.L’OOP e Java possono non essere per tutti. È importante che stabiliate prima le vostrenecessità per decidere se Java potrebbe soddisfarle in modo ottimale o se non vi troverestemeglio con un altro sistema di programmazione (compreso quello che state utilizzandoadesso). Se sapete che le vostre esigenze saranno molto specializzate nel prossimo futuro ese avete vincoli specifici che non possono venire soddisfatti da Java, sarà vostro preciso in-teresse approfondire le alternative18. Anche se poi finirete per scegliere Java come vostro

18 In particolare, vi raccomando di prendere in considerazione il Python (http://www.python.org).

001ThIJava01.fm Page 48 Tuesday, April 9, 2002 12:07 AM

INTRODUZIONE AGLI OGGETTI 49

linguaggio, avrete capito quali sono le opzioni e vi saranno chiare le ragioni che vi hannoportato in quella direzione.Sapete già come si presenta un programma procedurale: definizioni di dati e chiamate difunzione. Per capire un programma del genere bisogna impegnarsi un po’, esaminandochiamate di funzione e concetti a basso livello per creare un modello mentale. È per que-sta ragione che abbiamo bisogno di rappresentazioni intermedie quando progettiamoprogrammi procedurali: di per sé, questi programmi tendono a generare confusione, per-ché si esprimono con termini più orientati al computer che al problema che stiamo risol-vendo.Siccome il linguaggio Java aggiunge molti nuovi concetti a quelli che già si trovano in unlinguaggio procedurale, potreste essere indotti a supporre che la funzione main( ) in unprogramma Java sarà molto più complicata della sua equivalente in un programma in C.In questo caso sarete piacevolmente sorpresi: un programma ben scritto in Java è in gene-rale molto più semplice e molto più facile da capire dell’equivalente programma in C.Quello che vedrete sono le definizioni degli oggetti che rappresentano concetti che si col-locano nello spazio del vostro problema (invece che essere concetti relativi ad aspetti delcomputer) e messaggi inviati a quegli oggetti per rappresentare le attività in quello spazio.Uno degli aspetti piacevoli della programmazione orientata agli oggetti è il fatto che,quando un programma è ben concepito, è facile capire il codice leggendolo. Di solito, c’èanche molto meno codice, perché parecchi dei vostri problemi verranno risolti riutiliz-zando librerie di codice esistenti.

001ThIJava01.fm Page 49 Tuesday, April 9, 2002 12:07 AM

001ThIJava01.fm Page 50 Tuesday, April 9, 2002 12:07 AM