Programmazione Java

Page 1

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’ <<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’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’uso della macchinetta del self-service.

ESERCIZIO 2

Creare un activity diagram sul funzionamento del software dell’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’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’ 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’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’ 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’eccezione throw new CompraException(); } //metodo che compra il posto // se non viene lanciata l’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’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’indice dell’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’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’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’interno del codice senza dover essere collegati ad altri metodi per essere stampati a video.

ESERCIZIO 1 :

Crea una classe e all’interno inserisci un metodo con return che effettua il calcolo del consumo di un’automobile con un pieno.


ESERCIZIO 2 :

Crea una classe e all’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’utilizzo dei tipi di dati, come del resto per l’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’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’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’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’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’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’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’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’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 •

Type 2 migliori prestazioni non indipendente dalla piattaforma

•

Type 3 client indipendente dalla piattaforma servizi avanzati (caching) architettura complessa

•

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’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 “ ? “.

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


• 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‌ 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. Ăˆ 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’interno di un metodo di sistema come per esempio il System.out.println(), queste devono essere concatenate utilizzando l’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’ultimo deve essere equivalente al primo, i tre oggetti devono eseguire i quattro metodi spiegati in questo capitolo.


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.