Introduzione alla programmazione
Storia della programmazione
Avvicinandosi alla programmazione, di qualsiasi genere essa sia, bisogna conoscere e capire la storia e le
caratteristiche dei molteplici linguaggi di programmazione. Questo perché è importante conoscere quali eventi hanno contribuito a creare e a far nascere un linguaggio,
rendendoci maggiormente semplice il compito di scegliere
quello più adatto alle nostre finalità e capacità. All'inizio,
negli anni '40, l'unico metodo per sviluppare software era il linguaggio macchina, in altre parole il lavoro del
programmatore era quello di impostare ogni singolo bit a 1
o 0 su enormi computer che occupavano stanze intere e
pesavano decine di tonnellate; si capisce che questo tipo di
1
Capitolo 1
procedimento, non solo era faticoso, ma era riservato ad
una cerchia molto ristretta di persone. Già pochi anni più tardi intorno al 1950, il progresso tecnologico e la
riduzione delle dimensioni e dei costi dei calcolatori, vennero sviluppati due importanti linguaggi di
programmazione, il FORtrAN (FORmula trANslator),
utilizzato strettamente per svolgere in maniera automatica calcoli matematici e scientifici, e l'ALGOL (ALGOrithmic
Language), altro linguaggio per applicazioni scientifiche
sviluppato da Backus (l'inventore del FORtrAN) e da Naur, i quali studiarono un metodo per rappresentare le regole dei vari linguaggi di programmazione che stavano
nascendo. Nel 1960 nacque il COBOL (COmmon Business
Oriented Language), ideato per applicazioni nei campi dell'amministrazione e del commercio, per
l'organizzazione dei dati e la manipolazione dei file. Nel 1964 fa la sua comparsa il BASIC, il linguaggio di
programmazione per i principianti, la sua caratteristica
fondamentale è quella di essere molto semplice, infatti, diventa in pochi anni uno dei linguaggi più utilizzati al
mondo. Intorno al 1970, Niklus Wirth creò il PASCAL per 2
Introduzione alla programmazione
andare incontro alle esigenze di apprendimento dei neoprogrammatori, introducendo anche la possibilità di
creare programmi più leggeri e comprensibili di quelli
sviluppati in basic. Possiamo sicuramente affermare che
l’obiettivo è stato raggiunto, infatti ancora oggi il Pascal
viene usato come linguaggio didattico nelle scuole. Alcuni
anni più tardi fa la sua comparsa il C, il suo maggior punto di forza stava nell’ essere molto versatile nella
rappresentazione dei dati. Anche se, ad un primo appoccio
appare come un linguaggio assai povero data la limitatezza
degli strumenti a disposizione. Al contrario la sua forza
risiede proprio in questi pochi strumenti che permettono di fare qualsiasi cosa, non a caso viene considerato "il
linguaggio di più basso livello tra i linguaggi ad alto livello", per la sua potenza del tutto paragonabile al linguaggio
macchina, mantenendo però sempre una buona facilità
d'uso. Ma la vera rivoluzione si è avuta nel 1983 quando Bjarne Stroustrup inventò il C++ (o come era stato
chiamato inizialmente "C con classi") che introduceva,
sfruttando come base il C, la programmazione Orientata agli Oggetti (OO - Object Oriented) usando una nuova
3
Capitolo 1
struttura, la classe.
Il concetto di classe è semplice, dividere l'interfaccia dal
contenuto, ottenendo in questo modo tanti "moduli" che
comunicano tra loro attraverso le interfacce, permettendo così al programmatore di cambiare il contenuto di una
classe (se sono stati trovati errori o solo per introdurre delle ottimizzazioni) senza troppe variazioni e senza
doversi preoccupare di controllare eventuale altro codice
che richiami la classe. Questo linguaggio ha completamente stravolto il modo di programmare precedente ad esso che, sostanzialmente, si riduceva ad una programmazione procedurale che lasciava poco spazio al riutilizzo del
codice, cambia sostanzialmente l’approccio al mondo della programmazione; basti pensare all'utilizzo del GOSUB nel Basic che usato su migliaia di righe di codice creava
naturalmente confusione, o al Pascal nel quale una
variabile deve essere dichiarata prima di essere usata. Insomma con il C++ è nato un nuovo modo di
programmare, di progettare un programma, di pensare alla programmazione, rendendo il codice scritto più chiaro e soprattutto "riutilizzabile". Altri linguaggi di 4
Introduzione alla programmazione
programmazione che meritano di essere menzionati sono :
il LISP (1959), l'ADA (1970), lo SMALLTALK (1970) e il
LOGO. Sempre grazie al processo tecnologico in continua
evoluzione, negli ultimi anni sono stati creati altri linguaggi
di programmazione come il JAVA, linguaggi di scripting come l'ASP e il PHP .
La scelta del linguaggio…
Ogni linguaggio di programmazione ha le proprie
attitudini, è opportuno conoscerle per creare un sistema
con il minor sforzo e nel minor tempo possibile . Mi sembra doveroso introdurre alcune caratteristiche per i più diffusi campi di applicazione:
• Applicazioni Matematiche o di Ricerca: per questo tipo di programmazione i linguaggi più adatti sono ancora il Fortran e l'Algol, solo perché dalla loro
5
Capitolo 1
creazione è stato scritto ogni genere di programma matematico e quindi non è economicamente
pensabile convertire tutto il codice già esistente per un altro linguaggio di programmazione. Ad oggi esistono però molti applicativi usati in ambito
universitario che hanno potenzialità maggiori in
termini di prestazioni e di rappresentazione grafica, ma rimangono solamente ad uso e consumo dei pochi interessati che studiano materie
scientifiche/ingegneristiche. Naturalmente per
soluzioni o sviluppi proprietari viene molto usato •
anche il C++.
Sistemi Operativi: La programmazione di un
sistema operativo richiede una conoscenza molto
approfondita non solo del linguaggio da utilizzare ma anche del computer a 360° , infatti è necessaio il
controllo di tutte le risorse del computer e ciò è
possibile solamente scrivendo il motore del sistema operativo nel linguaggio più vicino al linguaggio 6
macchina, ovvero l'Assembler il quale è sì
Introduzione alla programmazione
estremamente potente, ma anche estremamente difficile da imparare e, soprattutto, da usare in maniera corretta. Le parti restanti del sistema
operativo (ad esempio l'interfaccia con l'utente) •
generalmente vengono scritte in C o in C++.
Programmi Gestionali: La programmazione di
software gestionali segue due filoni ben distinti; da una parte c'è chi scrive i programmi utilizzando
Visual Basic, e che quindi girano solo su Windows; dall'altra parte, invece, c'è chi utilizza il C/C++ su sistemi operativi Unix (o come si chiama ora, OpenServer) o Linux.
La scelta su quale linguaggio usare è totalmente
libera, bisogna però considerare il bacino d’utenza e quindi quale sistema operativo useranno •
maggiormente i nostri clienti.
Programmi per Windows: Oggi come oggi per creare un programma Windows esistono molti
linguaggi, primo tra tutti il Visual Basic, i più famosi poi sono il Visual C++, Delphi (l'evoluzione visuale
del Pascal) e Java. Insomma per Windows la scelta è
7
Capitolo 1
libera sicuramente data l'ampia fetta di mercato
•
ricoperta da questo Sistema Operativo.
Programmi MultiPiattaforma: Ad oggi l'unico vero linguaggio di programmazione cross-platform è il
Java, che permette di creare programmi che possono
girare indistintamente su Windows, su Macintosh, su Linux , in remoto su pagine internet o, addirittura, su elettrodomestici o cellulari. Il punto di forza di Java rimane la Java Virtual Machine che, una volta
installata su una periferica o un sistema operativo permette di eseguire i programmi Java senza problemi legati all'hardware o alla diversa •
impostazione dei sistemi operativi.
Programmi di Apprendimento: Nei licei e nelle
università per insegnare a programmare utilizzano principalmente tre linguaggi, l'HTML, il Pascal e il
C++. Premesso che ciò varia molto da scuola a scuola e da università ad università, si può dire che questi
linguaggi sono ottimi per imparare a programmare, soprattutto il Pascal, che come abbiamo affermato 8
Introduzione alla programmazione
qualche riga più su, è nato proprio per insegnare le
•
basi della programmazione.
Creazione di Giochi: Lo sviluppo di videogiochi
avviene principalmente utilizzando il C++ e in alcune occasioni (per migliorare la velocità della grafica)
anche l'Assembler. Bisogna dire però che possono
essere creati semplici videogiochi anche utilizzando il Java ed il Flash, anche se quest'ultimo non può essere considerato un vero linguaggio di •
programmazione.
Sviluppo Siti Dinamici: Per lo sviluppo di siti dinamici, invece, si possono utilizzare molti
linguaggi, come il Perl, l'Asp o il Php. Recentemente stanno emergendo anche Python, Zope e Jsp, un
linguaggio di scripting che utilizza le librerie di java.
Per concludere possiamo sicuramente affermare che la scelta del linguaggio da utilizzare per sviluppare un
software, spetta al programmatore. Lo sviluppatore
tenderà a scegliere la via più semplice, la strada che gli permetterà di ottenere il miglior risultato con il minor
9
Capitolo 1
sforzo. Necessariamente dovrĂ considerare, quindi, "cosa" e "per chi" inizierĂ a programmare.
10
Introduzione alla programmazione
11
Il ciclo di vita del software
L’ingegneria del software
La prima parte di questo corso sarà dedicata alla progettazione e in particolare ad un linguaggio di progettazione che è l’UML.
Infatti prima della vera e propria programmazione c’è una
fase impegnativa che è la progettazione, soprattutto per la difficoltà dei software che si realizzano oggi, è necessario
che il programmatore faccia la progettazione di un software prima di mettersi a scrivere il vero è proprio codice in uno o linguaggi di programmazione.
1
Capitolo 2
Generalmente la realizzazione del software non avviene quasi mai per scopi personali, ma si realizza sempre un software per qualcuno che ne ha fatto un’esplicita richiesta,
o al massimo perché si deve lanciare sul mercato. Questo
implica che ci sono diverse figure professionali che gireranno attorno realizzazione di un software e alcune di
queste figure non hanno nulla a che fare con l’informatica o con la programmazione, per cui ci sono tanti elementi da
curare. Queste figure, che costituiranno il team di lavoro, possono essere : il manager dell’azienda, il progettista,
l’analista, il tecnico che dovrà montare l’hardware e il
software da installare, il cliente, gli utenti, i tester, l’ingegnere di sistema e il programmatore…
L’ingegneria del software è la materia che ha come oggetto di studio tutto quello che ha a che fare con la costruzione di
un sistema completo. Studia le migliori strategie da utilizzare come metodo di sviluppo di sistemi software di
ogni genere, a partire da un semplice sito web fino ad arrivare agli applicativi più complessi. 2
Il ciclo di vita del software
Secondo l’ingegneria del software un software è definito
come i programmi, le procedure, e la documentazione
necessaria e tutti i dati relativi alle funzionalità`di un sistema.
Quindi L’ingegneria del software e` una
disciplina
tecnologica e gestionale che riguarda la produzione e la manutenzione
dei
prodotti
software
che
vengono
sviluppati e modificati entro i tempi e i costi preventivati.
Quindi possiamo anche definire l’ingegneria del software
come un corpus di teorie, metodi e strumenti , sia di tipo
tecnologico che organizzativo , che consentano di produrre applicazioni con le caratteristiche desiderate.
Le caratteristiche del software
Per definire un buon software deve avere delle precise caratteristiche quali :
3
Capitolo 2
• Affidabilità; • Efficienza;
• Facilità di utilizzo; • Manutenibilità.
Un software si dice affidabile quando in caso di guasto, non produce danni fisici od economici. E’ tanto più affidabile quanto più raramente, durante l'uso del sistema, si manifestano malfunzionamenti.
Un software è efficiente quando non fa un uso
indiscriminato di memoria e tempo di calcolo. Quindi se usa memoria (RAM), CPU (processore) e le altre risorse
necessarie in modo proporzionato alle funzioni che svolge, cioè senza sprechi.
La facilità d’utilizzo si riferisce alla semplicità con cui un utente può svolgere le operazioni che il sistema fornisce. Il
software deve essere corredato di una interfaccia utente e della documentazione appropriate.
La manutenibilità è la facilità di apportare modifiche a sistema realizzato. La manutenzione riguarda infatti ogni
4
Il ciclo di vita del software
miglioramento del software ed è intesa in termini di evoluzione del software. La manutenzione è di tre tipi :
1. Correttiva : serve a correggere gli errori fatti durante le fasi di scrittura del codice o durante le fasi precedenti;
2. Perfettiva
:
serve
a
perfezionare
il
codice,
aggiungendone funzionalità, riguarda le modifiche fatte per migliorare le qualità del software;
3. Adattativa : serve ad adattare il software alle nuove richieste del mercato hardware e nel sistema
operativo.
Il ciclo di vita del software
Ci sono una serie di passaggi che un team di sviluppo deve rispettare per ottenere un buon risultato finale. Durante il
corso di studio daremo particolare importanza alla progettazione studiando un linguaggio di modellazione che 5
Capitolo 2
si chiama UML che è importantissimo ai fini della comprensione di un sistema.
Quando un sistema viene creato, di qualsiasi genere esso
sia e con qualsiasi linguaggio venga scritto dovrebbe
rispettare le seguenti fasi: • Studio di fattibilità
• Analisi dei requisiti e specifica dei requisiti
• Progettazione (design)
• Coding (sviluppo) • test (o collaudo) • Manutenzione
Prendiamo in esame le singole fasi.
Lo studio di fattibilità corrisponde all’analisi che si effettua su ciò che si deve creare. Non è una fase dalle caratteristiche tecnico informatiche, ricopre più gli aspetti del marketing. Un analista cerca di capire le esigenze del
6
Il ciclo di vita del software
cliente e se il progetto può essere preso in carica dal team di lavoro. E’ quindi una valutazione preliminare dei costi e dei tempi di sviluppo di un’applicazione, per stabilire se si
debba avviarne lo sviluppo, quali siano le alternative
possibili, quali le scelte più ragionevoli, e quali sono le risorse finanziarie e umane necessarie per l’attuazione del
progetto. Lo studio di fattibilità generalmente viene fatto da un analista e soprattutto da un manager che valuta i costi.
La fase dell’analisi dei requisiti come si può facilmente immaginare è svolta da un analista che con interrogando il cliente
verifica
dall’applicativo,
nei
quali
dettagli
cosa
vuole
dovrebbero essere i
ottenere risultati
dell’utilizzo dello stesso e quali caratteristiche e qualità si
aspetta di trovare una volta che l’applicativo sarà completato. Per poter generare una lista di requisiti
completa, è importante consultare anche gli stakeholder, gli utenti finali del sistema.
Si stabiliscono, quindi, le funzionalità , i vincoli e gli
obiettivi consultando sia il cliente che gli utenti. Il risultato 7
Capitolo 2
principale della fase di analisi è il Documento di Specifica dei Requisiti (DSR) che deve essere approvato dal
committente, seppur sia una visione superficiale del
progetto. Questo documento dovrà essere comprensibile
sia agli utenti che agli sviluppatori perché costituisce un
riferimento, il punto di partenza per l’attività di sviluppo del prodotto.
Con le informazioni ottenute nella fase di analisi si inizia a modellare
il
programma.
Ha
inizio
quindi
l’importantissima fase della progettazione. Dovremmo aver
risposto alla domanda “cosa deve fare il mio programma?” ora ci domandiamo “come posso realizzare?”.
La progettazione è una parte fondamentale nel ciclo di vita di un software, perché una progettazione ben fatta è metà
lavoro fatto. Il programmatore se ha un progetto ben scritto incontrerà molte meno difficoltà nella stesura del codice.
La fase della progettazione può procedere in due modi differenti. Gli approcci top-down e bottom-up.
8
Il ciclo di vita del software
La strategia top down è definita anche come metodo di
raffinamento per passi successivi. Con questo approccio, viene formulata una visione generale del sistema senza
scendere nel dettaglio di alcuna delle sue parti. Ogni parte del sistema è successivamente suddivisa, aggiungendo maggiori dettagli. In questo modo le nuove parti del progetto ottenute possono essere suddivise in parti più
piccole e maggiormente dettagliate, finché la specifica
completa è sufficientemente dettagliata. Si segue il
principio del “ divide et impera”. L'analisi top down, quindi,
parte dai i requisiti che devono essere soddisfatti per risolvere il problema, che vengono poi divisi in un piccolo numero di sotto-requisiti.
L'analisi bottom up, al contrario, parte dai piccoli requisiti
che sono già completi, e studiandoli cerca il modo di
connetterli per svolgere compiti più complessi, fino a
risolvere l'intero problema. La strategia bottom up quindi è
quella in cui le parti individuali del sistema sono strutturate
in
dettaglio.
Queste
parti
vengono 9
Capitolo 2
successivamente connesse tra loro in modo da formare componenti più grandi, che vengono a loro volta collegati
fino a realizzare il sistema completo.
La svolta nel mondo della progettazione avviene con la
tecnica di progettazione ad oggetti, la quale consiste nell'analizzare il problema allo scopo di identificare tutti
gli elementi rilevanti che compaiono nella sua definizione. Questi componenti sono gli oggetti che identificano il
problema, che avranno tra di loro delle relazioni ben precise, che determineranno la struttura finale del programma.
Quindi riepilogando, nella fase di progettazione si definisce l’architettura
generale
sia
hardware
che
software
dell’intero sistema. Si descrivono le attività che il sistema
deve svolgere, ciascuna delle quali verrà trasformata in uno o più programmi eseguibili, chiamati moduli.
Al termine della fase di progettazione dovrebbe essere
prodotto il Documento di Specifiche di Progetto (DSP) nel
quale la definizione dell’architettura software può anche
10
Il ciclo di vita del software
essere data in termini tecnici e formali, usando opportuni linguaggi di specifica di progetto.
Per modellare un software si possono utilizzare due
strumenti più o meno formali, i diagrammi di flusso o lo pseudocodice. Lo pseudocodice consiste nel descrivere l'algoritmo in un linguaggio di programmazione "finto",
senza rigide regole di sintassi quindi con una maggiore flessibilità, con la possibilità di utilizzare delle espressioni
in linguaggio naturale, come se fosse un codice pieno di
commenti, laddove sia necessario specificare con chiarezza che cosa debba fare una riga di codice. I diagrammi di
flusso che studieremo nei capitoli successivi, servono per rappresentare graficamente i passaggi logici del sistema.
Rappresentano come il sistema interagisce con l’utente, coni sistemi preesistenti, con i componenti interni. Dimostrandone la struttura interna e i comportamenti.
La fase della modellazione migliora la chiarezza del
progetto, permette di non perdere di vista quello che è
l’obiettivo finale e concorre alla costruzione della documentazione.
11
Capitolo 2
Il programmatore ricevuta la documentazione può iniziare
a scrivere il software riga per riga. Solitamente si procede suddividendo il progetto in moduli indipendenti, come se
fossero piccoli programmi, con funzionalità proprie. Questa
suddivisione aiuta la fase di test, ottenendo così un applicativo più corretto e funzionale. I moduli creati, una volta testati vengono integrati dando vita all’applicativo
intero. Nella fase di coding il programma vero e proprio
viene scritto nel linguaggio di programmazione prescelto,
lo sviluppatore sarà portato a scegliere il linguaggio di programmazione che gli faciliterà il compito, cioè che gli
permetterà di raggiungere l’obiettivo con il minimo sforzo
e nel minor tempo possibile.
La fase di test procede in parallelo con la scrittura del
codice, perché abbiamo visto che il codice suddiviso in moduli viene testato sequenzialmente. I test sono eseguiti
per verificare la conformità del progetto e il rispetto delle specifiche iniziali. Il DSR , prodotto nella fase di analisi, ora
sarà molto utile per controllare nei dettagli che sia 12
Il ciclo di vita del software
correttamente possibile fare quanto richiesto inizialmente dal cliente.
L’ultima fase del ciclo di vita prevista dall’ingegneria del
software è la manutenzione. Questa è un parte molto lunga,
ricopre più della metà della vita dell’applicativo. All’inizio del
capitolo
sono
state
spiegate
le
tipologie
di
manutenzione : correttiva, perfettiva e adattativa. La manutenzione sarà sicuramente inevitabile. I requisiti di
un sistema software sono in continua evoluzione perché seguono i cambiamenti del dominio applicativo, delle tecnologie, delle necessità degli utenti. I sistemi software devono essere manutenuti affinché essi rimangano utili.
13
Capitolo 2
Le metodologie di sviluppo
Dopo aver studiato il ciclo di vita del software è necessario conoscere anche quali sono state le tipologie di
modellazione dei sistemi prima del Visual Modeling che sarà trattato in maniera approfondita nel prossimo capitolo.
Il metodo di sviluppo che ha avuto maggior successo è il
waterfall method , definito intorno agni anni ‘70 da 14
Il ciclo di vita del software
Winston Royce è un modello sequenziale lineare in cui c’è una successione di fasi sequenziali. Il processo di sviluppo,
segue dei precisi passaggi, ognuno di questi produce un output che è usato come input per dare inizio alla fase successiva. Ogni fase del processo viene documentata. Questo modello ricopre l’intero ciclo di vita del software,
dall’analisi dei requisiti fino alla manutenzione. Il modello a cascata ha numerosi vantaggi tra cui la semplicità, difatti
è quasi intuitivo e la netta e chiara divisione del lavoro, si adatta bene a logiche organizzative e politiche del
personale basate su una divisione del lavoro molto accentuata.
15
Capitolo 2
Test
Manutenz ione
Coding Progettaz ione Analisi
Sono però presenti anche degli svantaggi che hanno portato, con il tempo, a cercare metodologie alternative.
Precedentemente abbiamo detto quanto sia importante
l’interazione all’interno del team di lavoro e come la chiarezza del processo di sviluppo non possa mai mancare.
Con il modello a cascata le prime verifiche concrete, i primi
risultati visibili e comprensibili sia dal cliente che dagli
utenti, arrivano nella parte finale del progetto, al termine
della fase di test, quando è troppo tardi per fare delle modifiche senza dover stravolgere il lavoro svolto. Questi 16
Il ciclo di vita del software
svantaggi
portano
numerosi
problemi
tra
cui
la
convinzione che i requisiti non cambino una volta decisi
nelle fasi iniziali e che si raggiunga un accordo sulla carta
stimando tempi e costi del progetto, senza possedere la competenza
necessaria
implementativi.
sugli
aspetti
tecnici
ed
Il modello a cascata ha come svantaggio il fatto che se una
fase non produce l’effetto desiderato, bisogna tornare
indietro e fare tutto da capo, e ciò implica dei costi, per questo motivo facendo un software al cliente c’è il rischio di rimetterci tempi e costi facendo un lavoro doppio. Il modello a cascata è utile quando il progetto è semplice,
quando il cliente non cambia spesso idea (ed è molto serio) ed abbiamo un buon analista e un buon progettista.
Ecco perché è stato introdotto il modello a prototipo. Seguendo il modello a prototipo, per ogni fase, prima che sia sviluppata, viene creato precedentemente un prototipo. quindi è più costoso in termini di tempi, di costi e di risorse 17
Capitolo 2
umane, ma migliore. Questo prototipo viene mostrato al
cliente che conferma se si sta procedendo per la giusta strada. Questo modello però presenta uno svantaggio: il
prototipo viene buttato! E quindi in un mercato in cui c’è una concorrenza spietata, si cerca di risparmiare e quindi
realizzare un prototipo costoso potrebbe incidere in maniera negativa sui guadagni totali.
Progetto Raffinamento Ingegnerizzato Prototipo
Analisi
18
Bozza del Progetto
Il ciclo di vita del software
Per questo motivo sono stati introdotti dei modelli
iterativi che sono il modello incrementale e il modello
evolutivo. I modelli iterativi sono metodologie in cui
rientrano tutti i processi di sviluppo che permettono di ritornare ad una fase del processo di sviluppo già
terminata. Quindi si tratta di fasi iterabili in cui si parte con
una bozza di quella che probabilmente sarà la soluzione che verrà successivamente dettagliata nel prossimo
passaggio.
Il modello incrementale consiste in un miglioramento del
modello a cascata. Ad ogni fase successiva viene controllato tutto quello che è stato fatto nella fase precedente, quindi
si evita di andare avanti se non si è sicuri delle scelte prese. Tutto ciò ha un costo maggiore rispetto al modello a
cascata perché ovviamente ad ogni fase devo ricontrollare ciò che ho fatto per essere sicuro di poter andare avanti.
Questo modello è utilizzato per la progettazione di grandi software che richiedono tempi ristretti. Inoltre la
realizzazione è incrementale: ogni sequenza lineare
produce uno “step” operativo del software. In seguito alla
19
Capitolo 2
valutazione del cliente si stende un piano per lo step
successivo. Il modello iterativo risulta utile quando le
risorse umane sono insufficienti a garantire
l’implementazione completa del progetto. Possiamo
affermare che questa metodologia di sviluppo prevede il
completamento ed il perfezionamento delle fasi con
ripartenza da zero in modo da ottenere miglioramenti ad ogni passaggio.
Il modello evolutivo invece è un miglioramento del
modello a prototipo, il prototipo creato non viene buttato.
Nelle varie fasi viene creato un prototipo e raffinandolo
man mano che vado avanti nel processo di sviluppo, il
prototipo diventa il prodotto finale. Il modello evolutivo può essere interpretato in modi differenti ma che
comunque hanno in comune un ciclo di sviluppo in cui un prototipo iniziale si evolve gradualmente verso il prodotto
finale, seguendo sempre lo stesso schema, l’analisi, la
progettazione, l’implementazione e il test. Il principale vantaggio è il continuo confrontarsi con gli utenti e i clienti. 20
Il ciclo di vita del software
Possiamo affermare, quindi che questa metodologia evolutiva si basa sulla prototipizzazione. Questo processo consiste nell'uso di specifici strumenti software per la
realizzazione di una versione semplificata del sistema, con
la quale sarĂ possibile sperimentare e verificare le singole funzionalitĂ .
21
Capitolo 2
22
Il ciclo di vita del software
23
La programmazione Object Oriented
La programmazione orientata agli oggetti (Object Oriented Programming)
La programmazione orientata agli oggetti, Object Oriented Programming , è ormai la tipologia di programmazione piÚ
diffusa. Questo nuovo paradigma di programmazione ha stravolto il mondo della produzione dei software. Le tecniche di programmazione usate negli anni ’70, la
programmazione strutturata e procedurale, sono state sostanzialmente
sostituite
grazie
ai
numerosissimi
vantaggi che la programmazione object oriented offre. La programmazione ad oggetti nasce molti anni fa, i primi
linguaggi definiti "ad oggetti" furono il SIMULA I e il 1
Capitolo 3
SIMULA 67, sviluppati da Ole-Johan Dahl e Kristen Nygaard nei
primi
anni
'60.
Questi due linguaggi adottavano alcuni degli elementi su
cui oggi si basa la programmazione ad oggetti, come ad
esempio le classi e le sottoclassi ma ci volle ancora molto
tempo affinché questo nuovo modo di programmare avesse
successo.
Lo studio della nuova programmazione è proseguito, infatti
negli anni '70 fu introdotto il linguaggio SmallTalk, che è passato alla storia dell’informatica come il vero e proprio
linguaggio ad oggetti, il primo. Sempre in quegli anni nasce e riscuote grande successo anche il noto linguaggio C. Il linguaggio C deve la sua popolarità sì alla sua potenza ma
anche al successo che raggiungeva il sistema operativo Unix programmato proprio utilizzando questo linguaggio.
La vera svolta c’è stata con la nascita del C++ che pur basandosi sulla sintassi del linguaggio C rappresenta
comunque un linguaggio completamente indipendente e autonomo rispetto al C.
2
La programmazione Object Oriented
Il vantaggio della programmazione orientata agli oggetti sta
nell’essere
estremamente
potente
e
vicina
al
programmatore. Da la possibilità di creare software che sopravvivono alle continue evoluzioni del sistema. Si basa
su una metodologia di sviluppo di tipo top down, che parte da una conoscenza generale del problema, per scendere
più a fondo dettagliando la risoluzione del problema.
Ovviamente l’ elemento fondamentale è l’ oggetto che viene, in un primo momento definiti, descrivendone le caratteristiche attraverso la costruzione della classe, poi
creato e usato in relazione l'uno con l'altro. La struttura della programmazione object oriented si basa sulla
modellazione di entità astratte che sarebbero le classi le quali vengono realizzate attraverso oggetti di “forme”
diverse. Difatti il polimorfismo è uno dei concetti più
importanti della programmazione object oriented, insieme ad ereditarietà e incapsulamento.
E’ molto importante conoscere anche cosa c’è stato prima della
programmazione
OO,
citiamo
quindi
la
programmazione non strutturata, procedurale e modulare.
3
Capitolo 3
La programmazione non strutturata
Passando in rassegna le strutture che hanno preceduto la programmazione
Object
Oriented
partiamo
dalla
programmazione non strutturata. Questa tipologia porta ad avere un programma composto da un unico blocco di codice chiamato "main" all’interno del quale troviamo tutti
i dati gestiti in maniera sequenziale. I dati sono
rappresentati da variabili di tipo globale, quindi gestibili da
ogni parte del programma ed residenti in memoria per tutto il tempo che il programma rimane in esecuzione.
Programma main + dati
Si può facilmente capire che questo modo di programmare
è limitato e pieno di svantaggi, in quanto non c’è alcun controllo su parti di codice ridondanti o ripetute che non
4
La programmazione Object Oriented
faranno altro che rendere ingestibile e poco chiaro il codice
stesso, causando un spreco delle risorse del sistema. Come
dice la definizione stessa si ha un programma privo di struttura.
La programmazione procedurale
Ben presto la programmazione non strutturata è risultata molto poco funzionale, per questo è stata studiata la
programmazione procedurale, un notevole passo in avanti.
La programmazione strutturale organizza il codice
procedure. Le procedure sono porzioni di codice ripetute utilizzabili e richiamabili ogni volta che se ne ha il bisogno.
Possiamo pensare alle procedure sottoprogrammi che
svolgono una funzione, queste sono rese visibili e richiamabili dall’intero del codice. Secondo questo schema un programma è strutturato come una sequenza di istruzioni.
I
linguaggi
di
programmazione
procedurali sono : fortran, basic, cobol, pascal, C.
definiti 5
Capitolo 3
Procedura 1 main
Procedura 2 Procedura 3
Dati Uno
dei
maggiori
vantaggi
della
programmazione
procedurale deriva dal fatto che se una procedura è be
strutturata e quindi corretta allora si ha la sicurezza che potrĂ restituire solo risultati corretti. 6
La programmazione Object Oriented
La programmazione modulare
Il progresso dello studio dei migliori modi per costruire i sistemi
non
si
è
mai
arrestato,
infatti
dopo
la
programmazione procedurale è stato fatto un ulteriore
passo avanti con la programmazione modulare. I programmi diventavano sempre più complessi e quindi al programmatore riutilizzare
le
si
presentava
procedure.
Le
l'esigenza
procedure
di
dover
con
la
programmazione modulare venivano messe a disposizione da un programma in modo che anche altri programmi le potessero utilizzare. Per raggiungere questo obiettivo , le
procedure venivano raggruppate per dominio, per esempio quelle che eseguivano operazioni matematiche in moduli
separati. Sostanzialmente costituivano le librerie di programmi.
Con
la
programmazione
modulare
un
programma non è più costituito da un solo file in cui è presente tutto il main e tutte le procedure ma da diversi
7
Capitolo 3
moduli, uno per il main e tanti altri quanti sono i moduli a cui il programma fa riferimento.
Main + dati
MODULO 1
MODULO 2
Dati + Dati 1
Dati + Dati 2
Procedura
Procedura Procedura
8
La programmazione Object Oriented
La programmazione ad oggetti
La programmazione ad oggetti è un paradigma di programmazione che permette di creare e definire oggetti
in grado di interagire gli uni con gli altri attraverso lo scambio
di
messaggi.
Questi
oggetti
interagiscono
vicendevolmente. Ăˆ particolarmente adatta nei contesti in cui si possono definire delle relazioni di interdipendenza tra i concetti da modellare.
metodo
Dati
Dati
metodo
Dati
9
Capitolo 3
I
vantaggi
di
questa
particolare
programmazione sono tanti tra cui:
tipologia
di
• permette di modellare l’applicativo partendo da un •
ragionamento logico più solido;
permette di gestire e manutenere qualsiasi tipologia
di progetto sia di piccole che di grandi dimensioni;
• con l’uso delle classi il codice viene strutturato in modo da favorire la modularità e il riuso del codice.
La sua caratteristica più forte è la somiglianza con il comportamento umano ma può anche essere il suo più grande
svantaggio,
infatti
potrebbe
portare
il
programmatore a fare quegli errori che , anche in grammatica italiana si chiamano errori di semantica. Come per esempio “Il gatto vola nel prato”, questa frase è sintatticamente corretta ma non ha alcun significato. 10
La programmazione Object Oriented
Come nel mondo reale…se pensiamo ad una automobile si
conoscono le caratteristiche generali ma non tutti siamo a conoscenza dei sofisticati meccanismi che ne regolano il funzionamento.
Ci soffermeremo sulle caratteristiche : • Casa produttrice ;
• Numero di cavalli ; • Consumo medio ; • Velocità.
E sulle possibili azioni : • Accelera ; • Frena ;
• Consuma .
Un oggetto viene inteso come un insieme di caratteristiche,
alcune visibili e altre nascoste. Offre agli altri oggetti, come per esempio gli utenti , un insieme di operazioni da
11
Capitolo 3
svolgere , senza che sia necessario conoscere la sua organizzazione interna.
Un
oggetto possiede stato,
funzionamento e identità. L’insieme dei valori assunti dagli
attributi costituisce lo stato dell’oggetto. L’insieme delle operazioni definiscono il comportamento dell’oggetto.
La struttura e il funzionamento di oggetti con le stesse caratteristiche
sono
definite
nella
classe
a
cui
appartengono , di cui, possiamo dire che ne siano istanze. Infatti, quando viene creato un oggetto da una classe si dice
che è stata creata un’istanza della classe. I termini istanza e
oggetto sono intercambiabili.
Gli oggetti sono inizialmente delle entità passive, in un dato
momento nell’esecuzione di un programma un solo oggetto è attivo , quindi in esecuzione. Un oggetto diventa attivo
quando riceve l’invocazione di un metodo da parte di un altro oggetto. Mentre l’oggetto ricevente è attivo, il richiedente è passivo , cioè non in esecuzione, in attesa dei
risultati del metodo invocato. Una volta che i risultati sono
stati inviati al richiedente, l’oggetto ricevente torna in uno stato passivo ed il richiedente prosegue la sua esecuzione.
12
La programmazione Object Oriented
I metodi
Un metodo serve a costruire una azione che può essere compiuta da un oggetto. Quando si crea un oggetto ci si
deve chiedere: "Cosa deve fare questo oggetto?". Pensando
in questo modo ad un oggetto ci avviciniamo al concetto centrale della programmazione object oriented, il fatto che un elemento realizzi il concetto astratto espresso da una classe come se questo abbia realmente natura umana.
Le proprietĂ
13
Capitolo 3
Le proprietà rappresentano i dati dell'oggetto, ovvero le
informazioni su cui i metodi possono eseguire le loro
funzioni. Per non incorrere in errori e per non appesantire inutilmente il codice, le proprietà associate aglio oggetti
dovranno essere ben definite. Un oggetto dovrà contenere
le proprietà che, effettivamente, gli serviranno nel corso del programma e non tutte quelle che gli si potrebbero
comunque attribuire, altrimenti si avrebbe una lista infinita.
La classe
Una classe, come abbiamo già detto, descrive la struttura
interna e il funzionamento di un oggetto. La classe è un
contenitore di caratteristiche (attributi e metodi) che compongono la struttura formale attraverso la quale si
possono definire nuovi tipi di dati e le relazioni che
intercorrono con le altre classi. E’ appunto composta da
due elementi: gli attributi, o variabili di istanza, che 14
La programmazione Object Oriented
descrivono i dati della classe e i metodi che possono essere identificati come le azioni effettuate con e attraverso gli
attributi, secondo l’utilizzo di uno svariato numero di
istruzioni. Possiamo definire la classe come un modello
astratto tramite il quale vengono definite delle proprietà comuni ad una vasta categoria di oggetti. L'oggetto è la realizzazione del contenuto della classe da cui deriva. Si
inizializzano gli attributi e si carica il codice dei suoi metodi
in
accordo
al
modello
della
classe
di
appartenenza. Gli oggetti che appartengono ad una stessa classe hanno comuni proprietà, essi fanno una copia per se
stessi delle variabili che possono essere gestite in maniera
differente da ogni singolo oggetto. Quindi tra di loro sono indipendenti e distinti! Per esempio se si considerasse la class persona su ogni oggetto persona si potrebbe distinguere il colore dei capelli, l'altezza o il peso.
Data una classe è possibile costruire una nuova classe che sia un'estensione della prima senza dover ridefinire gli
attributi e i metodi già implementati nella prima attraverso il meccanismo della ereditarietà. Questa nuova classe è 15
Capitolo 3
chiamata "classe derivata" o "sottoclasse" della classe da
cui eredita le sue caratteristiche. In pratica la sottoclasse ha i propri metodi e i propri attributi ed in più eredita quelli della sua superclasse e ciò avviene per tutte le
ulteriori sottoclassi. Per fare un esempio, si può immaginare di rappresentare con una classe il concetto di
cane ed avere come sottoclasse il concetto di cane labrador. Questa tecnica è rappresentativa di come la
programmazione orientata agli oggetti si sviluppa in maniera top down dal concetto più generico al più
specifico. E’ anche possibile limitare l'accesso alle variabili
e metodi mediante l'utilizzo degli specificatori d’accesso
pubblico (public) –privato (private) –protetto(protected) – package.
16
Unified Modeling Language
UML (Unfied Modeling Language)
L’UML è un linguaggio “universale per la progettazione dei sistemi, nel senso che può esprimere modelli di
varie tipologie di sistemi creati per obiettivi diversi,
proprio come fa un linguaggio di programmazione o un linguaggio naturale possono essere usati in modi diversi”.
1
Capitolo 4
Nel mondo costantemente in fermento ed in evoluzione quale è quello dello sviluppo di applicazioni Object
Oriented, diviene sempre più difficile costruire e gestire
applicazioni di alta qualità in tempi brevi. Le esigenze
del mercato sono in continuo cambiamento soprattutto per quanto riguarda anche la complessità dei software da
proporre
agli
utenti.
Questo
panorama
di
molteplicità delle applicazioni porta all’esigenza di avere un linguaggio universale per modellare gli oggetti
che potesse essere utilizzato da ogni industria produttrice di software. Per questo fu inventato il linguaggio UML, la cui sigla sta per Unified Modeling
Language. In termini pratici, potremmo dire che il linguaggio UML è come la rappresentazione grafica di
un progetto di ingegneria edile, riportato nel mondo dell'Information Technology. L'UML
è,
dunque,
un
metodo
per
descrivere
l'architettura di un sistema in dettaglio. Come è facile intuire, con la progettazione grafica sarà molto più facile
2
costruire o controllare un sistema e assicurarsi che il
Unified Modeling Language
sistema stesso si presterà senza troppe difficoltà a futuri
cambiamenti delle funzionalità. La potenza dell' Unified Modeling Language consiste nel fatto che il processo di
disegno del sistema può essere effettuata in modo tale
che i clienti, gli analisti, i programmatori e chiunque altro sia coinvolto nel sistema di sviluppo possa capire ed esaminare in modo efficiente il sistema e prendere parte alla sua costruzione in modo attivo.
Caliamoci in uno scenario : dovendo acquistare una casa nessuno di noi la acquisterebbe con serenità avendo semplicemente un elenco delle sue caratteristiche.
Tutti desidereremmo studiare a tavolino, magari con l'ausilio dell'architetto esperto, tutti i dettagli possibili
servendoci, a tale scopo, della mappa della casa, del suo
progetto più o meno dettagliato. In un modo molto simile possiamo immaginare che funzioni la "macchina" dello sviluppo di sistemi software. Invece di leggere la piantina della casa, in questo caso ci si servirà dei
diagrammi UML che non fanno altro che documentare 3
Capitolo 4
in modo esauriente (se costruiti bene) e a tutti i livelli le
caratteristiche
tecniche
che
implementate dal sistema completo.
dovranno
essere
La storia dell’UML
Come abbiamo già detto all’inizio del capitolo il
progresso informatico è in continua evoluzione. Durante
gli anni '90, in particolar modo, c’è stato il boom della diffusione dei PC e quindi il conseguente aumento di
richiesta di software. Sempre in quegli anni furono introdotte nel mercato dell'Information Technology
parecchie metodologie per il disegno e la progettazione di sistemi software, per migliorarne la semplicità di
sviluppo. Essendo in fase di sviluppo, però, ognuna di
queste tecnologie aveva il suo insieme proprio di
notazioni e simboli, che differiva, a volta in modo 4
Unified Modeling Language
rilevante, dalle altre. Quindi si rischiava di ottenere il
risultato opposto a quello desiderato.
In particolare, eccellevano tre di queste metodologie:
• OMT (Rumbaugh) • Booch 1991
• OOSE (Jacobson)
Ognuno di questi metodi aveva, naturalmente, i suoi
punti di forza e i suoi punti deboli. Ad esempio, l'OMT si rivelava ottimo in analisi e debole nel disegno, è facile
capire che un progetto non ben disegnato non fa un sistema stabile. Booch 1991, al contrario, eccelleva nel
disegno e peccava in analisi, anche in questo caso
possiamo facilmente dire che un sistema non può essere ben disegnato se non ne è stata fatta una buona analisi.
Jacobson aveva il suo punto di forza nell'analisi dei requisiti e del comportamento di un sistema ma si
rivelava debole in altre aree. Successivamente, questi
tre studiosi hanno iniziato a pensare ad un linguaggio di 5
Capitolo 4
progettazione che fosse universale e che doveva
esprimere modelli di varie tipologie e che potesse essere utilizzato per tutti gli obiettivi possibili e in vari ambiti (non solo in quello informatico!). Infatti Booch scrisse il suo secondo libro che si basava sui principi di
analisi utilizzati da Rumbaugh e Jacobson. A sua volta, Rumbaugh pubblicò una serie di articoli, conosciuti
come la nuova versione della sua metodologia l’OMT-2,
che descrivevano parecchie delle tecnologie di disegno di Booch. Possiamo affermare che, i tre metodi stavano convergendo verso un'unica visione che incorporasse i
vantaggi di ognuna delle tre maggiori tecnologie. L'unico problema stava nella notazione grafica che ogni metodo
portava
ancora
con
sĂŠ.
Un
problema
assolutamente da sottovalutare in quanto l'uso di
simbologia differente portava ad una confusione sul
mercato a causa del fatto che un determinato simbolo poteva avere un significato differente per analisti e disegnatori differenti. Dopo un periodo di tempo in cui
passato alla storia come guerra della metodologia, 6
"method war", nell'Ottobre del 1995, nacque la prima
Unified Modeling Language
bozza dell'UML, ovvero l'unificazione delle notazioni e
delle idee prodotte da Booch, Rumbaugh e Jacobson per modellare un sistema software. La prima versione ufficiale,
prodotta
dall'OMG
(Object
Management
Group) fu rilasciata nel Luglio del 1997 e nel Novembre dello stesso anno l'UML venne adottato come standard.
I vantaggi
L’introduzione dello standard dell’UML ha portato numerosi benefici alcuni di questi elencati qui di seguito:
1. L’UML permette di disegnare professionalmente e
documentare un sistema ancor prima che ne venga scritto il relativo codice da parte degli sviluppatori. Si
sarà così in grado di conoscere in anticipo il risultato finale del progetto su cui si sta lavorando.
7
Capitolo 4
2. Il software essendo, come detto, progettato nella fase
di disegno prima della stesura del codice, ne consegue
che la scrittura del codice stessa è resa più agevole ed efficiente oltre al fatto che in tal modo è più facile
scrivere del codice che sarà riutilizzabile in futuro. I
costi di sviluppo, dunque, si abbassano notevolmente con l'utilizzo del linguaggio UML.
3. Altro vantaggio importantissimo che deriva dalla progettazione
è
una
più
facile
previsione
ed
anticipazione di eventuali bugs nel sistema. Il software che si scrive, si comporterà esattamente come ci si aspetta senza spiacevoli sorprese finali.
4. L'utilizzo dei diagrammi UML permette di aumentare
il livello di chiarezza per chiunque sia coinvolto nello
sviluppo, di tutto l'insieme che costituisce il sistema. In questo modo, si potranno sfruttare al meglio anche le risorse hardware in termini di memoria ed efficienza,
senza sprechi inutili o, al contrario, rischi di sottostima 8
dei requisiti di sistema.
Unified Modeling Language
5. Grazie alla documentazione del linguaggio UML
diventa ancora più facile effettuare eventuali modifiche future al codice nella fase della manutenzione del software. Questo, ancora, a tutto beneficio dei costi di mantenimento del sistema.
Grazie alla progettazione grafica voluta dall’UML si ottiene una migliore comunicazione e interazione tra i
componenti del team di lavoro. Un team eterogeneo che può parlare la stessa "lingua", un gruppo che avendo chiari gli obiettivi da raggiungere evita rischi di incomprensioni e quindi sprechi di tempo.
VISUAL MODELING
Si è già accennato nell'introduzione circa la similitudine
tra i diagrammi UML e il progetto grafico edilizio, è però necessario capire bene ed approfondire il concetto
di "Visual Modeling" prima di andare avanti. Abbiamo 9
Capitolo 4
più volte utilizzato, precedentemente, il termine
sistema. Ma, prima di tutto, cosa è esattamente un sistema? Un Sistema è una combinazione di componenti
software e hardware che fornisce una soluzione ad
un'esigenza del mondo reale, è ciò che si utilizza per
realizzare le richieste di un cliente. Per far sì che un sistema sia efficiente, sarà necessario recepire bene le
richieste del cliente e fare in modo che esse vengano condivise e recepite come requisiti indispensabili dall'intero team di lavoro. Successivamente, si useranno
tali requisiti per generare il codice necessario alla costruzione del sistema assicurandosi, andando avanti
nella scrittura del codice, che non si perda mai di vista l'obiettivo finale. Tale procedimento prende il nome di
"Modeling" , la modellazione. L’ormai passato metodo di
modeling dei sistemi, conosciuto anche come metodo a Cascata. Il Waterfall Method, come abbiamo visto si
basava sul principio che i vari passi che costituivano il
processo di sviluppo di un sistema fossero sequenziali, l'inizio di una fase avveniva soltanto dopo il termine di 10
un’ altra. Quello che accadeva seguendo il Waterfall
Unified Modeling Language
method era che le persone del team si trovavano a
lavorare su task differenti e spesso, non avevano alcun modo di comunicare, data la netta suddivisione dei
compiti, a discapito di importanti questioni inerenti il Sistema stesso. Un altro punto debole del Waterfall method era il fatto che esso prevedeva che si
concedesse la maggior parte del tempo alla scrittura del
codice, togliendolo, in tal modo, all'analisi ed al disegno
che invece rappresentano la base per un solido progetto. Il Visual Modeling, che ha sostituito ormai il
Waterfall method, non è altro che l’aggiunta della visualizzazione grafica di un modello, utilizzando un insieme ben definito di elementi grafici, che nel linguaggio UML sono rappresentati dai 9 Diagrammi di base.
11
Capitolo 4
Il team di lavoro
Il processo per la progettazione e costruzione di un
sistema coinvolge molte persone e figure professionali.
Prima di tutto il cliente, ovvero colui che ci pone un “problema” da risolvere. La persona che desidera avere una risoluzione ad un problema o ad una sua esigenza.
Un analista ha il compito di documentare il problema del cliente e trasmettere le informazioni raccolte agli
sviluppatori, i quali costituiscono la parte del team che si occupa di implementare il codice, testarlo con il
supporto di un team apposito per il test e installarlo sull'hardware dei computer.
La suddivisione dei
compiti è strettamente necessaria poiché al giorno d'oggi i sistemi sono diventati davvero molto complessi
ed è richiesta una preparazione molto più specializzata
da rendere la costruzione di un sistema non gestibile
12
soltanto da una persona.
Unified Modeling Language
Il Visual Modeling prevede una continua interazione tra
le risorse umane del team di lavoro. Analisti e disegnatori, per esempio, devono di un continuo
scambiarsi opinioni per permettere che gli sviluppatori
possano basarsi su una più solida informazione. Anche i programmatori devono interagire con gli analisti ed i disegnatori
per
condividere
le
loro
intuizioni,
modificare i disegni e consolidare il codice. Il vantaggio di tale tipo di approccio è che la conoscenza generale del team cresce notevolmente, ognuno è in grado di
capire a fondo il sistema e il risultato finale non può che essere un sistema più solido.
Il RAD
Quando si inizia un progetto per lo sviluppo di un sistema non ci si può basare sull'improvvisazione ma si
deve seguire un iter sia tecnico che logico che consenta 13
Capitolo 4
di arrivare in tempi brevi alla soluzione migliore. Il RAD rappresenta proprio una sorta di guida dei vari passaggi
del progetto. Il RAD, Rapid Application Development , è
lo sviluppo rapido di applicazioni che consiste, fondamentalmente, in cinque fasi:
1. Raccolta delle informazioni 2. Analisi
3. Disegno
4. Sviluppo
5. Deployment
14
Unified Modeling Language
La raccolta delle informazioni
Questa è la fase iniziale in cui si inizia a conoscere il
sistema da sviluppare. Questa sezione consiste, a sua
volta, di parecchie azioni. Esaminandole bisogna tener presente sempre che è di fondamentale importanza
capire bene i requisiti che il cliente richiede. Soltanto così si otterranno dei sistemi stabili e robusti.
La raccolta delle informazioni inizia quindi con la scoperta del business. In questa fase, gli analisti
intervistano il cliente chiedendogli di analizzare a fondo i processi rilevanti, passo per passo. Di solito, al termine
di tale attività vengono abbozzati uno o più activity diagrams, i diagrammi di flusso per eccellenza.
Segue una più specifica analisi del dominio. Durante
questa fase sempre l'analista cerca di capire bene le entità principali del dominio del cliente. Si cerca di 15
Capitolo 4
sottolineare con maggiore importanza le entità chiave
del dominio che vengono indicate come possibili Classi.
Al termine di questo processo si produce un Class Diagram , diagramma molto importante per capire la struttura di un codice Java.
Successivamente un ingegnere di sistema identifica la possibile
correlazione
tra
sistemi,
identifica
esattamente le dipendenze tra i vari sistemi, ovvero da
quali eventuali sistemi esistenti il nuovo sistema dovrà dipendere
e,
viceversa,
quali
sistemi
dovranno
dipendere dal nuovo. Il responsabile di tale fase è
dunque un System Engineer che al termine di tale
sezione produrrà un Deployment Diagram.
La fase dell’identificazione dei requisiti di sistema è la
prima sessione di sviluppo misto , la Joint Application Development - JAD. Infatti, ad essa partecipano i
responsabili dell'organizzazione del cliente, i potenziali utenti ( gli stakeholder)
16
e i membri del team di
sviluppo. Con le maggiori informazioni ottenute alcuni
Unified Modeling Language
membri del team dovrebbero prendere appunti e
rifinire il class diagram disegnato nella precedente fase
di analisi del dominio. A questo punto il team può presentare i risultati al cliente, di questo si occupa il Project Manager.
Analisi
Durante la fase di analisi i componenti del team sono pronti
per
dettagliare
i
risultati
ottenuti
nella
precedente raccolta delle informazioni e approfondire, quindi, la conoscenza del problema.
Possiamo
sicuramente affermare che alcune delle azioni di analisi
sono giĂ iniziate, in realtĂ , proprio nella fase di raccolta delle informazioni quando viene creata una bozza del
class
diagram.
Dopo
aver
capito,
anche
se
superficialmente come sarĂ costituito il sistema, il team inizia la fase di comprensione dell'utilizzo del sistema, 17
Capitolo 4
durante la quale con i potenziali utenti, gli analisti
lavorando per definire gli actors che interagiranno con ogni singolo use case, caso d'uso. E’ questo, appunto, il momento in cui vengono prodotti gli use case diagrams.
Si studia quindi come il nostro sistema potrà essere utilizzato da utenti o sistemi esterni.
Ottenuti i risultati vengono ridefiniti i class diagrams. La figura incaricata di modellare gli oggetti, dovrà porre la massima attenzione alle discussioni e alle richieste dei
clienti al fine di rifinire i vari diagrammi delle classi. Tale operazione può consistere nel dare dei nomi
appropriati alle classi, alle associazioni, alle classi
astratte, alle generalizzazioni e alle aggregazioni. Questo il momento in cui il class diagram diviene più dettagliato. Se sarà necessario dovranno essere
analizzati i cambi di stato negli oggetti. Vengono prodotti in questa fase gli state diagrams. Questi oggetti, per dar vita al sistema dovranno interagire tra di loro.
Vengono prodotti i sequence diagrams e i collaboration 18
diagrams, che rispettivamente dimostrano come gli
Unified Modeling Language
oggetti comunicano nel tempo e nello spazio. Fino ad ora il team di lavoro è arrivato a costruire un insieme di
use cases e di class diagrams più o meno rifiniti e
ultimati. Il system engineer, procede in parallelo con
tutte le fasi descritte sin qui, analizzando l'integrazione del sistema da sviluppare con gli altri sistemi
preesistenti o comunque sistemi con i quali sarà necessario
cooperare.
L’ingegnere
di
sistema
si
domanderà quale tipo di comunicazione viene utilizzata tra i sistemi? Come è fatta la topologia della rete? Il
sistema dovrà interagire con un database? Se si, con quali tipi di database? Durante tale fase verranno costruiti i deployment diagrams.
Disegno
Terminata la fase di analisi il team lavora in base ai risultati ottenuti per disegnare la soluzione al 19
Capitolo 4
“problema” posto dal cliente. Ora la parte del team che
si occupa del disegno e quella che si è occupata dell'analisi necessitano di aggiornarsi vicendevolmente
fin quando non si reputa che la fase di disegno sia completata, finché non si raggiunge una visione grafica completa del sistema da sviluppare.
I programmatori leggendo il class diagram generano
tutti gli object diagrams che saranno necessari anche in base all'esame di ogni operazione. Parallelamente si
sviluppa un corrispondente activity diagram che costituirà la base per la maggior parte della scrittura del codice nella fase di sviluppo.
A questo punto dovranno essere studiati tutti i
componenti che costituiranno il sistema e mostrare le dipendenze tra loro. Durante tale fase i programmatori
hanno un ruolo molto importante. In questa fase che vengono prodotti i component diagrams.
Dopo aver costruito il component diagram ,sempre, il
20
system engineer pianifica il deployment dell’intero
Unified Modeling Language
sistema per gestire le varie interazioni con altri sistemi. Questi diagrammi cosĂŹ creati mostreranno dove i componenti risiederanno. Quando
deve
essere
disegnato
un
prototipo
dell'interfaccia utente è richiesta un'altra sessione di
interazione con gli utenti. Tale fase costituisce un punto
intermedio tra analisi e disegno. Un analista GUI
(Graphical User Interface) lavora con gli utenti per sviluppare dei prototipi. In questa fase vengono
prodotto delle stampe dei prototipi delle possibili interfacce utente.
I test che saranno effettuati sul sistema non potranno
essere improvvisati, infatti vengono disegnati. Per
sviluppare questa fase è consigliabile avvalersi di una
figura esterna al team di sviluppo che sia sviluppatore o di un tester esperto. Questa figura utilizzerĂ gli uses
cases diagrams per sviluppare dei test cases che solitamente sono automatizzati.
21
Capitolo 4
Tutti i diagrammi disegnati e le informazioni ottenute vengono
organizzate
dagli
specialisti
della
documentazione che lavorando con i disegnatori del
sistema iniziano a costruire la documentazione ed
arrivare man mano ad una struttura di alto livello per ogni documento. Ad ottenere quindi la versione finale di
ogni diagramma. Viene prodotta in questa fase una bozza di documentazione.
Sviluppo
Le fasi che precedono la stesura del codice devono
essere fatte con molta accuratezza se si vuole ottenere sia un buon risultato sia se si vuole rendere il lavoro piĂš
semplice ai programmatori. Infatti solo se si è
proceduto seguendo le regole la fase dello sviluppo non potrĂ che scorrere velocemente e senza grossi problemi. 22
Unified Modeling Language
Si passa quindi all’implementazione del codice. Con i
class diagrams, gli object diagrams, gli activity diagrams e
i
component
diagrams
a
disposizione,
i
programmatori implementano il codice per il sistema, riga per riga, classe per classe.
Man mano che il codice prende forma dovrà essere
controllato con una opportuna fase di test. Si può, praticamente, dire che le due fasi camminano di pari passo fin quando il codice supera tutti i livelli di test sviluppati nella fase di disegno.
L’interfaccia utente viene scelta tra i vari prototipi proposti , costruita e collegata al codice. Questa fase si
può considerare come un legame tra Disegno e Sviluppo. Ovviamente anche la fase della creazione e del
collegamento necessita di una opportuna fase di test e
verifica. Al termine di questi passaggi si dovrebbe avere
a disposizione il sistema funzionante completo con l'interfaccia utente.
23
Capitolo 4
La documentazione precedentemente abbozzata potrà,
a questo punto, essere completata. Avendo scritto tutto il codice completo di interfaccia funzionante, gli esperti della documentazione potranno terminare la stesura di tutto il materiale necessario per descrivere il sistema.
Deployment
La fase che abbiamo studiato fin ora si chiama fase di
sviluppo, development. Una volta completata, il sistema viene installato sull'hardware appropriato ed integrato
con i sistemi con cui deve interagire. Questa è la fase che prende il nome di deployment.
La fase di deployment inizia con la creazione di un piano per il backup ed il recupero delle informazioni. Il
System Engineer avrà il compito di creare un piano che
descriverà i passi da seguire nel caso in cui il sistema 24
Unified Modeling Language
dovesse andare in crash, costruirà il piano di crash recovery.
Ora che il sistema è completo al 100% il team di
sviluppo dovrà chiedersi se il sistema si comporta come
ci si aspettava e se il piano di backup e di recovery
funzionano. A seconda del responso di queste domande
si deciderà se è opportuno ricontrollare e raffinare ulteriormente il sistema oppure festeggiare il lavoro terminato con successo.
UML : cosa ci serve?
Abbiamo detto all’inizio che l’UML non è un linguaggio di programmazione e quindi non ha delle regole rigide,
ma il rispetto dei passaggi permetterà di lavorare con maggiore consapevolezza e fluidità. Difatti, niente e nessuno obbliga il team di lavoro a seguirne ogni
singolo passaggio. Allo stesso tempo l’UML come ogni 25
Capitolo 4
linguaggio ( di modellazione )che si rispetti necessita
dell'utilizzo di strumenti appropriati che ne agevolino l'utilizzo. Come vi verrà suggerito durante le lezioni l'utilizzo di carta e penna è sicuramente utile quando ci
si avvicina per la prima volta al disegno di un diagramma, però non potrà essere abbastanza.
Facendo una ricerca su internet vi accorgerete che i
tools per la costruzione dei diagrammi sono tantissimi. Probabilmente, però, il più diffuso ed anche il più
preciso è lo strumento della Rational, Il Rose, prodotto
dalla IBM. Il Rational Rose è stato disegnato per fornire ai team di sviluppo tutti gli strumenti necessari per il modellamento
di
soluzioni
efficienti. http://www.rational.com.
robuste
ed
Anche la Microsoft ha prodotto uno proprio strumento
che permette di definire i diagrammi: il Microsoft Visual
Modeler. Diciamo che tale software può essere consigliato a chi si avvicina per la prima volta al mondo 26
del Visual Modeling. Lo strumento che noi utilizzeremo
Unified Modeling Language
si chiama ArgoUml che è un prodotto open source ed è scritto in Java.
http://argouml.tigris.org/
Vi presento di seguito la sua interfaccia.
Il programma permette di creare i diagrammi che
verranno studiati durante il corso. La sua interfaccia è dotata di un’ ampia area di lavoro centrale :
della consueta barra dei menù e di una barra degli strumenti personalizzata per ogni diagramma :
27
Capitolo 4
I componenti dell’UML
Il linguaggio UML, come scritto in precedenza, è composto da un set di componenti grafici utilizzati diversamente a seconda del diagramma da creare.
Poiché l'UML è un linguaggio, esso utilizza delle regole
per mettere insieme i componenti del linguaggio nella
creazione dei diagrammi. L'obiettivo dei diagrammi è
quello di costruire molteplici viste di un sistema tutte correlate tra di loro, in modo da avere chiari tutti gli
aspetti del sistema. L'insieme di tali viste costituirà quello che abbiamo definito Visual Modeling.
Vediamo adesso, brevemente, tutti i diagrammi UML, che verranno analizzati più dettagliatamente nei
28
prossimi capitoli.
Unified Modeling Language
Lo standard del linguaggio UML prevede nove diagrammi di base, ma è pur sempre possibile costruire e aggiungere dei diagrammi differenti dagli standard, che vengono chiamati ibridi rispetto a quelli definiti dal linguaggio.
Class Diagram, sono diagrammi strutturali, sono fondamentali per introdurre la struttura del codice e la suddivisione delle classi. Una classe è un insieme di
caratteristiche, attributi e metodi, che verranno utilizzati dagli oggetti. Per esempio: computers, automobili, piante, animali, sono potrebbero costituire delle classi.
categorie che
I class diagrams forniscono le rappresentazioni che verranno utilizzate dagli sviluppatori.
Negli object diagram vengono definiti gli oggetti. Un oggetto è una istanza di una classe, cioè un elemento specifico che ha dei valori determinati per i suoi attributi e dei comportamenti specifici.
29
Capitolo 4
Con gli use case diagram sono definiti i casi d’uso che
sono una descrizione di un comportamento particolare di un sistema dal punto di vista dell'utente. Fanno parte della categoria dei diagrammi comportamentali. Per gli
sviluppatori, gli use case diagram rappresentano uno
strumento molto utile. Infatti tramite tali diagrammi,
essi possono agevolmente ottenere una idea chiara dei
requisiti del sistema dal punto di vista utente e quindi
scrivere il codice con sicurezza, senza dubbi sullo scopo finale del sistema. Nella rappresentazione grafica, viene
utilizzato un simbolo particolare per l'actor (l'utente o un altro sistema che interagisce) presente solo nel
diagramma dei casi d’uso che vedremo in seguito.
Con gli state diagram viene rappresentato lo stato che in un determinato istante, durante il funzionamento del
sistema, un oggetto soddisfa. Gli state diagrams visualizzano tali stati ed i loro cambiamenti nel tempo. Ogni state diagram inizia con un simbolo che identifica
lo stato iniziale (Start State) e termina con un altro 30
simbolo che rappresenta lo stato finale (End State).
Unified Modeling Language
Rappresentano quindi il ciclo di vita logico- temporale
che un oggetto sviluppa.
In un sistema funzionante, gli oggetti interagiscono l'uno con l'altro, e queste interazioni avvengono in relazione al trascorrere del tempo. Il sequence diagram
mostra le dinamiche, basate sul tempo, delle varie comunicazioni tra gli oggetti.
L’ activity diagram dimostra le attività che si riscontrano all'interno di use case o all'interno del comportamento
di
un
oggetto
che
tipicamente, in una sequenza ben definita.
accadono,
Con i collaboration diagram possiamo rappresentare
come gli elementi di un sistema lavorano insieme per
realizzare e soddisfare le necessitĂ del sistema. Un
linguaggio di modellazione deve avere un modo per rappresentare tale cooperazione.
Oggi, nell'ingegneria del software viene sempre piĂš
utilizzato il modello di organizzazione secondo il quale 31
Capitolo 4
ognuno nel team di lavoro si occupa di lavorare su un
componente differente. Il component diagram descrive come il team costruisce i componenti.
Il deployment diagram mostra l'architettura dal punto di vista fisico e logistico di un sistema. Tale diagramma può descrivere i computer e i vari dispositivi presenti,
mostrare le varie connessioni che intercorrono tra di essi e, ancora, il software che è installato su ogni macchina.
I disegnatori e gli analisti avranno la necessità sviluppare tutti e nove i diagrammi che l'UML mette a disposizione? E’ impossibile rispondere a priori a questa domanda, in quanto la risposta varia in relazione alla complessità del
sistema che si intende costruire ma, come detto in precedenza, per avere un sistema più solido ed un programmatore più preparato la risposta non può che essere affermativa. Inoltre, come si è già detto, i
diagrammi UML sono stati pensati per venire incontro
32
all'esigenza di rendere il sistema comprensibile da
Unified Modeling Language
differenti persone con differenti figure professionali che
costituiranno l’intero team di sviluppo compresi il cliente e gli utenti. Ci sono nello standard alcuni
diagrammi che saranno sicuramente più tecnici, ad
esempio, il class diagram o il deployment diagram, ma
un manager o il cliente stesso potrà, certamente, avere le idee più chiare analizzando uno use case diagram.
I diagrammi sono un potente mezzo per gestire la complessità,
per
visualizzare,
strutturare
e
documentare anche i sistemi più difficili mantenendo un
alto livello di chiarezza per tutti coloro che sono partecipi al processo di costruzione.
33
Capitolo 4
34
I diagrammi : Use Case Diagram
Il concetto dei diagrammi
I diagrammi servono per sviluppare un processo logico
attraverso degli elementi grafici. Questi rappresentano con
simboli e relazioni il susseguirsi dei passaggi che intercorrono tra i componenti di un sistema.
Diagrammi diversi sono usati per scopi diversi secondo il punto di vista dal quale si analizza il sistema. Le differenti
1
Capitolo 5
viste sono chiamate “viste architetturali”, esse facilitano l’organizzazione della conoscenza del problema nel team di sviluppo
mentre
comunicazione.
i
diagrammi
ne
permettono
la
Use case diagram (diagrammi dei casi d’uso)
Il diagramma dei casi d’uso è una tipologia di diagramma comportamentale, descrive la modalità di utilizzo del
sistema da parte di persone o altri sistemi (actors) e 2
I diagrammi : Use Case Diagram
interazioni tra sistema e attori, quindi come il mio applicativo risponde all’intervento esterno.
L'actor è l'elemento esterno che interagisce con uno use
case dando inizio ad una sequenza di azioni descritte dallo
use case stesso e, se necessario riceve delle precise risposte dal sistema. Può essere una persona fisica o anche
un altro sistema.
Deduciamo , quindi, che la sequenza di eventi descritta da
un caso d’uso viene iniziata dall’actor, per esempio da un pezzo di hardware o ancora dal passare del tempo. Al
termine della sequenza l’actor deve ottenerne un risultato
utile, vale a dire che le funzioni del mio sistema devono concretizzare un bisogno generato esternamente.
Un actor viene collegato ad uno o a più casi d’uso mediante una relazione chiamata “associazione” rappresentata
attraverso una semplice linea retta, che parte dall’actor e
arriva allo use case in oggetto.
3
Capitolo 5
C’è una distinzione, non figurativa ma concettuale, tra gli actor di un diagramma.
Distinguiamo actor primario e actor secondario. L’actor
primario è colui che utilizza direttamente lo use case a cui
si collega, è l’entità per cui il caso d’uso viene creato ne è il
diretto beneficiario. L’actor secondario è colui che rende
possibile il caso d’uso, senza il quale l’actor primario non potrebbe usufruirne. Nei diagrammi non c’è alcuna distinzione grafica tra i due actors perché questa differenza
è da notare in base al singolo caso d’uso a cui gli actors vengono collegati.
4
I diagrammi : Use Case Diagram
Ogni use case è una lista di scenari, ed ogni scenario è una sequenza di passi.
Ogni use case, genererà uno scenario che potrà essere
rappresentato con un actor che dà inizio alla sequenza , le pre-condizioni per lo use case, i passaggi dello scenario vero e proprio, le post-condizioni dello use case a scenario compiuto e l'actor che beneficia dell'use case in oggetto.
In ogni diagramma gli elementi vengono messi in relazione. Tra i vari use cases sono possibili le seguenti relazioni : •
• •
Inclusion;
Extension;
Generalization/Specialization.
Prendiamole in esame…
5
Capitolo 5
INCLUSION
L'inclusione permette il riutilizzo di un use case all'interno di un altro use case.
Lâ&#x20AC;&#x2122; <<include>> rappresenta una azione obbligatoria, nel
senso che deve essere necessariamente svolta per completare
lo
use
case
originario,
che
resterebbe incompleto e quindi non realizzabile.
altrimenti
Per rappresentare graficamente una relazione di inclusion,
si utilizza una linea tratteggiata con una freccia al suo
termine. Sulla freccia è inserito uno stereotipo, la parola <<include>> racchiusa, come si vede nellâ&#x20AC;&#x2122;esempio, tra
doppie parentesi formate dai simboli "<<" e ">>".
6
I diagrammi : Use Case Diagram
EXTENSION
L‘estensione permette di creare un nuovo use case aggiungendo dei passi in più ad un use case già esistente.
L’ <<extend>> rappresenta una azione non obbligatoria , un
completamento accessorio ad uno use case che può regolarmente aver luogo anche senza l’esecuzione della relazione di extension.
Come nel caso dell’inclusion, usiamo per rappresentare
l'extension una linea tratteggiata con una freccia finale.
Sulla freccia è inserito uno stereotipo che mostra la parola <<extend>> tra parentesi.
7
Capitolo 5
GENERALIZATION/SPECIALIZATION
Questa relazione è molto importante per capire un concetto alla base della programmazione Object Oriented , l’ereditarietà. Come vedremo, le classi possono ereditare le
caratteristiche di un'altra classe (superclasse). Allo stesso modo, tale ragionamento è applicabile agli use case. Possiamo
notare
che
i
due
termini
che
la
contraddistinguono questo tipo di relazione sono antitetici,
quindi sono uno il contrario dell’altro. In effetti questa relazione può essere letta in due versi : dal caso d’uso generico che si specializza in due o più casi d’uso differenti, dai casi d’uso specifici che si generalizzano in un caso d’uso generico.
Spiega che un caso d’uso può svolgersi in modi diversi.
Nell’ ereditarietà tra i casi d’uso, lo use case figlio assume il
comportamento ed il significato dal caso d’uso padre ed in più aggiunge le proprie caratteristiche specifiche.
8
I diagrammi : Use Case Diagram
La relazione di generalizzazione o specializzazione può intercorrere anche tra actors.
9
Capitolo 5
IL RAGGRUPPAMENTO
Tenendo presente che l’obiettivo della costruzione dei
diagrammi è aumentare la conoscenza del problema e
soprattutto la chiarezza, quando necessario, in alcuni Use Case, bisogna organizzare il diagramma in un modo diverso.
Quando l’ambito del diagramma riguarda un sistema composto da più sottosistemi ci potrebbero essere molti
casi d’uso e, quindi, per renderne più agevole la lettura e
semplice la comprensione si possono organizzare gli use
cases in gruppi correlati tra loro. Non c’è alcuno strumento
grafico che ci permette di creare un raggruppamento.
Possiamo traslare questo concetto in Java con i package.
10
I diagrammi : Use Case Diagram
ESEMPIO
Questo è un breve esempio di uno use case diagram per un software di bancomat. L’utente che utilizza tale software può svolgere tre azioni : prelevare, verificare il saldo e
verificare la lista dei movimenti. Per tutti i casi d’uso principali è necessario verificare l’identità o con l’impronta digitale o con l’immissione di un pin. Per l’azione di
prelevare si può scegliere se stampare lo scontrino, mentre 11
Capitolo 5
per i casi d’uso “Lista dei movimenti“ e “Verifica Saldo” è necessario stampare lo scontrino per completare l’azione.
ESERCIZIO 1
Crea un Use Case Diagram sul funzionamento di una
macchina self-service per alimenti e bevande. ESERCIZIO 2
Creare un Use Case Diagram sul funzionamento del software dell’agenzia di viaggi.
12
Diagrammi : Activity Diagram
Activity diagram (diagrammi di attivitĂ )
I diagrammi di attivitĂ vengono utilizzati per illustrare le sequenze di operazioni che possono nascere da una
richiesta o un intervento sul sistema. Sono utili per visualizzare a quali conseguenze e sviluppi può portare 1
Capitolo 6
una certa operazione. I diagrammi di attività sono simili ai
classici diagrammi di flusso (flow chart), ne sono una evoluzione. Come i flow chart offrono la possibilità di
definire flussi che dipendono da test condizionali, domande che hanno una risposta vera o falsa.
Sono diagrammi “comportamentali” che spiegano il flusso logico sequenziale di un processo.
Questi diagrammi permettono di avere una vista
dettagliata delle attività che compongono uno use case o
all'interno del comportamento di un oggetto che accadono in una sequenza ben definita. Questa sequenza da vita agli Activity Diagram.
In un activity diagram ogni attività è rappresentata da un rettangolo con gli angoli arrotondati. L'esecuzione di
un'attività è composta da un ciclo chiuso che al suo termine
porta automaticamente all’inizio della successiva attività.
Una freccia rappresenta la transizione da un'attività alla sua successiva. 2
Diagrammi : Activity Diagram
L 'activity diagram rappresentando un ragionamento
logico ha un capo ed una coda, ogni attivitĂ del nostro diagramma dovrĂ arrivare in qualche modo al punto finale.
Graficamente avremo un punto iniziale, rappresentato da
un cerchio pieno, ed un punto finale rappresentato da un
occhio di bue.
3
Capitolo 6
PUNTI DECISIONALI
I punti decisionali, rappresentati graficamente con un
rombo, stanno ad indicare una diramazione del nostro diagramma. Dal rombo partono tutti i possibili cammini del flusso.
Abbiamo detto che il diagramma di attività serve per rappresentare le eventuali scelte che proporrà il nostro applicativo, quelli che in programmazione si chiamano cicli
condizionali.
Quindi abbiamo la possibilità di disegnare i percorsi “alternativi” che condurrà il nostro programma basandosi sulla veridicità o falsità di una domanda.
4
Diagrammi : Activity Diagram
5
Capitolo 6
I PATH CONCORRENTI
I
comportamenti
concorrenti
che
avvengono
contemporaneamente nel tempo si modellano con due componenti le biforcazioni, fork e le unioni, join.
Il fork si rappresenta con una linea orizzontale in grassetto.
A tale figura si connette una sola transizione in ingresso e
diverse transizioni in uscita ed una per ciascuna attivitĂ concorrente.
Il join si rappresenta una linea orizzontale in grassetto. Ha
piĂš transizioni in ingresso ed una sola transizione in uscirĂ .
Serve quindi a segnalare la fine di un tratto dove le transizioni potevano avvenire in parallelo. Si torna quindi
alla situazione in cui le transizioni avvengono una alla volta.
6
Diagrammi : Activity Diagram
7
Capitolo 6
Sono utili per rappresentare quelle situazioni in cui due o piĂš azioni devono essere eseguite in maniera concorrente,
poichĂŠ ci sono appunto due transizioni che avvengono in contemporanea. Per rappresentare graficamente tale situazione si disegna una freccia. Questa freccia termina su
una linea in grassetto costruita perpendicolarmente. Da questa linea in grassetto si inseriscono tutte le transizioni
necessarie e concorrenti dirette alle attivitĂ coinvolte.
Quando abbiamo bisogno di riunire le transizioni lo schema grafico è lo stesso. Le diverse transizioni
precedentemente divise, finiscono tutte su una linea in grassetto da cui si diparte, sempre perpendicolarmente, la freccia che rappresenta la transizione complessiva.
8
Diagrammi : Activity Diagram
9
Capitolo 6
LE SWIMLANES
Nei diagrammi di attività non sono presenti gli actors come
per i casi d’uso. Quindi per migliorare la visibilità e la
chiarezza del diagramma, si usano le swimlanes per definire più ruoli su cui ricadono le varie attività.
Per crearle si separa il diagramma in rettangoli paralleli, chiamati swimlanes (corsie di nuoto).
Alla sommità di ogni swimlane troviamo il nome di un determinato ruolo (actor) e nella swimlane troviamo le
attività svolte da quel ruolo.
Possono essere create anche alla fine di un diagramma.
10
Diagrammi : Activity Diagram
ESEMPIO
L’esempio tratta un diagramma di attività sull’utilizzo del
bancomat. Possiamo identificare come prima azione l’inserimento della carta e del codice pin a cui segue il 11
Capitolo 6
controllo dello stesso, se il pin non è corretto il processo
finisce, se il pin è corretto si può continuare scegliendo l’operazione da svolgere. Le operazioni proposte sono
prelievo, saldo e lista dei movimenti. Per l’azione del
prelievo se l’importo non è disponibile il processo finisce,
al contrario ritiriamo il denaro e la carta. Alle azioni della verifica del saldo e della lista dei movimenti segue la
stampa dello scontrino e il processo termina con la restituzione della carta.
12
Diagrammi : Activity Diagram
ESERCIZIO 1
Creare un activity diagram sullâ&#x20AC;&#x2122;uso della macchinetta del self-service.
ESERCIZIO 2
Creare un activity diagram sul funzionamento del software dellâ&#x20AC;&#x2122;agenzia di viaggi.
13
Diagrammi : Sequence Diagram
Sequence diagram (diagrammi di sequenza)
Con l’uso del Diagramma di sequenza si può creare l’ ordinamento temporale di messaggi, invocazione di
metodi, scambiati tra i diversi oggetti dell’applicazione.
Questo diagramma svolge una funzione analoga al
diagramma di collaborazione. Quindi il diagramma delle sequenze mostra le dinamiche, basate sul tempo, delle
varie interazioni tra gli oggetti del sistema. Per questo oltre
alla definizione degli stati, vi è la necessità di instaurare una comunicazione tra gli oggetti. Cosa fanno?
1. Modellano la comunicazione tra gli oggetti; 2. ruolo fondamentale lo svolge il tempo;
1
Capitolo 7
3. Controllano che le interazioni tra gli oggetti avvengano seguendo un ordine ben preciso .
Per rendere possibili queste azioni questo diagramma utilizza i seguenti elementi : • Oggetti
– Nome dell’oggetto in alto e sottolineato;
– disposti in sequenza da sinistra verso destra.
• Tempo
– rappresentato come progressione verticale.
• Messaggi
– rappresentati da linee continue recanti una freccia alla loro fine .
2
Diagrammi : Sequence Diagram
Gli oggetti sono disegnati vicino la sommitĂ del diagramma e sono disposti in sequenza da sinistra verso destra.
Possono essere inseriti in un qualunque ordine che semplifichi la comprensione del diagramma.
Da ogni 1
Capitolo 7
rettangolo si diparte una linea tratteggiata verso il basso,
denominata "linea della vita" (lifeline). Lungo la lifeline si
trova un piccolo rettangolo chiamato "attivazione" (activation).
L'activation rappresenta l'esecuzione di
un'operazione di cui l'oggetto si fa carico. La lunghezza del
rettangolo, invece, rappresenta la durata temporale
dell'activation. Più l’activation sarà lunga più l’azione in oggetto durerà nel tempo.
2
Diagrammi : Sequence Diagram
1
Capitolo 7
Nell’immagine viene mostrata la figura dell’actor che come sappiamo è propria degli Use case diagram , in ArgoUml
non troveremo questo strumento grafico. Lo troviamo nell’esempio per dimostrare che anche attraverso il
Sequence diagram può essere implementato un intervento esterno che sarà rappresentato come un oggetto semplice, cioè con un rettangolo con la propria lifeline.
Per rappresentare la comunicazione tra i vari oggetti si
usano i messaggi. I messaggi sono rappresentati da linee
continue recanti una freccia alla loro fine. Partono dalla lifeline dell'oggetto mittente del messaggio e arriva sulla
lifeline dell'oggetto a cui il messaggio è diretto. Se un
oggetto manda un messaggio a se stesso, parte dalla sua
lifeline e arriva alla stessa lifeline, questa azione viene definita ricorsione .
Esempio ricorsione: Si supponga che uno degli oggetti del sistema sia un calcolatore e che una delle sue operazioni sia il calcolo degli interessi. Al fine di permettere il calcolo per un lasso 2
Diagrammi : Sequence Diagram
di tempo che racchiuda parecchi periodi, l'operazione per calcolare gli interessi deve invocare se stessa diverse volte MESSAGGI E FRECCE :
mple
nchronous
ynchronous
Rappresenta il trasferimento del controllo da un oggetto ad un altro.
Se un oggetto invia un messaggio sincrono, allora si attende che gli venga restituita una
risposta al messaggio stesso prima di poter continuare con altre operazioni.
Diversamente dai messaggi sincroni, se un oggetto invia un messaggio asincrono, non
attende che gli venga inviata alcuna risposta prima di continuare con altre operazioni
1
Capitolo 7
LA CREAZIONE
I sequence diagrams possono essere definiti su una singola istanza oppure definire un modello piĂš generico.
I sequence diagrams generici forniscono spesso la possibilitĂ di rappresentare delle istruzioni condizionali
(if) e dei cicli while in cui ogni condizione descritta da un if (oppure da un while) viene racchiusa tra parentesi (nel
caso del while la parentesi sinistra viene preceduta da un asterisco).
Esiste la possibilitĂ , in un sequence diagram, di creare un oggetto che viene rappresentato sempre con un rettangolo
all'interno del quale viene descritto il nome dell'oggetto ma posizionato lungo la dimensione verticale che indica il
tempo in cui tale oggetto viene creato.
2
Diagrammi : Sequence Diagram
Esempio
Il sequence diagram nell’esempio tratta l’utilizzo di una
macchinetta per self-service. Vengono visualizzati quattro
oggetti : l’utente, la parte frontale, la cassetta delle monete
e il contenitore dei prodotti. L’utente da l’avvio alla sequenza inserendo le monete e scegliendo il prodotto, le monete vengono inviate dalla parte frontale (che le riceve)
alla cassetta delle monete che le controlla. Al primo 1
Capitolo 7
controllo le monete risultano inferiori rispetto al costro del
prodotto e la stessa cassetta invia un messaggio alla parte frontale chiedendo all’utente di inserirne di nuove.
L’utente visualizza il messaggio e inserisce nuove monete che vengono rinviate alla cassetta delle monete che nuovamente effettua il controllo. Le monete risultano maggiori del costo del prodotto, quindi, la cassetta
provvede a calcolare il resto. Il resto viene espulso sulla parte frontale. A questo punto le monete inserite corrispondono al costo del prodotto e quindi il contenitore dei prodotti è autorizzato dalla cassetta delle monete ad espellere il prodotto selezionato dall’utente. All’utente non resta che ritirare il resto e il prodotto.
2
Diagrammi : Sequence Diagram
ESERCIZIO 1 :
Modellare un sequence diagram sulla macchina selfservice, l’esempio riportato è incompleto. Aggiungere dove e come necessario i messaggi mancanti. ESERCIZIO 2 : Modellare un Sequence diagram sul bancomat. ESERCIZIO 3: Modellare un Sequence diagram sul software per un’ agenzia di viaggi.
1
COLLABORATION DIAGRAM ( Diagrammi di collaborazione) I Collaboration Diagram fanno parte dei diagrammi comportamentali come i Sequence Diagram, gli Activity Diagram, gli Use Case Diagram e gli Statechart Diagram. Gli elementi di un sistema lavorano insieme per realizzare e soddisfare le necessità del sistema. Queste collaborazioni sono rappresentate dal diagramma di collaborazione . Vengono utilizzati per analizzare le stesse situazioni dei diagrammi di sequenza, evidenziando maggiormente i legami fra le varie componenti e la loro comunicazione nello spazio. Mostrano quindi come interagiscono gli oggetti mettendo in risalto gli oggetti ed i messaggi che si scambiano tra di loro. Oltre la rappresentazione grafica i diagrammi di sequenza e i diagrammi di collaborazione sono molto simili, infatti si può convertire un diagramma di collaborazione con uno di sequenza. Sequence diagram mette in risalto il tempo Collaboration Diagram mette in risalto lo spazio. Nel Collaboration diagram il tempo comunque svolge un ruolo importante e quindi viene implementato ugualmente, non come progressione temporale attraverso la lifeline ma con i numeri di sequenza, sono utilizzati per rappresentare i messaggi appartenenti ad una stessa azione. I diagrammi sono composti da :
• Ruoli = Oggetti rappresentati da rettangoli uniti da una linea continua; • Messaggi rappresentati da frecce con una scritta che indica il messaggio e parametri inseriti tra parentesi tonde. I messaggi sono gli stessi del Sequence diagram ed hanno anche lo stesso significato:
• • •
Semplice = freccia normale Sincrono = freccia piena Asincrono = mezza freccia
RAPPRESENTAZIONE GRAFICA (in questo esempio vediamo l’oggetto cliente che invia un messaggio “costo complessivo” all’oggetto calcolatore).
Possono essere inseriti nel diagramma anche oggetti multipli :
(in questo esempio vediamo l’oggetto professore che invia un messaggio “ assegna i compiti “ a tutti i componenti dell’oggetto multiplo studenti).
Vengono disegnati come una pila di rettangoli inseriti in sequenza inversa (da destra a sinistra). Si aggiunge una condizione (circondata da parentesi) e preceduta da un asterisco per indicare che il messaggio deve giungere a tutti gli oggetti.
ESERCIZIO 1 : Modellare un Collaboration diagram sulla macchina self-service, lâ&#x20AC;&#x2122;esempio riportato è incompleto. Aggiungere dove e come necessario i messaggi mancanti.
ESERCIZIO 2 : Modellare un Collaboration diagram sul bancomat.
ESERCIZIO 3: Modellare un Collaboration diagram sul software per unâ&#x20AC;&#x2122; agenzia di viaggi.
STATE DIAGRAM (Diagrammi di stato) Questo tipo di diagramma illustra la vita di un oggetto software durante l'attività del sistema, mostrando tutti i possibili stati in cui esso può venirsi a trovare, e come avviene il passaggio fra gli stati. Il diagramma di stato, chiamato anche State chart , rappresenta lo stato in cui si trova quella particolare istanza l’oggetto e gli eventuali cambiamenti nel tempo dell’oggetto stesso. Ogni stato del diagramma inizia con un simbolo che identifica lo stato iniziale (Start State) e termina con un altro simbolo che rappresenta lo stato finale (End State). Come i diagrammi di attività sono diagrammi di flusso (flow chart). Consentono di modellare un processo o un'entità tramite: • Stati; • Transizioni tra stati. In UML lo stato viene definito come: “una condizione o una situazione della vita di un oggetto durante la quale tale oggetto soddisfa una condizione, esegue un‘attività o aspetta un qualche evento” Affinché sia rilevante modellare degli stati di una entità deve esistere tra tali stati una differenza semantica.
I diagrammi di attività sono un tipo speciale di diagrammi di stato in cui gli stati sono stati d'azione e le transizioni sono attivate automaticamente al completamento delle azioni e delle attività di uno stato. Utilizzano un piccolo sottoinsieme della sintassi UML per i diagrammi di stato. I diagrammi di stato vengono usati per modellare il ciclo di vita di una entità reattiva, descrivere la macchina a stati di una singola entità e modellare il comportamento dinamico di: • Classi; • Casi d'uso; • Sottosistemi; • Sistemi interi.
Una entità reattiva è : – È un oggetto che fornisce il contesto ad un diagramma di stato; – Risponde a eventi esterni; – Ha un ciclo di vita definito, che può essere modellato come successioni di stati e transizioni; – Ha un comportamento corrente che dipende dai comportamenti precedenti.
Per ogni classe può esistere una macchina a stati che modella tutte le transizioni di stato di tutti gli oggetti di quella classe, in risposta a diversi tipi di evento. Gli eventi sono tipicamente dei messaggi che vengono inviati da altri oggetti o generati internamente. La macchina a stati di una classe modella il comportamento degli oggetti della classe in modo trasversale per tutti i casi d'uso interessati.
ELEMENTI GRAFICI : Gli stati sono rappresentati con rettangoli arrotondati;
Le transizioni indicano un possibile percorso tra due stati e sono modellate con delle frecce;
Gli eventi sono istantanei e vengono scritti sopra la transizione che attivano.
ESEMPIO GRAFICO :
Nellâ&#x20AC;&#x2122;immagine precedente si può notare come uno stato può attivare delle azioni:
– Entry: le azioni si attivanpo non appena si entra nello stato; – Do: le azioni si svolgono nel ciclo di vita dello stato; – Evento: le azioni avvengono in risposta ad un evento; – Exit: le azioni si attivano appena prima di uscire dallo stato; – Include: chiama una submachine, rappresentata da un altro diagramma degli stati.
LE TRANSIZIONI : Quando avviene un evento, se la condizione risulta vera, allora esegue una azione, quindi entra immediatamente nello stato B.
GLI EVENTI : Un evento un evento è qualcosa che accade che colpisce il sistema. Può essere anche parametrizzato. Ne conosciamo quattro tipi: – Evento di chiamata: equivale a una richiesta di esecuzione di un insieme di azioni; – Evento di segnale: ricezione di un segnale, un segnale è un pacchetto di informazioni inviato in modo asincrono da un oggetto a un altro;
Poiché un segnale permette solo il passaggio di informazioni tra oggetti diversi, non può avere alcuna operazione. La ricezione di un segnale da parte di un'entità reattiva può essere modellata come un evento di segnale. L'attivazione di un evento di segnale è un metodo che prende come parametro quel tipo di segnale.
– Evento di variazione: l'azione associata viene eseguita quando l'espressione Booleana seguente alla parola chiave quando risulta vera;
– Evento del tempo: evento che viene attivato in determinati momenti del tempo, indicati con le parole chiave quando e dopo.
STATI COMPOSTI : Uno stato composto è uno stato che può essere ulteriormente spezzato in sottostati, questi possono essere rappresentati all'interno del medesimo diagramma o in uno a parte. Esempio di state machine di una azione “crea viaggio”:
STATI CONCORRENTI : È possibile che uno stato composto possieda più stati concorrenti. Esempio di stati concorrenti per la classe Cliente in fase di registrazione , un'istanza di Cliente sarà sia nello stato Attesa Pagamento che in quello Attesa accertamenti.
LINEE DI SINCRONIZZAZIONE : Nell'esempio precedente è possibile eliminare del tutto lo stato Candidato e promuovere i due sottostanti. Si presenta lo stato Socio solo quando Attesa pagamento e Attesa accertamenti vengono portati a termine. Quando lo stato di una classe si divide in stati paralleli, questi si devono ricombinare nel medesimo diagramma.
Eâ&#x20AC;&#x2122; possibile rappresentare anche transizioni da e verso stati composti. Per indicare che, in fase di rinnovo, non si ha la necessitĂ di ulteriori accertamenti legali, si modella una transizione con condizione verso lo stato interno relativo al pagamento.
STATI DI SYNCH : A volte è necessario sincronizzare i flussi paralleli di uno stato composto. Per questo si usa la notazione degli stati di synch :
CLASS DIAGRAM (Diagrammi di classe) I class diagram sono fondamentali per comprendere la struttura della programmazione object oriented. Un class diagram mostra la struttura statica del modello, in altre parole, le cose che esistono come le classi, la loro struttura interna e le relazioni occorrenti tra loro e con altri oggetti. E’ un diagramma strutturale. E’ strutturato intorno alla classe, la quale viene intesa come una categoria o un gruppo di oggetti che hanno attributi simili e comportamenti analoghi. La classe viene inizializzata assegnandone un nome posto nella parte superiore dell’oggetto grafico.
Ogni elemento inserito all’interno di questo diagramma fa parte di uno schema gerarchico. Abbiamo detto che una classe contiene attributi e metodi. Vediamo insieme cosa sono. ATTRIBUTI :
Gli attributi sono le proprietà di una classe. Descrivono un insieme di valori che la proprietà può avere quando vengono istanziati oggetti di quella determinata classe. Gli oggetti daranno valore e vita a questi attributi rispettandone la tipologia. All’interno di una classe ci possono essere zero o più attributi. All’interno dell’oggetto grafico, gli attributi, vengono inseriti nel secondo riquadro.
METODI :
I metodi sono le operazioni che possono essere svolte attraverso questa classe. Come per gli attributi , anche i metodi, sono “nelle mani “ degli oggetti, possiamo dire, infatti, che sono un'azione che gli oggetti di una certa classe possono compiere. Nelle parentesi che seguono il nome di un metodo è possibile mostrare gli eventuali parametri necessari al metodo insieme al loro tipo. Se il metodo rappresenta una funzione è necessario anche specificare il tipo restituito. All’interno dell’oggetto grafico, i metodi , vengono inseriti nel terzo riquadro.
Tra i componenti di questo diagramma intercorrono delle relazioni. Ogni singolo elemento deve essere collegato in qualche modo al resto del diagramma, se ci fossero elementi isolati ne verrebbe meno la connotazione logica del diagramma ed ovviamente anche del programma su di esso creato.
Le tipologie di relazione ammesse sono :
1. Associazione; 2. Ereditarietà e Generalizzazione; 3. Aggregazione; 4. Composizione. 1. Quando più classi sono connesse l'una con l'altra da un punto di vista concettuale, tale connessione viene denominata associazione. E’ anche possibile assegnare dei nomi ai “ruoli” svolti da ciascun elemento di un associazione. I nomi vengono posti sulla linea retta dell’associazione. Anche nei casi in cui non è strettamente necessario, il nome del ruolo può essere utile per aumentare la leggibilità, quindi la chiarezza, del diagramma.
Esistono anche le associazioni complesse. Vengono inserite quando una classe si crea da una associazione.
ESEMPIO :
La molteplicità è un tipo speciale di associazione in cui si mostra il numero di oggetti appartenenti ad una classe che interagisce con il numero di oggetti della classe associata. Il valore di default della molteplicità è [1] ma è preferibile comunque esprimere sempre il valore della molteplicità. Una associazione può essere anche riflessiva. Quando Una classe associata con se stessa. Può essere chiamata anche auto-associazione.
ESEMPIO :
2. L’ereditarietà è uno dei tre principi dell’UML ed è quindi molto importante per la programmazione ad oggetti. Capirla è un grande passo avanti. Crea una gerarchia tra le classi del diagramma. Sta ad indicare che avendo una classe generica, chiamata correttamente superclasse, possiamo costruire delle classi
figlie, chiamate sottoclassi. La suddetta superclasse contiene attributi e metodi comuni ad un gruppo di sottoclassi, le quali potranno utilizzare il contenuto della classe madre ed aggiungere proprie caratteristiche. In questo modo potremo avere classi sempre più specifiche. ESEMPIO :
Dall’esempio che vediamo abbiamo una superclasse Animale la quale genera tre sottoclassi Anfibio, Mammifero e Rettile, a sua volta Mammifero diventa superclasse per Cane, Gatto e Cavallo.
3. L’aggregazione non implica legami di ereditarietà ma collega sempre le classi, una aggregazione è un tipo speciale di classe che può rappresentare l’insieme di altre classi che la compongono. Le classi collegate tra di loro vanno a costituire una relazione particolare del tipo: parte - intero (part-Whole). E’ rappresentata da un rombo vuoto sull’associazione, posto vicino alla classe le cui istanze sono gli “interi”.
ESEMPIO :
Con questo esempio possiamo rappresentare la composizione della classe TV che è l’insieme delle classi Telecomando, Box TV, Altoparlanti, Circuito Integrato e Schermo. A sua volta la classe Telecomando è il risultato dell’aggregazione delle classi Luci Remote, Tastiera, Batterie e Circuito Integrato.
4. La composizione è molto simile all’aggregazione ma crea tra le classi un legame più forte. Spiega che le classi appartenenti a questa relazione possono essere parte solamente di questo intero, all’esterno della composizione non hanno senso di esistere. E’ rappresentata da un rombo pieno vicino alla classe da cui parte la relazione tra gli “interi”.
ESEMPIO :
Nell’esempio riportato sopra possiamo notare come la classe Uomo sia composta chiaramente dalla classe Testa con rapporto uno a uno, dalla classe Corpo con rapporto uno a uno, dalla classe Braccia con rapporto uno a due e dalla classe Gamba anch’essa con rapporto uno a due.
ESERCIZIO 1 : Modellare un Class diagram che inserisca gli studenti specificando nome, cognome, numero di matricola e età, il corso di laurea a cui sono iscritti, ed i corsi di cui hanno sostenuto l’esame. Di ogni corso di laurea interessa il codice e il nome. Di ogni corso interessa il nome e la disciplina a cui appartiene (ad esempio: matematica, chimica, informatica, fisica…).
CLASSI ASTRATTE
Se pensate a delle classi, aventi uno o più metodi in comune, allora questi possono essere messi all’interno di una stessa superclasse che definisce le operazioni ma non le implementa: questa classe prende il nome di Classe Astratta e si dichiara con il modificatore abstract. Abstract può essere applicato sia a metodi che alle classi, ma non a variabili in quanto dichiarare una variabile astratta non avrebbe nessun senso logico. Iniziamo con il dire che se vogliamo definire un metodo astratto, dobbiamo variare la sintassi per la scrittura di tale metodo in quanto dovremo eliminare le parentesi graffe. Un metodo astratto si definisce nel seguente modo: abstract tipoDiRitorno nomeMetodo();
Una classe, se definita astratta, non può essere in alcun modo istanziata, cioè non può produrre oggetti e questo è in linea con tutto quello detto fin ora sugli oggetti. Dato che un oggetto è la realizzazione concreta di una classe, non può esserlo di una classe astratta. Per essere maggiormente chiari facciamo un esempio, non posso scrivere Persona p = new Persona(). Sarebbe un errore, perché la classe Persona è come se non esistesse essendo abstract.
1 2 3 4
public abstract class Persona
{ public abstract void datiPersonali(); } In questo modo posso creare una abstract class Persona e un metodo abstract datiPersonali, che compirà qualche operazione. Il cosa farà non è importante adesso, nella nostra superclasse, lo diventerà quando una sottoclasse andrà ad implementarlo, attraverso l’override. Le classi astratte dovranno essere utilizzate con criterio, cioè conoscendone limiti e potenze, infatti tutte le classi che deriveranno da Persona, devono implementare il metodo astratto datiPersonali.
1 2
3 4 5 6
public class Studente extends Persona
{ private int matricola;
public void datiPersonali() { System.out.println("Matricola: "+matricola); }
Se un metodo è definito abstract, allora deve essere abstract anche la classe altrimenti viene generato un errore in fase di compilazione del codice. Non si possono definire degli oggetti, delle istanze di una classe astratta. Tutti i metodi abstract devono essere implementati dalle sottoclassi, una classe astratta può avere anche metodi che non sono abstract e può dare loro un’implementazione di default, quindi metodi completi di parentesi graffe e istruzioni. In questo caso sarà possibile comunque fare l’override di suddetti metodi. Grazie alle classi astratte si può gestire in maniera più snella il codice di un progetto raggiunga dimensioni importanti. In questo caso sono molto utili perché permettono una migliore organizzazione dei comportamenti e delle funzionalità.
ESERCIZIO :
public abstract class Animale { public abstract String verso(); public abstract String movimento(); public abstract String habitat(); ... }
Costruite in base a questa superclasse due sottoclassi ancora una volta abstract: AnimaleTerrestre e AnimaleAcquatico. Di conseguenza generate a piacere due classi derivate non astratte e per ogni classe due oggetti.
LE INTERFACCE
Alcuni linguaggi permettono poi l’ ereditarietà multipla, quindi la capacità di una classe di estendere le sue funzionalità da più classi. Questo permette di ottenere una maggiore flessibilità nel codice ma allo stesso tempo maggiore confusione, in quanto possono essere presenti metodi con lo stesso nome ereditati da classi distinte, e quindi con semantica probabilmente differente. Per questo motivo in Java l’ereditarietà multipla non è ammessa. Ma resta sempre la necessità di disporre di un nuovo costrutto simile alla classe, ma privo di implementazioni e non legato alla gerarchia di ereditarietà delle classi, con i relativi vincoli. Un interfaccia è una collezione di metodi senza alcuna definizione. Possono esservi presenti anche costanti. Nasce per definire un protocollo del comportamento che deve essere fornito ad una classe attraverso l’implementazione dell’interfaccia stessa. Una qualsiasi classe che implementa una data interfaccia è quindi obbligata a fornire l’implementazione di tutti i metodi elencati all’interno dell’interfaccia. Quindi , una classe può implementare (una o più) interfacce tramite la keyword implements .In tal caso, la classe deve implementare tutti i metodi richiesti, pena errore di compilazione. Caratteristiche dell’interfaccia : contiene solo dichiarazioni di metodi, ed eventualmente ha costanti ma non variabili né implementazioni di metodi ha una struttura analoga a una classe, ma è introdotta dalla parola chiave interface anziché class.
Capita che le interfacce vengono confuse con le classi astratte, in realtà dal punto di vista logico sono molto simili, possiamo dire che le interfacce siano un'evoluzione delle classi astratte e permettono, di fatto, di simulare l'ereditarietà multipla. Vediamo le differenze tra interfacce e classi astratte: Un interfaccia non può avere metodi con istruzioni, mentre una classe astratta può fornire una implementazione, anche se parziale; Una classe può implementare molte interfacce ma può essere derivata da una sola superclasse; Le interfacce non fanno parte della gerarchia delle classi, quindi
classi non discendenti l’una dall’altra possono implementare la stessa interfaccia se occorre fornire ereditarietà multipla, si usano le interfacce, se si deve fornire una parte (comune) dell’implementazione, si usano le classi astratte se tuttavia ci si trova a scrivere una classe astratta senza alcuna implementazione, di fatto è un’interfaccia. Le interfacce possono dare luogo a gerarchie, come le classi. Ma la gerarchia delle interfacce è una gerarchia separata da quella delle classi. Come in ogni gerarchia, anche qui le interfacce derivate: •
possono aggiungere nuove dichiarazioni di metodi
•
possono aggiungere nuove costanti
•
non possono eliminare nulla.
Java supporta l’ereditarietà multipla fra interfacce perché una interfaccia contiene solo dichiarazioni di metodi, non ne riporta alcuna implementazione e quindi nessun problema di collisione fra metodi omonimi, non avendo variabili non vi è nessun problema di collisione fra dati omonimi. Possiamo dire che è un potente strumento di modellizzazione.
La sintassi della dichiarazione di un’interfaccia è questa : modificatore interface nome { istruzioni; } Per modificatore si intende: - annotazione - public (si potrebbe anche non inserire nulla, sarà quindi accessibile a livello di package) - strictfp, che impone alla JVM di attenersi strettamente allo standard IEEE 754 nel calcolo di espressioni in virgola mobile. Le costanti sono implicitamente static, final e public, saranno necessariamente inizializzate. Saranno richiamate tramite la notazione puntata con il nome dell’interfaccia. I metodi possono solo avere annotazioni, e nessun altro genere modificatore di accesso; sono implicitamente public, abstract, non possono essere statici (perché static e abstract sono incompatibili), né final, né prevedono synchronized, strictfp, native (tutti modificatori influenti sull’implementazione perché nell’interfaccia non esiste). RIEPILOGANDO… La differenza tra estendere e implementare è molto importante in quanto una classe può essere ereditata solo ed esclusivamente una volta, mentre una classe può implementare infinite interfacce permettendo così l'ereditarietà multipla. Questa manca in java, in realtà non è un vera e propria mancanza, anzi è un grosso vantaggio in quanto l'ereditarietà multipla selvaggia, ovvero il fatto di ereditarie da più classi, variabili e metodi già strutturati, è solitamente uno dei fattori principali che vanno a ledere la robustezza dell'intera applicazione.
La gestione delle eccezioni
Scrivendo un codice possiamo incorrere in errori. Esistono errori di sintassi e errori di semantica. I primi vengono individuati dalla JVM durante la creazione del bytecode , quindi nella fase della compilazione attivata dal comando javac. Gli errori di compilazione sono diversi, generalizzando possiamo dire che si verificano quando non si rispettano le regole della sintassi del linguaggio. Sono quegli errori che non permettono di visualizzare alcun output. I secondi, gli errori di semantica, sono chiamati pi첫 correttamente eccezioni, in quanto, anche se viene rispettata la sintassi del codice questo non produce un output logicamente corretto. Come con la lingua italiana, per essere pi첫 chiari, possiamo scrivere una frase grammaticalmente e/o semanticamente scorretta. Ne facciamo qualche esempio :
Il cane essere fame.
Ovviamente l’errore è palese, abbiamo sbagliato l’uso del verbo. Correttamente dovremmo scrivere :
Il cane ha fame.
Una eccezione alla grammatica italiana potrebbe essere :
Il prato corre sul gatto.
Anziché :
Il gatto corre sul prato.
Anche in questo caso l’errore è visibilissimo, la frase ha una struttura corretta (soggetto – verbo –complemento) ma non ha alcun senso logico.
In Java la gestione degli errori e delle eccezioni deriva dalla classe Throwable presente nel package java.lang (importato di default), dalla suddetta classe nascono le classi Error ed Exception. Ecco di seguito una parte della gerarchia di classi :
Il programmatore trae enorme vantaggio dalla gestione degli errori, perché Java riesce nella maggior parte dei casi ad essere molto preciso nell’individuazione degli errori. Precisiamo che la presenza di un errore di sintassi blocca il codice fino alla correzione del o degli errori (parte della preparazione di un programmatore sta proprio nell’individuare gli errori e correggerli al fine di non commetterli più), mentre la visualizzazione di un’
eccezione accade in runtime e quindi quando il programma è già in esecuzione. Per non far bloccare il programma durante l’utilizzo da parte dell’utente, facendo comparire errori incomprensibili o peggio facendo chiudere il programma causando la perdita dei dati, le eccezioni devono essere gestite dal programmatore. Per farlo si utilizzano i blocchi di codice trycatch. La JVM “lancia” l’eccezione trovata nel blocco try e la gestisce con le istruzioni trovate nel blocco catch. Vediamo un codice di esempio per chiarirci meglio le idee :
public class NotHandled{ public static void main(String args[]){ int num[]=new int[5]; System.out.println(“Prima dell’eccezione”); num[10]= 7; }
}
Se provaste ad eseguire questa classe vedreste apparire, dopo la creazione del bytecode, una “exception in thread main…”. La JVM ci sta dicendo che non trova un nesso logico tra quello che abbiamo scritto e quello che deve fare. In pratica abbiamo creato un array “num” la cui dimensione è 5, successivamente tentiamo di assegnare un valore alla posizione 10 dell’ array che chiaramente non esiste. Come avrete potuto notare l’errore non viene visualizzato nella fase della compilazione, perché il codice risulta scritto bene (segue le regole della sintassi), ma ci viene evidenziato durante la fase dell’esecuzione. Noteremo, inoltre, che
inizia ad eseguire, infatti stampa il contenuto del metodo di stampa, ma poi incontrata l’eccezione si blocca. Bene i blocchi di codice try-catch evitano proprio questo, che l’esecuzione del programma si blocchi dando la possibilità di mandare messaggi che spiegano all’utente cosa succede. Ora vediamo lo stesso codice “aggiustato” :
public class Handled{ public static void main(String args[]){ int num[]=new int[5]; try{ System.out.println(“Prima dell’eccezione”); num[10]= 7; System.out.println(“Non vedremo questo messaggio”); }
catch(ArrayIndexOutOfBoundsException exc){ System.out.println(“Hai superato i limiti dell’array”); } } }
Eseguendo questa nuova versione del codice precedente vedrete che il programma non si bloccherà più, l’eccezione si verificherà ugualmente ma sarà gestita. La JVM controllando le istruzioni presenti nel blocco try, se riscontra qualche problema cerca nei catch (perché possono essere anche più di uno) la soluzione alla precisa eccezione verificatasi. Da
questo capiamo, quindi, che il try è uno e può contenere una lunga serie di istruzioni mentre i catch possono essere molti e sono specifici , possono “acchiappare” solo il tipo di eccezione che contengono. Vediamo di seguito un codice con due catch :
public class DoubleCatch{ public static void main(String args[]){ int num[]={4, 8, 16, 32, 64, 128, 256, 512}; int den[]={2, 0, 4, 4, 0, 8}; for(int i=0; i<num.length;i++){ try{ System.out.println(num[i] + “ / “ + den[i] + “ = “ + num[i]/den[i]); } catch(ArithmeticException exc){ System.out.println(“Non puoi divider per zero!!!”);
} catch(ArrayIndexOutOfBoundsException exc){ System.out.println(“Hai terminato i dividendi”); } } } }
Eseguendo questo codice come output avrete due volte la gestione dell’eccezione aritmetica e due volte l’eccezione del superamento dei limiti dell’array le altre operazioni le svolgerà come gli viene richiesto. È anche possibile far seguire ad un blocco try, oltre a blocchi catch, un altro blocco finally , tutto ciò che è definito in esso viene eseguito in qualsiasi caso, sia se viene o non viene lanciata l’eccezione.
Siccome in un programma non si possono prevedere tutte le tipologie di eccezioni , ognuna delle quali ha un nome, all’interno dell’ultimo catch possiamo inserire la gestione più generica “Throwable exc” . Questa tipologia ci propone una modalità di gestione generica, infatti prende il nome della superclasse, inoltre deve essere messa all’ultimo posto altrimenti annulla tutte le altre gestioni che la seguono. Le eccezioni più frequenti sono Java: • NullPointerException; • ArrayIndexOutOfBoundsException;
• ClassCastException; • IOException e le sottoclassi FileNotFoundException, EOFException, etc…. del package java.io; • SQLException del package java.sql; • ConnectException, il package java.net.
Eccezioni personalizzate
Le eccezioni risiedono nei package di java, il programmatore imparerà a conoscerle con la pratica. In alcuni casi sarà utile allo sviluppatore definire nuovi tipi di eccezioni. Per farlo bisogna estendere la classe Exception ed eventualmente fare override di alcuni metodi come per esempio toString(). Ovviamente la JVM sa quando lanciare una eccezione che contiene nelle librerie, per le eccezioni personalizzate sarà il programmatore a dover decidere quando lanciarle. Esiste a questo scopo
la keyword “throw” che in inglese si traduce “lancia”, con la seguente sintassi :
NomeClasseNuovaEcc reference = new CostrClasseNuovaEcc(); Facciamone un esempio:
class Biglietteria { private int postiprenotabili; public Biglietteria() { postiprenotabili = 100; } public void compra() throws CompraException { try { //controllo sulla disponibilità dei posti if (postiprenotabili == 0) {
//lancio dellâ&#x20AC;&#x2122;eccezione throw new CompraException(); } //metodo che compra il posto // se non viene lanciata lâ&#x20AC;&#x2122;eccezione postiprenotabili --; } catch (CompraException exc) { System.out.println(exc.toString()); } } } public class GestorePosti { public static void main(String args[]) { Biglietteria biglietto = new Biglietteria();
try { for (int i = 1; i <= 101; ++i) { biglietto.compra(); System.out.println("Prenotato posto n째 " + i); } } catch (CompraException exc) { System.out.println(exc.toString()); } } } class CompraException extends Exception { public CompraException() { // Il metodo costruttore di Exception inizializza la // variabile privata message
super("Problema con la prenotazione"); } public String toString() { return getMessage() + ": non ci sono posti disponibili!"; } } Nel nostro codice di esempio avremo l’eccezione personalizzata creata nella classe CompraException ,che estende la classe Exception, attraverso il metodo costruttore della classe e l’override del metodo toString(). L’eccezione viene lanciata nella classe Biglietteria con la keyword throw e nella classe GestorePosti gestita con il blocco try-catch.
Asserzioni
Un altro elemento che aiuta il programmatore nella gestione del suo codice, è l’asserzione. A partire da Java 1.4 è stato aggiunto il comando assert che verifica se un'asserzione è violata e solleva un'opportuna eccezione quando questo accade. Permette , attraverso la keyword assert, di testare parti di codice con il fine di controllare i comportamenti dell’applicazione. Un’asserzione verifica una condizione booleana, se il risultato è false allora si tratta di un bug. Il programmatore può riempire il codice di asserzioni, in modo tale da verificare la robustezza del codice in maniera semplice ed efficace. Lo sviluppatore può infine disabilitare la lettura delle asserzioni da parte della JVM, in fase di completamento del software, al fine di non rallentarne l’esecuzione. Sono anche un ottimo strumento per migliorare la documentazione del software, per renderne più facile la fase della manutenzione. Un’asserzione , quindi, è un’istruzione che permette di testare gli eventuali comportamenti che un’applicazione deve avere. Le asserzioni possono quindi rappresentare un’utile strumento per accertarsi che il codice scritto si comporti così come ci si aspetta. Si possono utilizzare due tipologie di sintassi :
1 assert espressione_booleana; 2 assert espressione_booleana : espressione_stampabile;
Leggendo entrambe le sintassi possiamo notare quanto siano facili da ricordare. Nel caso della prima sintassi l’applicazione esegue la sintassi controllando l’espressione booleana, se è vera il programma prosegue se invece il risultato è falso viene lanciato l’errore AssertionError. Se il risultato è falso viene visualizzato uno stack-trace dal metodo printStackTrace() della classe Throwable. Nel caso della seconda sintassi viene aggiunta l’espressione stampabile, un messaggio. Questa espressione_stampabile può essere una qualsiasi espressione che ritorni un determinato valore, ovviamente non è possibile invocare un metodo con tipo di ritorno void. La seconda sintassi permette quindi di rendere maggiormente chiaro lo stack-trace espresso nelle asserzioni. Dal punto di vista sintattico equivale a :
if (!espressione_booleana) throw new AssertionError(espressione_stampabile);
“Design by contract” Le asserzioni devono il loro successo , la loro implementazione al Design by contract (DbC). La “ progettazione per contratto” è un approccio per la progettazione di software. Essa prescrive che i progettisti di software debbano definire specifiche formali, precise e verificabili di interfaccia per i componenti software, che estendono la definizione ordinaria di tipi di dati astratti con precondizioni, postcondizioni e invarianti. Queste specifiche sono indicati come "contratti", secondo una metafora concettuale con le condizioni e gli obblighi del contratto di lavoro. Per esempio il fornitore deve dare un certo prodotto ( obbligo) e ha diritto di aspettarsi che il cliente paghi il suo costo (beneficio) . Il cliente deve pagare la tassa ( obbligo) e ha diritto di avere il prodotto ( beneficio) . Entrambe le parti devono soddisfare alcuni obblighi , quali leggi e regolamenti , come si applica a tutti i contratti . Allo stesso modo , una classe di programmazione object-oriented che fornisce una certa funzionalità , deve:
1. Avere una certa condizione deve essere garantita in ingresso da qualsiasi modulo client che la chiami (precondizione), l’asserzione viene utilizzata per non perdere il controllo del codice o per non controllare troppo. 2. Garantire una certa proprietà in uscita (postcondizione), quindi anticipare lo stato dell’applicazione quando un’aziono viene completata. 3. Mantenere una certa proprietà , assunta in entrata e garantita in uscita (invariante), quindi permette di specificare i vincoli per tutti gli oggetti istanziati. Sottolineiamo che il Design By Contract è una tecnica di progettazione e non una tipologia di programmazione.
Gli ARRAY Gli array sono un potente strumento Java per la gestione dei dati. Possiamo immaginarli come dei contenitori di elementi dello stesso tipo. Normalmente per associare un valore utilizziamo una variabile e per più valori abbiamo bisogno di più variabili. Questa è una routine usuale , corretta e normalissima ma in alcuni casi potrebbe essere resa più semplice e breve. Anche se assomigliano a delle variabili, in realtà, in Java gli array sono oggetti. Gli array hanno un tipo di dati, un nome ed una dimensione. La sintassi della sua creazione è la seguente :
tipoDati nomeArray [] = new tipoDati [dimensione] ;
Le parentesi quadre sono necessarie per l’identificazione dell’array, possono anche essere antecedenti al nome, modificando la sintassi in questo modo :
tipoDati [] nomeArray = new tipoDati [dimensione] ;
Al momento della creazione , lâ&#x20AC;&#x2122;array, viene riempito di tanti elementi quanti ne esprime la dimensione. Tali elementi una volta creati assumono, automaticamente, un valore nullo, quindi vanno successivamente inizializzati. Lâ&#x20AC;&#x2122;indice dellâ&#x20AC;&#x2122;array comincia sempre dalla posizione 0, se dichiariamo un array di 10 posti , potremo gestire 11 valori, partendo dalla posizione 0 arrivando alla posizione 10. Secondo le regole di Java ogni posizione di un array va inizializzata singolarmente, seguendo questa sintassi :
nomeArray [posto] = valore ;
esempio[0]=10;
//array di interi
Ovviamente dare un valore ad ogni singola posizione in questo modo potrebbe rendere troppo lunga e fastidiosa l’inizializzazione degli elementi, per questo motivo Java ci permette di farlo usando una sintassi più breve :
int esempio [] = {10 , 8 , 6 , 4 , 2 ,0 } ;
Per utilizzare tutte le posizioni dell’array possiamo ricorrere alla variabile length, che richiamata dal nome dell’array ne restituisce la dimensione effettiva.
esempio.length ;
Solitamente gli array sfruttano i cicli per ottimizzare le proprie funzionalità d’insieme.
È necessario precisare che esistono due tipologie di array : di variabili e di oggetti. L’array di oggetti è dichiarato utilizzando la sintassi spiegata precedentemente ma ovviamente va sostituito al tipo di dati il nome della classe che genera gli oggetti. In questo modo :
nomeClasse nomeArray [] = new nomeCostruttore[dimensione];
cioè :
Class oggetti [] = new Class [10] ;
Dovendo creare gli oggetti, faremo in questo modo :
nomeArray[0] = new nomeCostruttore();
cioè :
oggetti [0] = new Class();
oppure , utilizzando la sintassi abbreviata :
nomeClasse nomeArray [] = { new nomeCostruttore() , new nomeCostruttore() , new nomeCostruttore() , new nomeCostruttore() …} ;
quindi :
Class oggetti [] = { new Class() , new Class() , new Class() , new Class() …} ;
Come possiamo notare vengono create nell’ultimo esempio solo i reference della suddetta classe. Possono essere dichiarati e definiti array di qualsiasi tipo di dati ed anche array multidimensionali con la seguente sintassi :
tipoDati nomeArray [] [] = new tipoDati[dimensione1] [dimensione2];
notiamo quindi che la sintassi cambia solo nell’aumento delle dimensioni. Java costruisce array multidimensionali da molti array monodimensionali, i cosiddetti array di array , possiamo immaginarli come una tabella formata da righe e colonne. Ogni riga è un oggetto cioè un array che può essere usato indipendentemente.
ESEMPIO :
int tabella [] [] = new int [10] [5];
In questo modo allochiamo un array di interi con 10 righe e 5 colonne. Come per tutti gli oggetti, i valori sono inizializzati a zero. Spesso gli array a bidimensionali sono trattati con cicli for annidati. Si noti il seguente esempio :
int tabella [] [] = new int [10] [5] ;
for (int x=0; x<tabella.length; x++) {
for (int y=0; y<tabella[x].length; y++) {
System.out.print(" " + tabella[x][y]);
}
System.out.println(" ");
}
Gli array hanno due limiti :
1. Sono solo omogenei, sono collezioni di dati dello stesso tipo; 2. Non sono ridimensionabili, essendo oggetti.
L’uso scorretto degli array, mi riferisco non alla sintassi ma a violazioni logiche del concetto di array, produce un eccezione che si chiama : “ArrayIndexOutOfBoundsException”
ESERCIZIO1: Usando un array unidimensionale crea una classe che stampi il tuo nome e cognome.
ESERCIZIO2: Usando un array unidimensionale crea una classe che di tale array ne stampi prima l’intero contenuto e poi il numero più grande ed il numero più piccolo.
ESERCIZIO3: Usando un array bidimensionale crea una classe che stampi la tavola pitagorica.
GLI SPECIFICATORI D’ACCESSO
Gli specificatori di accesso sono particolari keywords che indicano dove l'accesso , quindi l’utilizzo , ad un elemento è consentito. Vengono usati per mostrare la visibilità di un elemento (classe, metodo, variabile). Sono posizionati all’inizio di ciascuna definizione di un membro (“ public class foo “ – “ private void foo() “ – “ protected int foo ”). Se non si utilizza nessuno specificatore di accesso, si ha un accesso di default ,accesso a livello di package. In questo modo chi fornisce le librerie sa esattamente cosa può modificare e cosa no, senza correre il rischio di rendere inutilizzabile il codice ai clients.
Attraverso gli specificatori d’accesso si permette di utilizzare un altro principio della programmazione object oriented : l’incapsulamento.
Sono :
•
public : è il più semplice dei modificatori,
l'accesso è consentito ovunque, da qualsiasi parte del codice. E’ possibile utilizzare public anche nella definizione di una classe. Indica che tale classe è visibile al di fuori del package.
•
private : è il più restrittivo, l'accesso è consentito solo all'interno
della classe a cui appartiene l’elemento. •
protected : l’accesso è consentito nei limiti dell’ereditarietà, un
elemento è visibile dalle sottoclassi di una superclasse ed anche a livello package. •
package : l’accesso è consentito solo all’interno dello stesso
package, può essere anche omesso. Tutte le classi nel package hanno accesso a quell’elemento, ma a tutte le classi fuori dal quel package l’elemento appare come private.
A seconda dell’elemento che accompagnano , gli specificatori d’accesso, possono avere un significato diverso. Per esempio quando una classe
viene definita public è visibile ed utilizzabile da tutti, quando invece è definita private è invece utilizzabile solamente all’interno del file in cui è definita. In uno stesso file possono essereci definite più di una classe, l’importante è che se si vuole rendere utilizzabile una di esse deve essere public ed avere il nome del file.
Lo specificatore d’accesso private può risultare fin troppo restrittivo, per questo java mette a disposizione dei meccanismi per utilizzare anche gli elementi contrassegnati da esso. Questi sono i metodi d’accesso . Sono metodi di lettura e scrittura “get “ e
“ set”, quindi per leggere o
modificare gli elementi contrassegnati con private è necessario implementare e richiamare questi due metodi. I metodi d’accesso vanno dichiarati e definiti nella classe che contiene l’elemento private. Esistono nel linguaggio java altri modificatori come final, abstract, static etc…che vedremo in seguito.
ESERCIZIO 1 :
Create una classe con variabili e metodi public, private, protected e accessibili a livello di package. Create un oggetto di questa classe e verificate quali messaggi ricevete dalla JVM quando cercate di accedere a tutti i membri della classe.
ESERCIZIO 2:
Create una classe contenente due variabili dichiarate come private, siano esse la base e lâ&#x20AC;&#x2122;astezza di un triangolo. Nella classe esecutiva create due oggetti (due triangoli) i quali dovranno dare un valore alle variabili e successivamente dovranno stampare tale valore.
I CICLI E LE CONDIZIONI
In Java , come in tutti i linguaggi di programmazione , sono presenti dei costrutti che permettono di gestire il flusso delle istruzioni. Questi sono cicli o condizioni. I cicli permettono di iterare un numero finito di volte alcune istruzioni, nello specifico sono for , while e do while , dalla versione 5 di Java è stato introdotto anche il “ciclo for migliorato”. Le condizioni permettono di eseguire o meno un gruppo di istruzioni a seconda se la condizione espressa dal costrutto risulti vera o falsa, sono if e switch. Tutti questi costrutti possono essere annidati. L’ if e il while sono i costrutti storici di Java , vengono chiamati costrutti di programmazione semplici perché la loro sintassi è molto facile da ricordare e implementare.
Il costrutto if
Partiamo dalla condizione if , in fase di esecuzione del codice, la Java Virtual Machine prova l’espressione booleana espressa dall’if , e a seconda che essa risulti vera o falsa esegue il blocco di istruzioni, oppure no. La sintassi è la seguente :
if ( condizione ) istruzione ;
La condizione di solito si avvale di operatori di confronto e di operatori logici. Per esempio :
if ( a = 10) System.out.println(“ a è uguale a 10 “);
Quindi l’ istruzione di stampa (System.out.println(…)), sarebbe eseguita se e solo se la variabile “ a” avesse valore 10. In quel caso la condizione in parentesi varrebbe true , e quindi sarebbe eseguita l’istruzione che segue l’espressione. Se invece l’espressione risultasse false, sarebbe eseguita direttamente la prima eventuale istruzione che segue il costrutto if. Volendo leggere il nostro esempio diremmo che “ se è vero che a è uguale a 10 allora eseguo System.out.println(“ a è uguale a 10 “) in tutti gli altri casi non faccio nulla” . Il costrutto if può essere esteso dalla keyword else modificando la sintassi in questo modo :
if ( condizione ) istruzione1; else istruzione2;
Notiamo dalla sintassi che la condizione di controllo resta la stessa ma le istruzioni sono due, quella espressa dall’ if viene eseguita se la condizione è vera, quella espressa dall’ else viene eseguita se la condizione è falsa. Quindi possiamo modificare l’esempio precedente in questo modo :
if ( a = 10) System.out.println(“ a è uguale a 10 “); else System.out.println(“ a non è uguale a 10”);
Per maggiore chiarezza inseriamo un’intera classe :
public class IfElseDemo{
public static void main (String args[]){
int a , b , c;
a=2;
b=3;
if (a < b)
System.out.println(“a è minore di b “);
if ( a = = b)
System.out.println(“a è uguale a b “);
if (a > b)
System.out.println(“a è maggiore di b “);
c= a - b;
System.out.println(“Valore di c = “ + c);
if( c > = 0)
System.out.println(“c è un numero positivo “);
else
System.out.println(“c è un numero negativo “);
c = b – a;
System.out.println(“Valore di c = “ + c);
if( c > = 0)
System.out.println(“c è un numero positivo “);
else
System.out.println(“c è un numero negativo “);
} }
Il costrutto while
Il ciclo while permette di fare una iterazione , cioè di ripetere un gruppo di istruzioni sempre rispettando la veridicità della condizione espressa tra parentesi tonde. La sintassi è la seguente :
while ( condizione ) istruzione / i;
quindi :
while ( a < = 10 ) { System.out.println(a); a++; }
Nel nostro esempio finché a risulta minore o uguale a 10 viene eseguita la stampa della stessa variabile a per la quale è anche previsto un aumento di valore. Il ciclo while è meno sofisticato di un ciclo for ma in sostanza ne effettua le stesse azioni. Il while è composto da una condizione di controllo e un corpo del ciclo. All’ingresso del ciclo e ogni volta che vengono eseguite le istruzioni del corpo viene verificata la veridicità della condizione. Il ciclo termina solo quando la condizione risulta falsa, in quel caso il while non produce alcun risultato e viene chiuso. Per questo motivo il ciclo while può andare in loop , cioè verrà eseguito infinite volte finché non viene bloccato manualmente. Per maggiore chiarezza inserisco di seguito una classe che potrete eseguire :
public class whileDemo { public static void main (String args [ ] ) { char c ; c = ‘ a ‘; while ( c < = ‘ z ‘){ System.out.println( c ); c + + ; // c = c +1 ; }
} }
Il costrutto do – while
In base a quanto detto sul ciclo while, se l’espressione booleana risultasse subito falsa , il costrutto non produrrebbe nulla. Se vogliamo essere certi di avere un output , quindi che almeno una volta il ciclo produca qualcosa , è possibile utilizzare il ciclo do – while. Seguendo questa sintassi :
do { istruzione / i ; // corpo del ciclo } while ( condizione ) ;
In questo modo viene prima preso in considerazione , quindi eseguito, il corpo del ciclo espresso dal do e poi viene verificata la condizione booleana espressa da while. Dopo aver controllato la condizione se è vera il ciclo continua se è falsa il ciclo si ferma.
Il costrutto for
Il ciclo for è molto utilizzato in Java, quindi è importante conoscerlo bene! Serve per iterare una parte di codice. La sintassi è la seguente :
for ( inizializzazione ; verifica ; incremento ) istruzione ;
quando si devono utilizzare più istruzioni conviene utilizzare un blocco di codice , in questo modo :
for ( inizializzazione ; verifica ; incremento ){ istruzione 1; istruzione 2; istruzione 3; }
Quindi rispetto al ciclo while notiamo che la sintassi può sembrare più complessa ma è sicuramente più completa. Di seguito vi scrivo un esempio che vi chiarirà le funzionalità del ciclo.
public class forDemo{ public static void main(String args[]){ int var; for(var = 0 ; var < 5 ; var = var + 1 ) System.out.println("This is var " + var ); System.out.println("Done!"); } }
In questo esempio la variabile ausiliaria var parte da 0 e viene incrementata aggiungendo una unità ad ogni ciclo non superando il limite imposto dal for. Dimostro in seguito un codice in cui è presente nel ciclo un operatore di decremento, per effettuare un conto alla rovescia :
public class ForDemo {
public static void main(String args[]) {
for (int n = 10; n > 0; n--)
System.out.println(n);
}
}
Dove notiamo che la variabile ausiliaria var parte da 10 ed arriva a 0 decrementandosi di una unità alla volta.
ENHANCED FOR LOOP
Dalla versione 5 di Java è stato introdotto un nuovo costrutto, il ciclo for migliorato “enhanced for loop “ . Il termine migliorato non deve far pensare ad una maggiore
potenza del ciclo rispetto al for , bensì ad una sua versione semplificata. In altri linguaggi di programmazione questo costrutto viene chiamato foreach (per ogni). La sintassi è la seguente :
for(var_temp : ogg_iterabile) istruzione/i;
var_temp corrisponde alla variabile a cui farà riferimento il corpo del ciclo, ogg_iterabile è un oggetto, per esempio un array, dei quali componenti si vuole effettuare una iterazione. Non vi è come nel normale for l’espressione booleana che sta ad indicare la fine del ciclo, questo ci fa capire che il foreach va utilizzato quando si gestisce un oggetto finito. Per esempio : Dato un array:
int arr [] = { 2, 4, 6, 8, 10};
e un ciclo foreach:
for (int tmp : arr) { System.out.println(tmp); }
Inserite queste istruzioni all’interno di una classe eseguibile, l’output sarà la stampa del contenuto dell’array arr.
Il costrutto swicth
Il costrutto switch si presenta come un’alternativa all’if. La sua sintassi è molto più lunga rispetto agli altri costrutti. Viene utilizzato quando è necessario effettuare dei controlli sulla stessa variabile. La sintassi è la seguente :
switch (espressione_costante) { case espressione1: istruzione1; break_opzionale; case espressione2: istruzione2; break_opzionale;
case espressione3: istruzione3; break_opzionale; default: istruzione4; }
Dove per espressione_costante si intende una variabile, solitamente compatibile con un intero (byte , int, short). Le espressioni che seguono i case sono sempre espressioni costanti e devono essere diverse tra loro. Il break_opzionale segna l’uscita dal ciclo, serve quindi per raggruppare i vari case. Il default serve come “ultima spiaggia” viene eseguito se non è stata trovata alcuna corrispondenza nei case precedenti. Quindi possiamo notare che con lo swicth effettuiamo dei controlli sull’ espressione costante al posto di utilizzare una serie di if-else-else… Di seguito ne scrivo un esempio eseguibile per aumentare la chiarezza.
class Switch{
public static void main(String args[]){
int Var= 6;
switch (Var) {
case 1:
System.out.println("Il valore di IntVar è : 1");
break;
case 2:
System.out.println("Il valore di IntVar è : 2");
break;
case 5:
System.out.println("Il valore di IntVar è: 5");
break;
default:
System.out.println("Il valore di IntVar non è 1, ne' 2, ne' 5, ma " +Var);
break;
} } }
L’output atteso sarà l’esecuzione dell’istruzione che segue il default in quanto l’espressione costante, Var , non equivale al valore di alcun case.
ESERCIZIO 1:
Utilizzate il ciclo if all’interno di una classe che stampi se i numeri interi inseriti sono pari o dispari. Di seguito modificate il flusso delle istruzioni inserendo l’if – else.
ESERCIZIO 2 :
Scrivete una classe che stampi le lettere dellâ&#x20AC;&#x2122;alfabeto utilizzando un ciclo while.
ESERCIZIO 3 :
Scrivete un codice in cui attraverso un ciclo for possiate moltiplicare il valore di una variabile.
ESERCIZIO 4 :
In una classe dato un intero x indovinatene il valore attraverso un costrutto swicth.
I METODI I metodi sono porzioni di codice che servono per definire delle operazioni che verranno eseguite. Possiamo dire che con il termine metodo vogliamo dire “azione”. Non come regola ma come convenzione, ogni metodo dovrebbe eseguire un singolo e ben definito compito e l’identificatore del metodo dovrebbe esprimere in modo chiaro il compito che verrà eseguito, i metodi dovrebbero contenere poche istruzioni perché metodi piccoli che eseguono un solo compito sono più facili da testare e correggere dei metodi di grandi dimensioni che eseguono più compiti, in questo caso è meglio dividere tale metodo in moduli ( o metodi) più piccoli. I metodi vengono invocati o chiamati da un oggetto, in questa chiamata al metodo l’oggetto ne deve definire la firma cioè il nome del metodo ed i parametri espressi tra parentesi tonde, in questo modo il metodo restituisce un risultato al metodo chiamante. Senza metodi gli oggetti tra di loro non potrebbero comunicare, possiamo dire che i metodi sono ciò che nei diagrammi dell’UML chiamavamo “messaggi”.
Un metodo viene dichiarato secondo la seguente sintassi :
modificatore tipo di ritorno identificatore (argomenti) {
istruzione/i; }
Per modificatore intendiamo una keyword che influisce sul comportamento del metodo, i più frequenti sono static, private, public… Per tipo di ritorno intendiamo il tipo dei dati che inserirà nel suo risultato. Se il metodo sarà int , il risultato sarà un numero intero, se sarà String il risultato sarà una stringa di caratteri. Se il tipo di ritorno è void vorrà dire che le operazioni del metodo non ritorneranno alcun valore. L’identificatore è il nome del metodo, con il quale verrà riconosciuto nel codice e verrà richiamato dagli oggetti. Per l’identificatore di un metodo valgono le stesse regole per l’identificatore della classe e delle variabili.
Gli argomenti (parametri) sono delle variabili che verranno utilizzate all’interno di un metodo, i parametri possono essere zero o più. Una dichiarazione di più parametri sarà separata da virgole. Le istruzioni costituiscono quello che comunemente viene chiamato corpo del metodo. Ed è la pozione di codice che verrà eseguita quando il metodo sarà invocato da un oggetto.
ESEMPIO 1:
public void stampa(){
System.out.println(“Hello world”);
}
ESEMPIO 2 :
public int somma(int a, int b){
return a + b;
}
Il metodo più importante per un codice Java è il metodo main. Attraverso il main la JVM inizia ad eseguire tutte le righe di codice che ne seguono, in assenza di questo metodo una classe e quindi l’intero codice non potrà essere eseguito, resterà quindi privo di output.
I ciclo di vita dei metodi Java è fatto di due fasi: la dichiarazione e la chiamata. Della dichiarazione ne abbiamo già parlato spiegandone la sintassi, ora ci occupiamo della chiamata.
Un concetto fondamentale, che ci ritornerà utile quando parleremo di overload e override, è la firma del metodo. La firma, in inglese signature, è costituita dal nome del metodo e dai suoi parametri, serve all’oggetto per chiamare il metodo e alla JVM per riconoscere quale metodo mandare in esecuzione. La chiamata di un metodo è molto simile all’accesso alle variabili, viene fatta sempre utilizzando l’operatore “dot” e rispetta la seguente sintassi : nomeOggetto.nomeMetodo();
come
nomeOggetto.nomeVariabile;
Dato un metodo :
public double somma(double a, double b){
return a + b;
}
e dato un oggetto ogg1 , questo chiamerà il metodo secondo la seguente sintassi :
ogg1.somma(2.4 , 5.89);
dove ogg1 come abbiamo detto è il nome dell’oggetto chiamante e somma(2.4 , 5.89) è la firma del metodo chiamato.
A questo punto è doveroso fare una precisazione… un metodo che contiene un return deve essere chiamato all’interno di un metodo di
stampa, come il System.out.println(), altrimenti la JVM non ne stampa il risultato.
System.out.println(ogg1.somma(2.4 , 5.89));
Mentre i metodi void possono essere chiamati allâ&#x20AC;&#x2122;interno del codice senza dover essere collegati ad altri metodi per essere stampati a video.
ESERCIZIO 1 :
Crea una classe e allâ&#x20AC;&#x2122;interno inserisci un metodo con return che effettua il calcolo del consumo di unâ&#x20AC;&#x2122;automobile con un pieno.
ESERCIZIO 2 :
Crea una classe e allâ&#x20AC;&#x2122;interno inserisci un metodo void che effettua la stampa delle variabili espresse nella classe.
I TIPI DI DATI
Il linguaggio di programmazione Java pone delle regole ferree per lâ&#x20AC;&#x2122;utilizzo dei tipi di dati, come del resto per lâ&#x20AC;&#x2122;intera sintassi. Innanzitutto dobbiamo chiederci quali sono questi tipi di dati, ne vengono definiti solamente otto :
1. int, byte, short, long; 2. float , double; 3. char; 4. boolean.
Il primo gruppo definisce tipi di dati interi, il secondo tipi di dati a virgola mobile , il terzo tipo testuale e il quarto tipo logico-booleano.
TIPI DI DATI INTERI Prendiamo in esame il primo gruppo : int, byte, short e long.
Tutti questi tipi di dati definiscono al loro interno dei numeri interi che siano positivi o negativi, ma ovviamente non hanno tutti la stessa funzionalità, infatti hanno intervalli di rappresentazione differenti.
• byte = contiene un numero da 8 bit, da - 128 a + 127; • short = contiene un numero da 2 byte, da - 32.768 a + 32.767; • long = contiene un numero da 8 byte, da - 9.223.372.036.854.775.808 a + 9.223.372.036.854.775.807; • int = contiene un numero da 4 byte, - 2.147.483.648 a + 2.147.483.647.
Possiamo quindi notare che per ogni tipo di dati esiste pari numero di numeri positivi e di numeri negativi. Ogni utilizzo errato degli intervalli dei tipi di dati provocherà un errore di compilazione, ad esempio :
byte b = 120;
SI
byte b = 128;
NO
Bisogna però fare anche molta attenzione all’uso che faremo dei valori assegnati, perché se utilizzo ad esempio :
byte b = 50;
e poi di b ne faccio una moltiplicazione per 2 :
b = b* 2;
il risultato ci darà errore di compilazione, perché trasforma il valore 50 in un tipo dati int e quindi il prodotto della sua moltiplicazione per 2 non combacerà con byte. Questo fenomeno è noto come “promotion”, promozione automatica delle espressioni. Nel nostro esempio anche se il prodotto (100) rientrava nei limiti del tipo dati byte la promotion avviene prima che sia fatta una qualsiasi operazione sui dati.
Questo accade per i tipi di dati binari ed ha risultati differenti a seconda del tipo dati che incontra , per esempio :
• se uno degli operandi è double anche l’altro si trasformerà in double; • se il più grande degli operandi è float anche l’altro si trasformerà in float; • se il più grande degli operandi è long anche l’altro si trasformerà in long; • in tutti gli altri casi gli operandi saranno trasformati in int.
Se però abbiamo la necessità di mantenere il tipo di dati senza che venga effettuata alcuna modifica, possiamo utilizzare il cast, chiamato anche casting. Consiste nello specificare, prima dell’assegnazione o dell’operazione, tra parentesi tonde il tipo dati da mantenere.
b = (byte) b * 2 ;
Altro problema a cui dobbiamo stare attenti è quando sommando due interi, anche solo di una unità , possiamo sforare il limite massimo del tipo dati int, per esempio :
int a = 2147483647 ; int b = 1; int c = a + b; c = - 2147483648; (!!!)
Non produce errori di compilazione, ma assegna il valore piĂš corretto. Anche per la divisione tra interi potremmo avere problemi, in quanto come risultato potremmo avere un numero decimale che verrebbe modificato in intero. Il compilatore interpreterĂ il tipo di dati long sempre come un int, per defalut, senza quindi segnalare errori. Per esempio :
long a = 30000 ;
per il compilatore sarebbe int a = 30000;
Se vogliamo essere precisi dobbiamo utilizzare un cast sul tipo dati long con una sintassi differente da quella spiegata prima, cioè :
long a = 30000L;
Aggiungendo la lettera “elle” , maiuscola o minuscola, dopo il numero. Finché il numero resta nel range di rappresentazione di un int, questo cast è facoltativo, ma se superassimo il range dell’int diventerebbe obbligatorio pena errore di compilazione. Tutti questi accorgimenti e modifiche da parte del compilatore semplicemente perché il tipo dati int è quello maggiormente utilizzato. Per rappresentare un intero possiamo avvalerci delle quattro notazioni utilizzate in informatica :
1. binaria, è la lingua dei computer composta da 0 e 1; 2. ottale, possiamo utilizzare solo i numeri da 0 a 7; 3. esadecimale, abbiamo i numeri da 0 a 9 e le lettere dalla A alla F; 4. decimale, quella che siamo abituati ad utilizzare con i numeri da 0 a 9.
Per lâ&#x20AC;&#x2122;utilizzo di alcune di queste notazioni bisogna avere qualche accortezza , per esempio, per la notazione binaria bisogna anteporre al vero numero uno 0 e la lettera b/B, maiuscola o minuscola non fa differenza alcuna. Per la notazione esadecimale dobbiamo anteporre al numero uno 0 e una x/X anche in questo caso non fa differenza se maiuscola o minuscola. Infine per la notazione ottale si antepone al numero semplicemente uno 0. Per la notazione decimale non è necessario aggiungere nulla.
TIPI DI DATI A VIRGOLA MOBILE
Adesso prendiamo in esame il secondo gruppo : float e double. Anche in questo caso avremo degli intervalli di valori .
• float : 32 bit ; • double : 64 bit.
Casting e promotion esistono anche per i tipi di dati a virgola mobile, il valore preminente è double. Quindi se vogliamo utilizzare il tipo dati float siamo costretti a ricorrere al cast. Nel caso seguente infatti avremmo errore di compilazione :
float a = 2.67 ;
Il cast, come nel tipo dati long, è più breve :
float a = 2.67F;
La lettera “effe” può essere indistintamente sia minuscola che maiuscola. Ne deduciamo che il cast con il tipo di dati double è opzionale. Dalla versione 7 di Java è stata introdotta una novità volta a migliorare la visibilità dei numeri. Per i dati interi ed anche per i dati a virgola mobile, si può ricorrere al separatore “ _ “ (underscore) per suddividere il numero , per esempio :
int a = 1000000000 ;
diventerebbe :
int a = 1_000_000_000 ;
Ovviamente ci sono delle regole da non violare , pena errore di compilazione. Queste regole sono :
• non porre l’underscore all’inizio o alla fine del numero; • non porre l’underscore vicino ad un punto se il numero è decimale ; • non porre l’underscore vicino la lettera “ L “ o “ F “, in caso di cast dei tipi long e float; • e dove ci si aspetta una stringa di caratteri.
TIPO DI DATI LETTERALE
Per questo tipo di dati la nostra scelta può ricadere solo su char. Serve per immagazzinare un carattere alla volta, il valore del tipo dati char può essere un qualsiasi carattere che vedete sulla vostra tastiera, l’importante è che sia supportato dal prompt Dos. Possiamo anche assegnargli un valore Unicode in esadecimale, anteponendo il prefisso “ \u “. Di codifica Unicode esistono tre tipologie :
1. UTF – 8 = a 8 bit che coincide con al ASCII; 2. UTF – 16 = a 16 bit che contiene la maggior parte dei caratteri utilizzati negli alfabeti del mondo; 3. UTF – 32 = a 32 bit contiene tutta la restante parte dei caratteri ,che sono anche quelli meno utilizzati.
In ogni caso il valore della variabile char sarà inserito tra apici, in questo modo :
char car1 = ‘ b ‘ ;
char car2 = ‘ + ‘ ;
char car3 = ‘ @ ‘ ;
char car4 = ‘ 1uABC8 ‘ ;
Essendo i valori char espressi in bit e quindi assumono valori numerici, questo tipo di dati possono essere anche utilizzati nelle operazioni numeriche.
TIPI DI DATI BOOLEANI
Questo tipo di dati rappresenta una scelta tra i valori “ true “ e “ false “ , quindi il tipo dati boolean può immagazzinare solo true e false. La dichiarazione e assegnazione di una variabile di questo tipo segue la sintassi :
boolean a = true ;
boolean b = false ;
ESERCIZIO : Prova ad utilizzare in una classe contenente il metodo main le variabili dei tipi che abbiamo studiato in questa lezione.
IL METODO COSTRUTTORE Il metodo costruttore in Java è un costrutto fondamentale perché viene utilizzato per creare gli oggetti derivanti da una specifica classe. Creare un oggetto, vale a dire istanziare una classe, ha la sintassi simile a quella della dichiarazione di un tipo dati primitivo. Il nome dell’oggetto sarà detto “reference”. La sintassi sarà :
NomeClasse nomeOggetto;
In questo modo l’oggetto viene semplicemente dichiarato, successivamente verrà istanziato con la parola chiave new. Quindi la creazione di un oggetto è fatta di due fasi : la dichiarazione del reference e l’inizializzazione dell’oggetto con new. Queste fasi possono essere inserite all’interno di una stessa istruzione :
nomeClasse nomeOgg = new nomeCostruttore(…) ;
Il metodo costruttore ha le seguenti caratteristiche :
• Assume lo stesso nome della classe a cui appartiene; • Non ritorna nulla al chiamante e quindi non ha tipo di ritorno, non significa che sia void (!!!); • Tutte le classi hanno il proprio metodo costruttore; • Ogni qualvolta una classe crea un oggetto, i metodo, viene chiamato automaticamente.
Date queste caratteristiche è di fondamentale importanza fare attenzione anche alle maiuscole e alle minuscole. Quando viene creato un oggetto automaticamente la classe richiama il suo metodo costruttore, chiamato metodo costruttore di default, a meno che nella suddetta classe non ne sia stato dichiarato uno esplicitamente.
Esaminiamo la sintassi della creazione di un oggetto utilizzando il costruttore di default, che secondo le regole Java non sarà definito esplicitamente nella classe e non avrà parametri. Quindi data una classe :
public class esempio{ int x; int y; }
La costruzione dell’oggetto seguirà questa sintassi :
esempio ogg1 =new esempio();
dove esempio() non è il nome della classe ma la chiamata al metodo costruttore di default. È il compilatore ad aggiungere alla classe questo metodo costruttore anche se il programmatore non lo ha definito. Il metodo costruttore di default “ cessa di esistere ” quando nella classe viene definito esplicitamente un metodo costruttore sia esso con o senza parametri. Nel momento in cui abbiamo la necessità di utilizzare un metodo costruttore esplicito dovremo scriverlo nella classe, in questo modo : data una classe :
class esempio { int var1 ; int var2 ; esempio ( int a, int b) { var1 = a ; var2 = b ;
} }
La creazione dell’oggetto sarà in questo modo :
esempio ogg = new esempio ( 10 , 16 ) ; dove esempio sarà il nome della classe , ogg il reference dell’oggetto , new la parola chiave di cui abbiamo detto in precedenza ed esempio( 10 , 16 ) la chiamata al metodo costruttore con i due parametri interi richiesti nella definizione. L’inserimento degli elementi nella classe non deve necessariamente seguire un ordine predefinito, del tipo variabili, costruttore , metodi… in quanto Java non è un linguaggio di programmazione procedurale, ma trovare un proprio ordine aiuta a non commettere errori ed aumenta la visibilità.
ESERCIZIO 1 :
Crea una classe Veicolo allâ&#x20AC;&#x2122;interno della quale inserisci tre variabili che saranno, a tuo piacere, tre caratteristiche di un veicolo. Nella classe esecutiva, quella con il metodo main, crea due oggetti della classe Veicolo e assegnane le variabili, infine stampa i valori delle variabili per i due oggetti.
ESERCIZIO 2 :
Crea una classe Animale e definisci allâ&#x20AC;&#x2122;interno tre caratteristiche generiche degli animali, a tua scelta. Nella classe Animale definisci e dichiara il metodo costruttore che prende come argomenti le tre variabili definite allâ&#x20AC;&#x2122;inizio. Nella classe esecutiva, quella con il metodo main, definisci tre oggetti e stampane le caratteristiche.
J2EE (Java Enterprise Edition) La Java Platform, Enterprise Edition o Java EE è una piattaforma di calcolo Java. La piattaforma fornisce API e un ambiente di runtime per lo sviluppo e l'esecuzione di software per le imprese, compresi i servizi di rete e web, e di altri grandi applicazioni di rete a più livelli, scalabile, affidabile e sicuro. La Java EE estende la Java Platform, Standard Edition (Java SE). La piattaforma incorpora un design basato in gran parte su componenti modulari in esecuzione su un server di applicazioni. Il software per Java EE è principalmente sviluppato in linguaggio di programmazione Java. La versione enterprise di Java è una piattaforma di sviluppo che viene incontro alle esigenze aziendali. Diventata, con il tempo, sinonimo di sviluppo di applicazioni aziendali robuste, sicure ed efficienti. La tecnologia J2EE può facilitare la creazione di modelli B2B (Business to Business) e B2C (Business to Consumer).
In pratica J2EE è un raccoglitore di tecnologie che facilitano lo sviluppo di software web based distribuito
• Tecnologia Web Application • Tecnologia Web Services • Tecnologia Management and Security
L’espressione Web Application è usata nell'ambito del software engineering, dove con il termine webapp si descrive un'applicazione accessibile via web per mezzo di un network, come ad esempio una intranet o attraverso la Rete Internet. Questo modello applicativo è divenuto piuttosto popolare alla fine degli anni novanta, in considerazione della possibilità per un client generico di accedere a funzioni applicative, utilizzando come terminale normali web browser. Infatti l'opportunità di aggiornare ed evolvere a costo ridotto il proprio applicativo, senza essere costretti a distribuire numerosi aggiornamenti ai propri clienti attraverso supporti fisici, ha reso la soluzione piuttosto popolare per molti produttori software.
PiÚ di recente colossi come Google e Microsoft hanno implementato interi pacchetti applicativi per office automation, tradizionalmente venduti in modo distribuito su supporti CD-ROM, e che ora si stanno velocemente trasformando a tutti gli effetti inwebapps. Quei client finalizzati unicamente alla funzione di collegarsi quali terminale di web-application, vengono chiamati sovente thin client. Sul piano dell'informatica teorica è possibile riconoscere una strutturazione tipica su tre livelli. Nella maggioranza dei casi è infatti possibile identificare un primo livello associabile al terminale di fruizione, il web browser; un secondo livello costituito dal motore applicativo, ovvero un core applicativo, costituito da codice sorgente in un qualche linguaggio di sviluppo dinamico lato-server (per es. PHP, ASP, ASP.NET, un qualche CGI, JSP/Java, ecc.); un terzo livello riconducibile al motore database associato (per es. MySQL, MSSql,Oracle, ecc.). Semplificando, il web browser del client invia le proprie richieste al livello intermedio, ovvero al motore applicativo dinamico del web server, che da una parte interpreta e gestisce le interrogazioni al motore DBMS(Database Management System ) e dall'altra genera il risultato in
un output diretto allo stesso browser, che lo interpreta e lo restituisce all'utente sotto forma di pagine Web.
Un Web Service è una applicazione modulare descritta, pubblicata, individuata ed utilizzata attraverso il web. Essa realizza delle funzioni, che spaziano da una semplice richiesta di informazioni ad un complicato processo commerciale. Un Web Service può essere sfruttato contemporaneamente per più operazioni, essendo condiviso in rete e disponibile a tutti. L'idea fondamentale che sta alla base dei Web services è l'integrazione come servizio. Il concetto rappresenta un set definito di tecnologie basate sugli standard industriali, che lavorano insieme per facilitare l'interoperabilità tra sistemi eterogenei, che siano all'interno della stessa azienda, o risiedano da qualche parte su Internet. Con i Web services si possono abilitare applicazioni al Web, per farle comunicare con altre applicazioni che sono state a loro volta abilitate agli standard dei Web services
Si tratta delle tecnologie legate alla gestione della stessa tecnologia Enterprise per realizzare lâ&#x20AC;&#x2122;accesso e lo scambio di informazioni tra macchine e servizi distribuiti.
Java “ il nuovo linguaggio di programmazione” Dopo aver elencato le caratteristiche della programmazione orientata agli oggetti è finalmente arrivato il momento di dedicarci a Java. E’ infatti un moderno linguaggio di programmazione OO (Object-Oriented). Nasce intorno alla metà degli anni 90 , è creato da James Gosling e altri ingegneri di Sun Microsystems. La Sun MicroSystems è stata acquisita da Oracle nel 2010. E’ stato sviluppato per risolvere i più comuni problemi della programmazione moderna, primi fra tutti la semplicità e la portabilità. Un buon punto di partenza per scoprirne di più è proprio il sito ufficiale: http://www.oracle.com/it/technologies/java/index.html Oggi è usato sia per sviluppare web-app che per sviluppare applicazioni business (molti server per l'e-banking sono basati su tecnologia Java). Collegandosi al sito ufficiale, si scopre che ci sono diversi ambienti e librerie disponibili a seconda del genere di obiettivi che ci siamo preposti nello sviluppo del software. Le 3 edizioni principali sono:
• J2SE: Java 2 Platform – Standard Edition • J2EE: Java 2 Platform – Enterprise Edition • J2ME: Java 2 Platform – Micro Edition
La J2SE è l’ambiente adatto allo sviluppo di applicazioni desktop, contiene le parti basilari della piattaforma Java e trattandosi di una piattaforma non dedicata ad uno specifico settore ne risulta la più versatile delle tre. La J2SE è stata organizzata in due parti fondamentali: Core Java è la parte che contiene le API fondamentali del linguaggio di
programmazione; Desktop Java è utilizzata per creare GUI di applicazioni e/o Applet (programmi Java lato client, eseguiti direttamente nel browser).
Il nostro linguaggio riprende tutte le caratteristiche della programmazione object oriented.
I codici scritti in java vengono eseguiti sulla JVM (Java Virtual Machine) , che è una macchina virtuale indipendente dal sistema operativo. Attraverso l’utilizzo di questa struttura viene eliminata la necessità di usare chiamate di sistema, poiché le chiamate avvengono solo con e attraverso la JVM. Inoltre si garantisce la portabilità assoluta (non importa l'OS, importa solo che sia installata la JVM). Possiamo ora passare alle caratteristiche del linguaggio :
1. Dinamico; 2. Multipiattaforma; 3. Interpretato; 4. Flessibile; 5. Ben organizzato; 6. Modulare; 7. Riusabile.
Con il linguaggio Java si può sfruttare tutta la potenza e la dinamicità della comunicazione client-server. Abbiamo già detto precedentemente che Java è ottimale per la creazione di web-app, cioè di applicazioni distribuite accessibili tramite un browser. Grazie alle sue caratteristiche e alla presenza della JVM il nostro utente ha la possibilità di non sovraccaricare il processore e l’intero sistema di dati e processi legati ad un programma. Infatti questo non si troverà installato, come siamo comunemente abituati, sul nostro sistema ma sarà residente su un server, al quale verrà effettuato l’accesso attraverso un browser. Approfondiremo l’argomento più avanti. Sempre grazie alla macchina virtuale il nostro programma scritto in Java può essere eseguito nella medesima forma su un qualsiasi sistema operativo , che sia Windows , Linux o Mac non ci sarà alcuna differenza, nè di visualizzazione nè di download. Ogni linguaggio di programmazione ha un compilatore specifico anche Java ha il proprio. Anche in questo caso è la JVM a “faticare” per noi. Il compilatore serve per trasformare il codice sorgente, scritto in un qualsiasi linguaggio, in linguaggio macchina (codice binario), perché
altrimenti il sistema non potrebbe leggere e interpretare le istruzioni da noi scritte. Sarebbe come se vi scrivessi in arabo, senza avere un traduttore sarebbe difficile se non impossibile comprenderci. Il compilatore della JVM trasforma il codice sorgente in un metalinguaggio, a metà tra codice sorgente e linguaggio macchina, chiamato bytecode , inserito in un file con estensione .class . La JVM trasforma il bytecode e lo rende leggibile per ogni PC , ecco perché Java è un linguaggio interpretato. L’esecuzione di un file java si suddivide in due fasi : 1. COMPILAZIONE; 2. ESECUZIONE. Durante la compilazione la JVM crea il bytecode dal file contenente il codice sorgente che ha estensione .java. In questa fase, che si attiva con il comando javac nomeFile.java, vengono individuati gli errori di sintassi, ovviamente sta al programmatore correggerli o ancor meglio non commetterli (!). Questi errori non permettono alla JVM di compilare il codice per una mancata correttezza delle norme di programmazione.
Una volta corretti gli errori di sintassi può essere effettuata la fase dellâ&#x20AC;&#x2122;esecuzione, che si attiva con il comando java nomeFile . Anche in questo frangente possono verificarsi dei problemi , noti come errori di semantica chiamati correttamente eccezioni (exception) . Sono eccezioni al codice che sebbene sia scritto correttamente non produce un output logico. Il linguaggio java gestisce molto bene sia errori che eccezioni dando al programmatore un valido aiuto per lâ&#x20AC;&#x2122;individuazione e la correzione degli stessi.
JDBC: Basi di Dati Concetti Fondamentali
Introduzione
Una applicazione che accede (in lettura e/o scrittura) ad una sorgente di dati (nel nostro caso un database relazionale) ha bisogno di fare le seguenti operazioni:
1. aprire una connessione alla sorgente dati 2. inviare attraverso la connessione istruzioni (di interrogazione e aggiornamento) alla sorgente dati 3. processare i risultati ricevuti dalla sorgente dati in risposta alle istruzioni inviate
La tecnologia Java offre indubbiamente tanti vantaggi. Tra questi c'è sicuramente l'interfaccia JDBC che da qualche anno sfida "sfrontatamente" uno standard affermato come ODBC. JDBC infatti, permette alla stessa applicazione di accedere senza modifiche a diversi RDBMS. Ciò implica l'aggiunta ad un'applicazione Java, di per sÊ indipendente dalla piattaforma,
anche l'indipendenza dal database engine. Caliamoci in uno scenario: supponiamo che una società crei un'applicazione Java che utilizza un RDBMS come DB2, lo storico prodotto della IBM. La stessa applicazione gira su diverse piattaforme come server Solaris e client Windows e Linux. Ad un certo punto, per strategie aziendali, i responsabili decidono di sostituire DB2, con un altro RDBMS questa volta di natura free: MySQL. A questo punto, l'unico sforzo da fare, è far migrare i dati da DB2 a MySQL, ma l'applicazione Java, continuerà a funzionare come prima... Il driver ODBC non è appropriato per un uso diretto dal linguaggio Java perché usa interfacce scritte in linguaggio C e una traduzione da API C ODBC in API Java non è raccomandata. Una API Java come JDBC è ottima per permettere una soluzione Java “pura”. ODBC è solitamente usato per applicazioni eterogenee , JDBC è normalmente utilizzato da programmatori Java per connettersi a DB relazionali. Nonostante questo attraverso un piccolo programma “bridge” è possibile
usare l’interfaccia JDBC per accedere a DB accessibili via ODBC.
Il JDBC cos’è…
API standard definita da Sun Microsystems Rappresenta la controparte Java di ODBC È un API Java di connessione a dati residenti in DB relazionali Consiste di un insieme di classi e interfacce scritte nel linguaggio di programmazione Java (package java.sql ) Fornisce ai programmatori uno strumento di sviluppo di tool/DB
Il JDBC cosa fa…
Stabilisce una connessione a un DB Invia istruzioni SQL Processa i risultati
Le sorgenti dati utilizzate da applet Java sono database relazionali, gestiti da un DBMS(Database Management System), ogni DBMS espone una API. Le applicazioni Java interagiscono con le API del DBMS attraverso un driver.
L'architettura di JDBC, così come quella di ODBC, prevede l’utilizzo di un “driver manager”, che espone alle applicazioni un insieme di interfacce standard e si occupa di caricare a “run-time” i driver opportuni per utilizzare gli specifici DBMS. Le applicazioni Java utilizzano le "JDBC API" per interagire con il JDBC driver manager, mentre il driver manager usa le JDBC driver API per parlare con i singoli driver che utilizzano i DBMS specifici. Esiste un driver particolare, il "JDBC-ODBC Bridge", che consente di interfacciarsi con qualsiasi driver ODBC in ambiente Windows.
Tipi di Driver Possiamo scegliere e utilizzare quattro tipologie diverse di driver JDBC caratterizzati da diverse potenzialitĂ e strutture. Il Driver Manager Rappresenta il livello di gestione di JDBC e opera tra lâ&#x20AC;&#x2122;utente e i driver, si occupa di tenere traccia dei driver disponibili e gestisce la creazione di una connessione tra un database e il driver appropriato. La vera e propria comunicazione con il DB viene realizzata dai driver i quali permettono di : Stabilire una connessione con una sorgente di dati Inviare istruzioni di interrogazione e aggiornamento alla sorgente di dati
Processare i risultati
I driver sono :
Type 1 – driver che implementa le API JDBC facendo da ponte per le API del database. Driver di questo tipo, sono generalmente dipendenti dalle librerie native del database e ciò ne limita la portabilità Un esempio è il driver JDBC-ODBC Bridge driver. Questo è Appropriato per utilizzo sperimentale e situazioni nelle quali non è disponibile un altro driver
Type 2 – driver scritto parte in Java e parte in codice nativo del database. Usa una libreria specifica della base di dati ed, anche in questo caso, la portabilità ne risente. Questo driver converte le chiamate JDBC in chiamate per gli altri tipi di database
Type 3 – driver che usa un client Java puro per comunicare con un middleware server usando un protocollo indipendente dal database. Il middleware server poi comunica le richieste del client alla base di dati. Questo driver converte le chiamate JDBC nel protocollo del middleware che vengono poi tradotte per il DBMS. Il middleware provvede alla connettività per molti tipi di DBMS
Type 4 - driver in puro Java che implementa i protocolli standard della rete. Il client si connette direttamente alla base di dati. Questo driver converte le chiamate JDBC nei protocolli di rete usati direttamente dal DBMS Permette chiamate dirette dal client al server. La soluzione più diretta e veloce quando possibile.
Confrontiamo i driver tra di loro… •
Type 1 (Ponte JDBC-ODBC) prestazioni scadenti
non indipendente dalla piattaforma fornito a corredo di SDK â&#x20AC;˘
Type 2 migliori prestazioni non indipendente dalla piattaforma
â&#x20AC;˘
Type 3 client indipendente dalla piattaforma servizi avanzati (caching) architettura complessa
â&#x20AC;˘
Type 4 indipendente dalla piattaforma buone prestazioni scaricabile dinamicamente
Il programmatore ha un compito piuttosto semplice: implementare del codice che sfrutta l'implementazione del vendor, seguendo pochi semplici passi. Un'applicazione JDBC deve:
1. Caricare un driver 2. Aprire una connessione con il database 3. Creare un oggetto Statement per interrogare il database 4. Interagire con il database 5. Gestire i risultati ottenuti
E Esempio JDBC app L’applicazione che mostriamo è costituita da un'unica classe contenente il metodo main, non è di sicuro la soluzione migliore, ma la utilizziamo ugualmente per evidenziare la sequenzialità delle azioni da eseguire. Alla riga 0, viene importato l'intero package java.sql, per poter utilizzare i reference relativi alle interfacce definite in esso, e sfruttando il polimorfismo utilizzarle per puntare ad oggetti istanziati dalle classi che implementano queste interfacce che sono quindi le classi fornite dal vendor. In questo modo l'applicazione non nominerà mai all'interno il nome di una classe
fornita dal vendor, rendendo cosĂŹ l'applicazione indipendente dal database utilizzato. Infatti, gli unici riferimenti espliciti all'implementazione del vendor risiedono all'interno di stringhe, che sono ovviamente facilmente parametrizzabili e modificabili.
0 import java.sql.*; 1 2 public class JDBCApp { 3 public static void main (String args[]) { 4 try { 5 // Carichiamo un driver di tipo 1 (bridge jdbc-odbc) 6 String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; 7 Class.forName(driver); 8 // Creiamo la stringa di connessione 9 String url = "jdbc:odbc:myDataSource"; 10 // Otteniamo una connessione con username e password 11 Connection con = 12 DriverManager.getConnection (url, "myUserName", "myPassword");
Alla riga 7, utilizziamo il metodo statico forName() della classe Class (package java.lang) per caricare in memoria l'implementazione del driver JDBC, il cui nome completo viene specificato nella stringa argomento di tale metodo. A questo punto il driver è caricato in memoria e si auto-registra con il DriverManager grazie ad un inizializzatore statico, questo processo è assolutamente trasparente al programmatore.
Tra la riga 9 e la riga 12 viene aperta la connessione al database mediante la definizione di una stringa url, che deve essere disponibile nella documentazione fornita dal vendor (che però ha sempre la stessa struttura = jdbc:subProtocol:subName ), e la chiamata al metodo statico getConnection() sulla class DriverManager.
13 // Creiamo un oggetto Statement per poter interrogare il db 14 Statement cmd = con.createStatement (); 15 // Eseguiamo una query e immagazziniamone i risultati 16 // in un oggetto ResultSet 17 String qry = "SELECT * FROM myTable"; 18 ResultSet res = cmd.executeQuery(qry); 19 // Stampiamone i risultati riga per riga 20 while (res.next()) { 21 System.out.println(res.getString("columnName1")); 22 System.out.println(res.getString("columnName2")); 23 }
Alla riga 14 viene creato un oggetto Statement che servirĂ da involucro per trasportare le eventuali interrogazioni o aggiornamenti al database. Per comunicare direttamente con il database. Tra la riga 17 e la riga 18 viene formattata una query SQL in una stringa chiamata qry, eseguita sul database, ed immagazzinati i risultati all'interno di un oggetto ResultSet. Quest'ultimo corrisponde ad una tabella formata da
righe e colonne dove è possibile visualizzare i risultati facendo scorrere un puntatore tra le varie righe tramite il metodo next(). Infatti tra la riga 20 e 23, un ciclo while chiama ripetutamente il metodo next(), il quale restituirà false se non esiste alcuna riga successiva a cui accedere. Quindi, fin quando ci sono righe da estrarre vengono stampati a video i risultati presenti nelle colonne chiamate columnName1 e columnName2.
24 res.close(); 25 cmd.close(); 26 con.close(); 27 } catch (SQLException e) { 28 e.printStackTrace(); 29 } catch (ClassNotFoundException e) { 30 e.printStackTrace(); 31 } 32 } 33 }
Tra la riga 24 e la riga 26 vengono chiusi il ResultSet res, lo Statement cmd, e la Connection con. Tra la riga 27 e la riga 31 vengono gestite le eccezioni SQLException (la quale viene lanciata da qualsiasi problema con JDBC, da una connessione non possibile, ad una query SQL errata) e ClassNotFoundException (la quale
viene lanciata nel caso fallisca il caricamento del driver mediante il metodo forName()). L’applicazione in oggetto è utilizzabile solo se si dispone di una fonte dati ODBC installata per il database che si vuole utilizzare (solo se si utilizza un driver del tipo bridge jdbc-odbc), e sostituire alcune stringhe così come segue : • Riga 6: è possibile assegnare alla stringa driver il nome di un altro driver disponibile; • Riga 9: è possibile assegnare alla stringa url il nome di un'altra stringa di connessione (dipende dal driver JDBC del database utilizzato e si legge dalla documentazione del driver); • Riga 12: è possibile sostituire le stringhe myUserName e my Password rispettivamente con la username e la password per accedere alla fonte dei dati. Se non esistono username e password per la fonte dati in questione, basterà utilizzare il metodo DriverManager.getConnection(url);
• Riga 17: sostituire nella stringa myTable con il nome di una tabella valida; • Righe 21 e 22: sostituire le stringhe columnName1 e columnName2 con nomi di colonne valide per la tabella in questione.
Le classi e le interfacce fondamentali JDBC : BC • Package java.sql che va sempre importato;
• Classe DriverManager;
• Interfaccia Driver;
• Interfaccia Connection;
• Interfaccia PreparedStatement;
• Interfaccia ResultSet;
• Interfaccia CallableStatement.
Esaminiamo le interfacce e le classi : Interfaccia Driver : rappresenta il punto di partenza per ottenere una connessione a un DBMS. I produttori di driver JDBC implementano l’interfaccia Driver (richiamando l’opportuna classe) affinché possa funzionare con un tipo particolare di DBMS. Avendo a disposizione un oggetto Driver è possibile ottenere la connessione al database. Ogni driver JDBC ha una stringa di connessione che riconosce nella sintassi: jdbc:product_name:database_alias in cui database_alias specifica il DB specifico a cui connettersi.
Classe DriverManager: facilita la gestione di oggetti di tipo Driver e consente la connessione con il DBMS. Nel momento in cui un oggetto Driver viene istanziato viene automaticamente registrato nella classe DriverManager.
Interfaccia Connection: un oggetto di tipo Connection rappresenta una connessione attiva a un database. Lâ&#x20AC;&#x2122;interfaccia mette a disposizione un insieme di metodi che permettono di formulare query SQL da inviare al DBMS tramite gli oggetti Statement, PreparedStatement o CallableStatement.
Interfaccia Statement: un oggetto di tipo Statement viene utilizzato per inviare query SQL semplici, ovvero che non fanno uso di parametri, verso il DBMS (una query può comprendere: UPDATE, INSERT, CREATE o SELECT).
Interfaccia PreparedStatement: un oggetto di tipo PreparedStatement viene utilizzato per creare query parametriche precompilate che vengono
chiamate “prepared”. Il valore di ciascun parametro non è specificato nel momento in cui lo statement SQL è definito, ma rimpiazzato dal carattere ‘?’.
Interfaccia CallableStatement: un oggetto di tipo CallableStatement viene usato per costruire query parametriche con parametri di input e output. Consente di eseguire una stored procedure memorizzata sul server.
Interfaccia ResultSet: un oggetto ResultSet è il risultato di una query di selezione è di fatto una tabella composta da righe e colonne.
I passi principali • Importazione package • Registrazione driver JDBC • Apertura connessione al DB (Connection) • Creazione oggetto Statement
• Esecuzione query e restituzione oggetto ResultSet • Utilizzo risultati • Chiusura oggetto/i ResultSet e oggetto Statement • Chiusura connessione
Caricare il Driver Per caricare un driver bisogna creare un oggetto della classe Driver, con la seguente sintassi : Driver d = new org.postgresql.Driver(); In seguito bisogna registrare il suddetto driver sul DriverManager, con la seguente sintassi : DriverManager.registerDriver(d); Fatte queste operazioni il driver è disponibile.
Connessione Per ottenere una connessione dal DriverManager è necessario specificare: host, dbms, db utente e password URI (“indirizzo” completo) della connessione specifica server, porta e database Sintassi jdbc:<sottoprotocollo>:<parametri> <subprotocol> si riferisce al driver <subname> si riferisce alla sorgente dati
Creazione della connessione Connection DriverManager.getConnection(String uri, String utente, String password); Esempio Connection connection;
connection = DriverManager.getConnection("jdbc:postgresql:university" , “postgres", "postgres" ); Attenzione: ottenere una connessione è un’operazione costosa creare troppe connessioni comporta problemi di prestazioni.
Istruzione SQL Vediamo ora il codice JDBC che esegue istruzioni SQL per: • salvare oggetti nel db; • cancellare oggetti dal db; • trovare oggetti dal db. Concentriamoci in particolare sul codice dei singoli metodi, piuttosto che del progetto di tale classe. Per eseguire una istruzione SQL è necessario creare un oggetto della classe che implementa PreparedStatement, creato dall'oggetto Connection invocando il metodo: PreparedStatement prepareStatement(String s);
La stringa s è una istruzione SQL parametrica: i parametri sono indicati con il simbolo â&#x20AC;&#x153; ? â&#x20AC;&#x153;.
ESEMPIO 1
String insert = "insert into students(code, firstname, lastname, birthdate) values (?,?,?,?)"; statement = connection.prepareStatement(insert);
Esempio 2
String delete = "delete from students where code=?"; statement = connection.prepareStatement(delete);
Per le istruzioni SQL i parametri sono assegnati mediante opportuni metodi della classe che implementa PreparedStatement. metodi setXXX (<numPar>, <valore>) un metodo per ogni tipo, il primo argomento corrisponde all'indice del paramentro nella query, il secondo al valore da assegnare al parametro.
Istruzione SQL Una volta assegnati i valori ai parametri, l'istruzione può essere eseguita. E’ necessario distinguere due tipi di operazioni: • aggiornamenti (insert, update, delete) : che modificano lo stato del database • interrogazioni (select) : che non modificano lo stato del database
Aggiornamenti â&#x20AC;˘ insert, delete, update
vengono eseguiti invocando il metodo executeUpdate() sull'oggetto PepareStatement.
ESEMPIO: PreparedStatement statement; String insert = "insert into students(code, firstname, lastname, birthdate) values (?,?,?,?)"; statement = connection.prepareStatement(insert); statement.setString(1, student.getCode()); statement.setString(2, student.getFirstName()); statement.setString(3, student.getLastName()); long secs = student.getBirthDate().getTime()); statement.setDate(4, new java.sql.Date(secs)); statement.executeUpdate();
Interrogazioni
â&#x20AC;˘ select
vengono eseguiti invocando il metodo executeQuery() che ritorna il risultato in un oggetto ResultSet.
ESEMPIO: PreparedStatement statement; String query = "select * from students where code=?"; statement = connection.prepareStatement(query); statement.setInt(1,code); ResultSet result = statement.executeQuery();
Rilascio delle risorse La connessione, statement, ResultSet devono essere sempre chiusi dopo essere stati utilizzati, questa operazione di chiusura corrisponde al rilascio di risorse.
La programmazione Object Oriented
La programmazione orientata agli oggetti (Object Oriented Programming)
La programmazione orientata agli oggetti, Object Oriented Programming , è ormai la tipologia di programmazione più
diffusa. Le origini della Programmazione ad Oggetti sono abbastanza remote: i primi linguaggi "ad oggetti" furono il
SIMULA I e il SIMULA 67, sviluppati da Ole-Johan Dahl e
Kristen Nygaard nei primi anni '60 presso il Norwegian Computing
Center.
Entrambi questi linguaggi adottavano già parecchie delle peculiarità
che
sottoclassi
e
sono
oggi
tra
i
capisaldi
della
programmazione ad oggetti, come ad esempio le classi, le le
funzioni
virtuali
ma,
a
dispetto
dell'importanza storica, tali linguaggi non riscossero particolare
successo.
Negli anni '70 fu introdotto il linguaggio SmallTalk, considerato da molti il primo vero linguaggio ad oggetti
1
Capitolo 2
"puro". Sempre negli anni '70 riscuoteva enorme successo anche il linguaggio C, grazie alle sue potenzialità e, soprattutto, al fatto che il famoso sistema operativo Unix
(ancora oggi fortemente usato) fosse stato scritto
utilizzando proprio utilizzando questo linguaggio. Intorno
agli anni '80 si diffuse il linguaggio linguaggio ADA che consacra la programmazione ad oggetti come modello da utilizzare.
Tornando alla storia dei linguaggi orientati agli oggetti c’è
da dire che la vera svolta fu rappresentata dall'avvento del
C++ che rappresenta un linguaggio completamente
autonomo rispetto al C, pur utilizzandone sostanzialmente la sintassi. In particolare, l’introduzione di costrutti quali i
template e le classi rende il C++ un linguaggio multi paradigma, quindi un linguaggio orientato agli oggetti.
Tra i più noti linguaggi di programmazione ad oggetti, citiamo il C++, Java, Delphi, C# e, come detto, il Visual Basic .NET. 2
La programmazione Object Oriented
La programmazione orientata agli oggetti è una filosofia di
programmazione estremamente potente e vicina al
programmatore. Essa consente di creare programmi che sopravvivono alle continue evoluzioni del sistema in cui
sono immersi separando la parte implementativa dalla modellazione del problema. Si basa su una metodologia di
sviluppo di tipo top_down, cioè parte da un punto di vista generale, per scendere più a fondo specializzando la
risoluzione del problema. Gli elementi fondamentali di tale paradigma sono gli oggetti che vengono, in un primo momento definiti, descrivendone le caratteristiche, poi
creati e allocati in memoria ed infine vengono usati in relazione l'uno con l'altro.
La struttura di questa tipologia di approccio innovativo alla programmazione si basa sulla modellazione di entità astratte.
Il polimorfismo è uno dei concetti più importanti della
programmazione object oriented, insieme ad ereditarietà e
incapsulamento. Probabilmente è anche il più difficile da
comprendere e da individuare. Il concetto di polimorfismo 3
Capitolo 2
è relativo proprio al significato della parola ,ovvero avere
più forme (poli-morfo), più aspetti. Per chiarire il concetto
possiamo fare un semplice esempio di applicazione del
polimorfismo nella vita reale: quando facciamo riferimento ad un computer probabilmente useremo lo stesso termine
(computer, appunto) sia per identificare un computer desktop, un portatile o un netbook. Questo tipo di generalizzazione viene effettuata in quanto gli oggetti cui
abbiamo fatto riferimento sostanzialmente effettuano le stesse operazioni... ma queste operazioni vengono fatte da oggetti con forme diverse (polimorfismo).
E’ molto importante conoscere anche cosa c’è stato prima della
programmazione
OO,
citiamo
quindi
la
programmazione non strutturata, procedurale e modulare.
La programmazione non strutturata
4
La programmazione Object Oriented
Con la programmazione non strutturata il programma è
costituito da un unico blocco di codice detto "main" dentro il quale vengono manipolati i dati in maniera totalmente
sequenziale. Tutti i dati sono rappresentati soltanto da variabili di tipo globale, ovvero visibili da ogni parte del programma ed allocate in memoria per tutto il tempo che il programma stesso rimane in esecuzione.
Contesto limitato e pieno di svantaggiâ&#x20AC;Ś Ad esempio, sarĂ facile incappare in spezzoni di codice ridondanti o ripetuti
che non faranno altro che rendere presto ingestibile ed
"illeggibile" il codice, causando oltretutto un enorme spreco di risorse di sistema.
Programma main + dati
5
Capitolo 2
La programmazione procedurale
Un notevole passo in avanti, rispetto alla Programmazione Non
Strutturata,
venne
fatto
con
l'avvento
della
Programmazione Procedurale. Il concetto base qui è quello di raggruppare i pezzi di programma ripetuti in porzioni di
codice utilizzabili e richiamabili ogni volta che se ne presenti
l'esigenza:
nascevano
le
Procedure.
Ogni
procedura può essere vista come un sottoprogramma che svolge una ben determinata funzione (ad esempio, il
calcolo della radice quadrata di un numero) e che è visibile
e richiamabile dal resto del codice. Inoltre ogni procedura ha la capacità di poter utilizzare uno o più parametri che ne
consentono una maggiore duttilità. Un programma è visto come una sequenza di istruzioni. Linguaggi procedurali sono : FORTRAN, BASIC, COBOL, PASCAL, C. 6
La programmazione Object Oriented
Il grande vantaggio della programmazione procedurale
deriva dal fatto che se una procedura è corretta allora vi è la certezza che essa restituirà risultati corretti.
Procedura 1 main Procedura 2
Procedura
Dati
7
Capitolo 2
La programmazione modulare
La programmazione modulare rappresenta un'ulteriore
conquista. Sorgeva l'esigenza di poter riutilizzare le
procedure messe a disposizione da un programma in modo che anche altri programmi ne potessero trarre vantaggio.
CosĂŹ, l'idea fu quella di raggruppare le procedure aventi un dominio comune (ad esempio, procedure che eseguissero
operazioni matematiche) in moduli separati. Quando sentiamo parlare di librerie di programmi, in sostanza si fa
riferimento proprio a moduli di codice indipendenti che ben si prestano ad essere inglobati in svariati programmi.
Il risultato, dunque, adesso è che un singolo programma
non è piÚ costituito da un solo file in cui troviamo il main e tutte le svariate procedure ma da diversi moduli, uno per il 8
La programmazione Object Oriented
main e tanti altri quanti sono i moduli a cui il programma fa riferimento.
Main + dati
MODULO 1
MODULO 2
Dati + Dati 1
Dati + Dati 2
Procedura 1
Procedura 2
Procedura 3
9
Capitolo 2
La programmazione ad oggetti
La programmazione ad oggetti è un paradigma di programmazione che permette di creare e definire oggetti
in grado di interagire gli uni con gli altri attraverso lo scambio
di
messaggi.
Questi
oggetti
interagiscono
vicendevolmente. Ă&#x2C6; particolarmente adatta nei contesti in cui si possono definire delle relazioni di interdipendenza tra i concetti da modellare.
metodo
Dati
10
Dati
metodo
Dati
La programmazione Object Oriented
I
vantaggi
di
questa
particolare
programmazione sono tanti tra cui:
tipologia
di
• fornisce un supporto naturale alla modellazione
software degli oggetti del mondo reale o del modello astratto
da
riprodurre,
ragionamento logico;
aiutando
un
miglior
• permette una più facile gestione e manutenzione di progetti sia di piccole che di grandi dimensioni;
• l'organizzazione del codice sotto forma di classi favorisce la modularità e il riuso del codice.
La sua caratteristica più forte è la somiglianza con il comportamento umano ma può anche essere il suo più
11
Capitolo 2
grande
svantaggio,
infatti
potrebbe
portare
il
programmatore a fare quegli errori che , anche in grammatica italiana si chiamano errori di semantica. Come
per esempio “Il gatto parla con la pentola dalla finestra”, questa frase è sintatticamente corretta ma non produce alcun significato.
Ciò che importa non è l'implementazione interna del codice
ma, piuttosto, le caratteristiche e le azioni che un componente software è in grado di svolgere.
Come nel mondo reale…se pensiamo ad un masterizzatore
si conoscono le caratteristiche generali ma quasi nessuno è a conoscenza dei sofisticati meccanismi che ne regolano il funzionamento.
Ci soffermeremo sulle caratteristiche : • Marca ;
• Velocità di scrittura su supporti CD-R ;
• Velocità di scrittura su supporti CD-RW ;
12
• Velocità di lettura .
La programmazione Object Oriented
E sulle possibili azioni : • Scrivi su CD ;
• Leggi CD ;
• Espelli CD . Un oggetto viene inteso come un insieme di servizi dotato
di una parte visibile (interfaccia) e di una parte nascosta. Offre agli altri oggetti, come per esempio i clienti di un
sistema , un insieme di attività , operazioni da svolgere ,
senza che sia nota e/o accessibile la sua organizzazione interna.
Un oggetto possiede stato, funzionamento e identità. La
struttura e il funzionamento di oggetti simili sono definite
nella classe a cui appartengono , di cui, possiamo dire che ne siano istanze. Quando viene creato un oggetto da una
classe si dice che è stata creata un’istanza della classe. I termini istanza e oggetto sono intercambiabili.
13
Capitolo 2
L’insieme dei valori assunti dagli attributi costituisce lo stato dell’oggetto.
L’insieme delle operazioni definiscono il comportamento dell’oggetto.
Gli oggetti sono inizialmente delle entità passive, in un dato momento nell’esecuzione di un programma un solo oggetto è attivo , quindi in esecuzione.
Un oggetto diventa attivo quando riceve l’invocazione di un metodo da parte di un altro oggetto. Mentre l’oggetto
ricevente è attivo, il richiedente è passivo , cioè non in esecuzione, in attesa dei risultati del metodo invocato. Una volta che i risultati sono stati inviati al richiedente, l’oggetto ricevente torna in uno stato passivo ed il richiedente prosegue la sua esecuzione.
Un metodo rappresenta una azione che può essere
compiuta da un oggetto. Una delle domande principali da
porsi quando si vuole creare un oggetto è: "Cosa si vuole che sia in grado di fare?". In linea con i concetti di cui
abbiamo parlato prima, quando ci si pone questa domanda, 14
La programmazione Object Oriented
si sta involontariamente dando per buono il fatto che qualsiasi oggetto sia in qualche modo capace di eseguire delle azioni, trattandolo come se avesse natura umana.
Le proprietà rappresentano i dati dell'oggetto, ovvero le
informazioni su cui i metodi possono eseguire le loro
funzioni. Uno degli errori più comuni che si commette quando si definiscono le proprietà di un oggetto è quello di
associare ad esso quante più proprietà possibili, ritenendo
che questo possa, in qualche modo, rendere migliore la stesura del programma. Al contrario un oggetto per essere ben
definito
deve
contenere
le
proprietà
che,
effettivamente, gli sono utili e non tutte quelle che gli si
potrebbero comunque attribuire. Questa è una regola di
buona programmazione che nasce dall'esigenza di rendere più facile la fase di disegno e quella di debug che al contrario risulterebbero certamente molto più complesse.
Una classe, invece, descrive la struttura interna e il
funzionamento di un oggetto.
Gli oggetti appartenenti a una stessa classe hanno:
15
Capitolo 2
• la stessa rappresentazione interna;
• le stesse operazioni;
• lo stesso funzionamento. Una classe rappresenta, sostanzialmente, una categoria particolare di oggetti e, dal punto di vista della
programmazione, è anche possibile affermare che una classe funge da tipo per un determinato oggetto ad essa appartenente.
La classe è un contenitore di caratteristiche (attributi e
metodi) che compongono la struttura formale attraverso la quale si possono definire nuovi tipi di dati e le relazioni che
intercorrono con le altre classi. E’ appunto composta da
due elementi: gli attributi, o variabili di istanza, che
descrivono i dati della classe e i metodi che possono essere identificati come le azioni effettuate con e attraverso gli 16
La programmazione Object Oriented
attributi, secondo l’utilizzo di uno svariato numero di istruzioni.
Possiamo definire la classe come un modello astratto tramite il quale vengono definite delle proprietà comuni ad
una vasta categoria di oggetti. L'oggetto è la realizzazione del contenuto della classe da cui deriva. Si inizializzano gli
attributi e si carica il codice dei suoi metodi in accordo al
modello della classe di appartenenza. Gli oggetti che
appartengono ad una stessa classe hanno comuni
proprietà, essi fanno una copia per se stessi delle variabili che possono essere gestite in maniera differente da ogni
singolo oggetto. Quindi tra di loro sono indipendenti e distinti! Per esempio se si considerasse la class persona su
ogni oggetto persona si potrebbe distinguere il colore dei capelli, l'altezza o il peso.
Data una classe è possibile costruire una nuova classe che sia un'estensione della prima senza dover ridefinire gli
attributi e i metodi già implementati nella prima attraverso 17
Capitolo 2
il meccanismo della ereditarietà. Questa nuova classe è chiamata "classe derivata" o "sottoclasse" della classe da
cui eredita le sue caratteristiche. In pratica la sottoclasse ha i propri metodi e i propri attributi ed in più eredita quelli della sua superclasse e ciò avviene per tutte le
ulteriori sottoclassi. Per fare un esempio, si può immaginare di rappresentare con una classe il concetto di
cane ed avere come sottoclasse il concetto di cane labrador. Questa tecnica è rappresentativa di come la
programmazione orientata agli oggetti si sviluppa in maniera top down dal concetto più generico al più
specifico. E’ anche possibile limitare l'accesso alle variabili
e metodi mediante l'utilizzo degli specificatori d’accesso pubblico-privato-protetto-package.
18
LE VARIABILI Le variabili in Java permettono di immagazzinare dati. Sono delle precise locazioni di memoria nelle quali vengono salvati i dati. Il nome della variabile è l’indirizzo attraverso il quale se ne ottiene il valore. Per esempio una variabile int in memoria occupa 32 bit. Il ciclo di vita di una variabile è fatto di due fasi : dichiarazione e assegnazione. Durante la fase della dichiarazione definiamo l’esistenza della variabile e ne diamo un tipo dati e un nome. Durante la fase dell’assegnazione ne colleghiamo un valore in correlazione con il tipo dati. L’assegnazione può essere ripetuta più volte.
La dichiarazione ha la seguente sintassi :
modificatore tipoDati nomeVariabile;
Il modificatore è un keyword che modifica le caratteristiche della variabile, può essere public, private , protected, etc… Il tipo di dato rispecchia il valore della variabile, per esempio se una variabile è dichiarata come int il suo valore sarà un numero intero. Il nome della variabile la identifica nel codice, per questo identificatore valgono le stesse regole degli identificatori delle classi e dei metodi.
L’assegnazione ha la seguente sintassi :
nomeVariabile = valore;
Il valore della variabile deve rispecchiare i tipo di dati espresso nella sua dichiarazione.
ESEMPIO :
int var1; var1 = 20;
Nel linguaggio di programmazione Java esistono tre tipologie di variabile, variabile d’istanza, variabile locale e parametri formali.
LE VARIABILI D’ISTANZA Partiamo dal presupposto che non è possibile dichiarare una variabile al di fuori dei limiti di una classe. Quindi una variabile d’istanza è la variabile dichiarata all’interno di una classe, ma non all’interno di un metodo. Sono gli attributi della classe. Restano allocate in memoria finché non è “in vita” l’oggetto della classe di cui ne sono istanza, quando l’oggetto
“muore “ e quindi termina il flusso dell’applicazione vengono deallocate dalla memoria. Come variabili d’istanza possiamo anche dichiarare delle costanti . Le costanti riportano i concetti di base delle variabili ma non cambiano il valore. Come possiamo capire dal nome, una costante mantiene lo stesso valore per tutto il suo ciclo di vita.
Vengono dichiarate con la seguente sintassi :
final int var2; var2= 30;
Possiamo notare che la sintassi della dichiarazione di una costante si equivale a quella di una variabile con l’aggiunta di final .
La keyword final indica alla JVM che il valore della costante var2 non potrà più essere variato durante l'esecuzione del programma. Se il valore viene modificato intercorreremo in un errore di compilazione.
LE VARIABILI LOCALI
Al contrario delle variabili d’istanza , le variabili locali sono tutte quelle variabili che vengono dichiarate ed utilizzate all'interno dei metodi di una classe. Possono essere chiamate anche temporanee o automatiche o di stack. Essendo legate alla dichiarazione di un metodo, esse termineranno di esistere una volta terminato tale metodo. Quindi la visibilità delle variabili locali è relativa al metodo nel quale vengono dichiarate. Se scrivessi un altro metodo all'interno della classe e facessi riferimento alla variabile "c" riceverei un messaggio di errore del compilatore che richiede di dichiarare la variabile perché, per quel metodo, di fatto non esiste.
ESEMPIO : public int somma (int a, int b) { c = a + b; return c; }
Nell’esempio c è una variabile locale.
PARAMETRI FORMALI
I parametri formali sono le variabili contenute nelle parentesi tonde dopo l’identificatore di un metodo, vengono chiamati anche parametri o
argomenti. Sono quindi definiti nella firma del metodo. I parametri vengono inizializzati quando viene chiamato il metodo. Questi parametri, dentro il metodo, vengono trattati come delle variabili, la cui visibilitĂ termina alla fine del metodo stesso.
ESEMPIO : public int somma (int a, int b) {
return a+ b;
}
Nel codice in esempio int a e int b sono parametri formali.
STRING , LE STRINGHE DI CARATTERI Una stringa per definizione è una sequenza di caratteri. Nel linguaggio java non esiste un tipo dati primitivo String ma una classe. Quindi non sono variabili ma oggetti e vanno trattati come tali. Ogni oggetto String ha un valore che viene racchiuso tra virgolette. La sintassi per la creazione di una stringa è la seguente : String ogg=new String(“Hello world !!!”); Dove ogg è il nome della stringa e “Hello world !!!” è il suo valore. Tutte le operazioni su questi oggetti vengono effettuate usando i metodi della suddetta class String che scopriremo. La classe String include i metodi per esaminare singoli caratteri espressi nella sequenza, per il confronto di stringhe, per l'estrazione di sottostringhe, e per la creazione di una copia di una stringa con tutti i caratteri tradotti in maiuscolo o in minuscolo. I metodi che esamineremo sono quattro :
• length(), • charAt(..), • substring(..), • equals(..).
Il metodo length() restituisce un intero contenente la lunghezza della stringa. Il metodo charAt(..) stampa a video il singolo carattere espresso nel suo parametro. Il metodo substring() stampa a video una sottostringa facendo riferimento sempre ai parametri. Il metodo equals() è un metodo boolean che ci restituisce valore true o false dopo aver paragonato la stringa richiamante con il parametro del metodo. Ricordiamo che questi quattro metodi non sono void ma hanno un return e per visualizzarne il risultato devono essere inseriti in un metodo per la stampa. Facciamo degli esempi :
str1.length();
str1.charAt(4); str1.substring(3,6); str1.equals(str2);
ATTENZIONE in un oggetto di questo tipo come valore sono intesi anche spazi vuoti e segni di interpunzione. I valori che vengono associati alle stringhe non possono essere successivamente modificati in quanto gli oggetti String sono costanti. Quando si stampano piĂš stringhe allâ&#x20AC;&#x2122;interno di un metodo di sistema come per esempio il System.out.println(), queste devono essere concatenate utilizzando lâ&#x20AC;&#x2122;operatore + . Dal seguente link possiamo accedere alla lista dei metodi della classe String : http://docs.oracle.com/javase/6/docs/api/java/lang/String.html.
ESERCIZIO : Create tre oggetti String di cui lâ&#x20AC;&#x2122;ultimo deve essere equivalente al primo, i tre oggetti devono eseguire i quattro metodi spiegati in questo capitolo.