Il 0% ha trovato utile questo documento (0 voti)
17 visualizzazioni293 pagine

Libro v2

Caricato da

naminanami231
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
17 visualizzazioni293 pagine

Libro v2

Caricato da

naminanami231
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 293

See discussions, stats, and author profiles for this publication at: https://www.researchgate.

net/publication/344569853

Dall'idea al codice con UML 2 Guida all'utilizzo di UML attraverso esempi

Method · October 2020

CITATIONS
0

3 authors, including:

Luigi Lavazza
Università degli Studi dell'Insubria
188 PUBLICATIONS 2,183 CITATIONS

SEE PROFILE

Some of the authors of this publication are also working on these related projects:

Defining Thresholds for Software Faultiness Estimation View project

All content following this page was uploaded by Luigi Lavazza on 09 October 2020.

The user has requested enhancement of the downloaded file.


Luciano Baresi
Luigi Lavazza
Massimiliano Pianciamore

Dall’idea al codice con UML 2


Guida all’utilizzo di UML attraverso esempi
Page i

Le informazioni contenute in questo libro sono state verificate e docu-


mentate con la massima cura possibile. Nessuna responsabilità derivan-
te dal loro utilizzo potrà venire imputata agli autori o a ogni persona e
società coinvolta nella creazione, produzione e distribuzione di questo
libro.

Questo libro è soggetto alla licenza Creative Commons “Attribuzione -


NonCommerciale - NonOpereDerivate 4.0 Internazionale” 1 .

Chiunque è libero di condividere, riprodurre, distribuire, comunicare


al pubblico, esporre in pubblico, rappresentare, eseguire e recitare que-
sto materiale con qualsiasi mezzo e formato. Il licenziante non può revo-
care questi diritti fintanto che i termini della licenza sottoriportati siano
rispettati. Alle seguenti condizioni:

Attribuzione È obbligatorio indicare la paternità del materiale, fornire


un link alla licenza e indicare se sono state effettuate delle modi-
fiche. Si può fare ciò in qualunque modo ragionevole, ma non in un
modo che suggesrisca che il licenziante avalli chi pubblica il mate-
riale o l’utilizzo del materiale.

NonCommerciale Non si può utilizzare il materiale per scopi commer-


ciali.

Non opere derivate Non si può distribuire il materiale modificato.

Divieto di restrizioni aggiuntive Non si possono applicare termini legali


o misure tecnologiche che impongano ad altri soggetti dei vincoli
giuridici su quanto la licenza consente loro di fare.

Tutti i marchi citati nel testo sono di proprietà dei loro detentori.

1
https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.it
Page ii

Prefazioni

Prefazione originale del 2006

Perché un altro libro su UML? Sicuramente questa è la prima doman-


da che potrebbe sorgere guardando la copertina del nostro. I libri sul-
l’argomento sono già molti —forse anche troppi— e la capacità di potersi
distinguere dagli altri è fondamentale.
Questo testo non nasce con l’idea di far leva sulla nuova versione 2
di UML e, quindi, con la volontà di presentare ogni nuovo dettaglio del
linguaggio. Troppo spesso, chi insegna UML, ma anche molti libri che
vorrebbero spiegarlo, si perdono nei dettagli; introducono concetti e pre-
cisazioni che non servono all’utente comune. Il progettista software vor-
rebbe avere una notazione chiara e utilizzabile in modo semplice, ma ri-
goroso, per modellare il proprio software e per comunicare le proprie de-
cisioni. Prima di poter fare questo, però, è necessario che il progettista
sappia come usare la notazione. Dopo aver letto libri sull’argomento, e
dopo aver assistito a corsi specialistici, è fondamentale che sappia vin-
cere “la sindrome da foglio bianco” e possa modellare le proprie idee in
modo corretto con UML.
Il libro nasce dall’esperienza di anni di insegnamento di UML in corsi
universitari e in corsi di aggiornamento per le maggiori aziende italiane
del settore. Molto spesso, dopo aver introdotto i diversi aspetti di UML,
i suoi molteplici modelli e le sottigliezze sintattiche, ci siamo sentiti di-
re: “... e adesso? Come facciamo a realizzare un buon progetto UML che
rappresenti le nostre idee?”. In questi corsi, ci siamo prodigati per spie-
gare che la conoscenza (sintattica) non deve essere fine a se stessa e che
troppi dettagli possono diminuire la leggibilità dei diagrammi prodotti. È
più importante acquisire un metodo e saper realizzare dei modelli UML
che catturino gli elementi essenziali del progetto, ma che al tempo stesso
mantengano la loro leggibilità, e quindi la loro usabilità.
Per scrivere un buon libro —e speriamo che questo possa esserlo—
non basta conoscere l’italiano, ma gli autori devono anche saper dar for-
ma alle loro idee, saperle presentare in modo ordinato e semplice, e sa-
per fornire delle soluzioni al lettore. Lo stesso vale per un buon proget-
to UML: la conoscenza del linguaggio è fondamentale, ma non è l’unico
elemento; anzi in molti casi gli strumenti CASE oggi disponibili aiutano
Page iii

il progettista nella scelta degli elementi della notazione. Il problema re-


sta il metodo: quali elementi usare e quando, come utilizzare UML per
risolvere problemi tipici di progettazione e come strutturare un progetto
complesso.
Il libro nasce con l’obiettivo di insegnare a progettare con UML. Non
vogliamo proporre l’ennesimo testo in cui si insegna UML, piuttosto in
cui si insegna a progettare con UML. Considerando le particolarità del
software, rispetto ad altri prodotti ingegneristici, non esiste un’unica ti-
pologia di prodotto e quindi un unico approccio alla progettazione. Per
queste ragioni, il libro presenta il problema a diversi livelli d’astrazione,
partendo dal progetto inteso come specifica dei requisiti per arrivare a un
prodotto che è molto vicino al codice che verrà realizzato. Per comincia-
re, si inizia con problemi, e quindi progetti, di dimensioni limitate, per
arrivare alla fine a uno completo di dimensioni reali.
Il libro è diviso in cinque parti. Il primo capitolo presenta gli elementi
principali di UML 2 con l’obiettivo di riassumere la notazione e descrive-
re quanto verrà poi usato negli altri capitoli. Il secondo capitolo analiz-
za un insieme di problemi semplici per familiarizzare con la notazione e
anche per iniziare a delineare le alternative progettuali che verranno uti-
lizzate nel seguito. L’uso di esempi ed esercizi semplici consente al let-
tore di concentrarsi sulla soluzione senza dover gestire la complessità del
problema stesso. Il terzo capitolo studia l’uso di UML per la definizione
e l’analisi dei requisiti. In un processo di produzione reale, non è pen-
sabile analizzare il problema da risolvere a un unico livello d’astrazione;
solitamente l’analisi dei requisiti è il primo livello a cui si inizia a “proget-
tare” il software. Il quarto capitolo, invece, si interessa di progettazione
in senso stretto e presenta soluzioni e alternative per un buon progetto
UML. Infine, il quinto capitolo riassume tutti gli elementi progettuali pre-
sentati proponendo un progetto completo e analizzando le diverse scelte
progettuali e implementative.
Il CD-ROM distribuito con il libro contiene i file d’installazione di uno
strumento CASE UML. La scelta è caduta su Poseidon, Community Edi-
tion, di Gentleware2 per la sua semplicità d’uso e la copertura della nuova
versione di UML, ma anche per le politiche commerciali dell’azienda che
ci consentono di distribuire lo strumento gratuitamente. Riteniamo, in-
fatti, che l’uso di uno strumento sia fondamentale per la progettazione di
software di qualità e anche per poter apprendere l’uso di una notazione
attraverso uno strumento reale e di larga diffusione. Spesso, purtroppo,
2
Attualmente, mentre stiamo scrivendo il libro, la versione disponibile è la 4.1.
Page iv

esistono delle discrepanze tra quanto definito nei documenti ufficiali e


quanto offerto dagli strumenti a causa della relativa novità di UML 2. IL
CD-ROM contiene anche alcuni modelli fatti con Poseidon degli esempi
presentati nel testo, tutti i diagrammi usati nei diversi capitoli e la docu-
mentazione ufficiale di UML 2 dell’OMG (Object Management Group),
l’organo ufficiale preposto alla standardizzazione del linguaggio. Questi
documenti non sono solitamente di accesso immediato e facile, quindi
non consigliamo al lettore di far riferimento a questa documentazione
per ogni dubbio o problema con UML. I documenti ufficiali descrivono
tutti gli aspetti della notazione, dando quindi un’idea precisa della com-
plessità di UML. Questi sono anche un riferimento inappellabile per avere
quelle risposte che i testi su UML normalmente non forniscono.
Riteniamo che il testo, cosı̀ strutturato e corredato degli strumenti di
supporto, possa essere d’ausilio sia per il professionista dell’informatica
che deve iniziare a “pensare in UML” sia per lo studente che frequenta cor-
si di ingegneria del software o di progettazione di software a oggetti. Nei
due casi, il libro vorrebbe essere un valido manuale di progettazione per
imparare a progettare, ma anche per ritrovare alcune soluzioni o metodi
ricorrenti che possono diventare facilmente parte del bagaglio culturale
del lettore.
Prima di concludere, vogliamo caldamente ringraziare Pearson Edu-
cation Italia, per avere accettato di pubblicare il nostro lavoro, e in par-
ticolare Alessandra Piccardo e Micaela Guerra, per l’entusiasmo con cui
hanno creduto in noi e per la pazienza dimostrata, e Federica Sonzogno,
copy editor, per il meticoloso lavoro di revisione del manoscritto. Senza
il loro lavoro, ma soprattutto senza il loro aiuto e i loro preziosi consigli,
questo libro non esisterebbe.

Milano, aprile 2006


Luciano Baresi
Luigi Lavazza
Massimiliano Pianciamore
Page v

Prefazione del 2020

Il libro, originalmente pubblicato da Pearson Education Italia S.r.L.


(ISBN: 88-7192-239-5), fu messo fuori produzione nel 2016. Ultimamente
abbiamo ricevuto diverse richieste di informazioni riguardo al libro, che è
ormai divenuto introvabile, ma che continua a essere consigliato in diver-
si corsi di studi. Abbiamo pertanto deciso di rendere il libro disponibile
con una licenza Creative Commons.
Vogliamo sottolineare che, per svariati motivi, non abbiamo potuto
aggiornare il libro, che è—salvo pochissimi dettagli—identico al libro edi-
to nel 2006. È importante notare che dal 2006 sono state rilasciate nuove
versioni del linguaggio UML, pertanto potrebbero esserci alcune carat-
teristiche delle nuove versioni di UML che vengono ignorate in questo
libro.
Da ultimo, va notato che il libro nell’edizione del 2006 comprendeva
un CD ROM contenente una copia del programma Poseidon, uno stru-
mento per la creazione di diagrammi UML, nonché i diagrammi presen-
tati nel libro, in un formato compatibile con Poseidon. Purtroppo anche
Poseidon ha cessato di essere prodotto e supportato, e di conseguenza
la versione dello strumento contenuta sul CD ROM non è più usabile,
perchè non se ne può ottenere la licenza. Pertanto, abbiamo deciso di
non rendere più disponibile il materiale originariamente contenuto nel
CD ROM, cosa che non compromette l’utilità del libro.
Concludiamo augurandoci di aver fatto cosa gradita a studenti e do-
centi rendendo il libro disponibile nella licenza Creative Commons.

Milano, ottobre 2020


Luciano Baresi
Luigi Lavazza
Massimiliano Pianciamore
Page vii

Indice

1 Introduzione a UML 2 1
1.1 Classi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.1 Interfacce . . . . . . . . . . . . . . . . . . . . . . . . 12
1.1.2 Oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.1.3 Package . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.2 Casi d’uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.3 Interazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.3.1 Sequenze . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.3.2 Comunicazioni . . . . . . . . . . . . . . . . . . . . . 24
1.4 Macchine a stati . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.5 Attività . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.6 Componenti . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.6.1 Strutture composte . . . . . . . . . . . . . . . . . . . 36
1.7 Deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.8 OCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

2 Esercizi introduttivi 45
2.1 Valutazione di polinomi . . . . . . . . . . . . . . . . . . . . 46
2.2 CAD tridimensionale . . . . . . . . . . . . . . . . . . . . . . 48
2.3 Rivendita di auto usate . . . . . . . . . . . . . . . . . . . . . 50
2.4 MyAir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.5 Distributore di merendine . . . . . . . . . . . . . . . . . . . 61
2.6 MegaGym . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.7 Automi a stati finiti . . . . . . . . . . . . . . . . . . . . . . . 71
2.8 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

3 Analisi dei requisiti 81


3.1 Biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.1.1 Requisiti informali . . . . . . . . . . . . . . . . . . . 81
Page viii INDICE

3.1.2 Casi d’uso e scenari . . . . . . . . . . . . . . . . . . . 83


3.1.3 Descrizione dettagliata degli scenari . . . . . . . . . 92
3.1.4 Modello statico . . . . . . . . . . . . . . . . . . . . . 100
3.1.5 Valutazione critica . . . . . . . . . . . . . . . . . . . 108
3.1.6 Qualcosa di più formale . . . . . . . . . . . . . . . . 111
3.1.7 Metodi e specifiche funzionali . . . . . . . . . . . . . 112
3.1.8 Dinamica del sistema . . . . . . . . . . . . . . . . . . 116
3.1.9 Alcune alternative . . . . . . . . . . . . . . . . . . . . 118
3.2 Ascensore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.2.1 Requisiti informali . . . . . . . . . . . . . . . . . . . 124
3.2.2 Casi d’uso e scenari . . . . . . . . . . . . . . . . . . . 126
3.2.3 Modello statico . . . . . . . . . . . . . . . . . . . . . 130
3.2.4 Qualcosa di più formale . . . . . . . . . . . . . . . . 134
3.2.5 Metodi e specifiche funzionali . . . . . . . . . . . . . 136
3.2.6 Dinamica del sistema . . . . . . . . . . . . . . . . . . 137
3.3 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

4 Progettazione 147
4.1 Chat distribuita . . . . . . . . . . . . . . . . . . . . . . . . . 147
4.1.1 Requisiti e modello concettuale . . . . . . . . . . . . 148
4.1.2 Architettura logica . . . . . . . . . . . . . . . . . . . 151
4.1.3 Progetto di dettaglio . . . . . . . . . . . . . . . . . . 173
4.1.4 Progetto di dettaglio con Java RMI . . . . . . . . . . 175
4.2 Controllo degli accessi in un’applicazione web . . . . . . . 183
4.2.1 Casi d’uso e modello concettuale . . . . . . . . . . . 185
4.2.2 Progetto . . . . . . . . . . . . . . . . . . . . . . . . . 192
4.3 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215

5 Battaglia navale in rete 217


5.1 Regole del gioco . . . . . . . . . . . . . . . . . . . . . . . . . 217
5.2 Requisiti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
5.2.1 Dominio del problema . . . . . . . . . . . . . . . . . 221
5.2.2 Responsabilità del sistema . . . . . . . . . . . . . . . 223
5.2.3 Vincoli . . . . . . . . . . . . . . . . . . . . . . . . . . 226
5.2.4 Specifica dei metodi . . . . . . . . . . . . . . . . . . 228
5.2.5 Diagrammi delle attività e di sequenza . . . . . . . . 229
5.2.6 Gioco contro il computer . . . . . . . . . . . . . . . 234
5.3 Progettazione . . . . . . . . . . . . . . . . . . . . . . . . . . 235
5.3.1 Architettura logica del sistema . . . . . . . . . . . . 235
5.3.2 Progetto di dettaglio . . . . . . . . . . . . . . . . . . 239
INDICE Page ix

5.3.3 Giocare in rete . . . . . . . . . . . . . . . . . . . . . . 254


5.4 Codice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
5.4.1 Invarianti e precondizioni . . . . . . . . . . . . . . . 258
5.4.2 Test di unità . . . . . . . . . . . . . . . . . . . . . . . 263
5.4.3 Macchina a stati per la gestione della partita . . . . 267
5.5 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Page 1

Capitolo 1

Introduzione a UML 2

Pensare di scrivere un libro su UML senza introdurne i concetti principali


sarebbe come iniziare a costruire una casa senza pensare alle fondamen-
ta. Ci sembra quindi utile cominciare con un breve capitolo introduttivo
sulla notazione che verrà usata in tutto il libro. Questo non significa che
il lettore digiuno di UML possa usare questo capitolo come unico riferi-
mento. Piuttosto, lo scopo è di fornire una semplice guida che possa es-
sere utile prima di leggere gli altri capitoli per fissare i concetti principali,
ma anche durante la lettura per risolvere dubbi e perplessità.
Il capitolo serve anche per “fare ordine” all’interno di UML 2. In que-
sto caso, ci proponiamo di risolvere due problemi. La nuova versione del-
la notazione introduce alcune novità rilevanti rispetto alle versioni pre-
cedenti e queste devono essere spiegate opportunamente al lettore (che
magari è rimasto a UML 1.5). Inoltre, la notazione è diventata sempre pi
complessa ed è quindi opportuno guidare il lettore tra i diversi diagrammi
offerti, tralasciando gli elementi che reputiamo irrilevanti e fornendo una
chiave di lettura delle diverse possibilità.
Sia per il carattere introduttivo che per la presentazione parziale di
UML 2, questo capitolo non vuole e non può sostituire un libro dedicato
alla presentazione (dettagliata) della notazione. I nostri riferimenti sono
UML Distilled [Fow03], per la sintesi e la chiarezza espositiva, e Poseidon,
per il supporto offerto. Non abbiamo neppure l’ambizione di insegnare la
teoria alla base del software a oggetti, per la quale rimandiamo il lettore a
Object-Oriented Software Construction [Mey00].
UML 2 propone tredici diversi tipi di diagrammi —un’enormità— di-
visi in tre categorie.

• I diagrammi di struttura comprendono i diagrammi delle classi, i


Page 2 CAPITOLO 1. INTRODUZIONE A UML 2

diagrammi degli oggetti, i diagrammi dei componenti, i diagrammi


delle strutture composte, i diagrammi dei package e i diagrammi di
deployment.
• I diagrammi di comportamento includono i diagrammi dei casi d’u-
so, i diagrammi delle attività e i diagrammi delle macchine a stati.
• I diagrammi di interazione sono i diagrammi di sequenza, i dia-
grammi di comunicazione, i diagrammi di temporizzazione e i dia-
grammi di interazione generale.
In questo libro, partiamo con le classi e i diagrammi correlati e de-
scriviamo quasi tutti i diagrammi, fatta eccezione per gli ultimi due tipi
(diagrammi di temporizzazione e di interazione generale) che non rite-
niamo importanti in questo contesto. Alcuni diagrammi sono più com-
plessi di altri perch il numero di alternative e i dettagli variano da caso
a caso, ma la lunghezza del paragrafo relativo non è assolutamente un
indicatore dell’importanza da attribuire al diagramma presentato. In al-
cuni casi, inoltre, non presentiamo tutti i dettagli e gli elementi sintattici
possibili.
Le scelte fatte non hanno alcuna ambizione di generalità, ma rispec-
chiano l’uso della notazione da parte degli autori nella loro attività di
insegnamento e di consulenza e, quindi, nel libro.

1.1 Classi
Le classi sono l’elemento principale di un progetto a oggetti e, di conse-
guenza, il diagramma delle classi è la parte più importante, e più usata, di
UML. Questo diagramma descrive i tipi (le classi) degli oggetti del siste-
ma che vogliamo realizzare e le loro relazioni. Volendo ridurre all’osso un
modello UML, possiamo togliere tutto, ma non il diagramma delle classi.
Una classe (Figura 1.1) si rappresenta con un rettangolo diviso in tre
parti: il riquadro superiore contiene il nome della classe (solitamente in
grassetto), quello centrale i suoi attributi e quello inferiore i suoi metodi, o
operazioni. La figura comprende anche un commento associato alla clas-
se per mezzo di una linea tratteggiata. Anche se il caso presentato è mol-
to semplice, i commenti possono essere aggiunti a qualunque elemento
della notazione.
UML consente di definire gli attributi con diversi gradi di precisione.
Solo il nome è obbligatorio, ma la forma completa è:
CAPITOLO 1. INTRODUZIONE A UML 2 Page 3

Persona
tutte le persone
devono essere nate
- nome: String
dopo l’1/1/1960 - cognome: String
- dataNascita: Date
+ siSposa(p: Persona): boolean
+ compieAnni(d: Date): boolean

Figura 1.1: Una classe UML.

visibilità nome: tipo [molteplicità] = default {stringa di proprietà}

La visibilità specifica se l’attributo è pubblico (+), privato (-), protet-


to (#) o “ristretto” al package (~)1 . Ogni attributo deve avere un nome
univoco all’interno della classe in cui viene definito e può avere un tipo.
La molteplicità definisce il numero minimo e massimo di elementi che
compongono l’attributo. Ha senso porsi il problema nel caso di attributi
composti; spesso la molteplicità è pari a uno e resta implicita. In genera-
le, possiamo usare un intervallo, ma i valori più interessanti sono 0..1 per
indicare un attributo singolo, ma opzionale, e * per indicare un numero
qualsiasi, anche zero, di elementi. Un intervallo, ad esempio 3..5, è cor-
retto e potrebbe aver senso, ma viene usato molto raramente. UML 2 vie-
ta le molteplicità discontinue (per esempio, 2, 4 per dire 2 o 4 elementi)
che erano ammesse in UML 1.5. Con default si definisce il valore iniziale
dell’attributo, mentre la stringa di proprietà consente di aggiungere ca-
ratteristiche specifiche. Ad esempio, potremmo scrivere {readOnly} per
caratterizzare attributi in sola lettura.
Molto spesso gli attributi hanno solo visibilità, nome e tipo, ma se vo-
lessimo completare l’attributo cognome della classe Persona della Figu-
ra 1.1, potremmo scrivere: - cognome: Stringa [1] = "",per dire che
l’attributo cognome è privato (realizzando il principio dell’information hi-
ding ), assume un unico valore di tipo stringa e il suo valore iniziale è la
stringa vuota.
Gli attributi possono essere derivati: è importante segnalare se un cer-
to valore può essere calcolato a partire da altri. Non deve essere una scelta
implementativa per decidere cosa memorizzare e cosa calcolare, ma sem-
1
Questo significa che la visibilità dell’attributo è limitata agli elementi del package.
Page 4 CAPITOLO 1. INTRODUZIONE A UML 2

plicemente un commento relativo alla disponibilità del valore di un certo


attributo. Un attributo è derivato, se la sua definizione è preceduta da /; la
definizione è completa se esiste anche la regola di derivazione sotto for-
ma di commento. Questo significa che se aggiungessimo l’attributo età
alla classe Persona, potremmo facilmente derivarlo e scrivere:
/ età: int [1] = 0 {età = oggi - dataDiNascita}.
La regola per calcolare l’età è un commento, espresso tra parentesi
graffe, e può essere accanto all’attributo o nelle vicinanze della classe.
L’informalità del commento ipotizza che la sottrazione tra date possa re-
stituire la differenza in anni.
UML distingue tra operazioni e metodi. Un’operazione viene invoca-
ta su un oggetto, e corrisponde alla dichiarazione di una procedura; un
metodo rappresenta il corpo della procedura. Noi, per semplicità —e an-
che perché l’errore non è grave— useremo i due termini come sinonimi,
senza prestare attenzione alle differenze.
Per i metodi “significativi”, quelli cioè che descrivono il vero compor-
tamento della classe, UML offre una sintassi da linguaggio di programma-
zione:

visibilità nome (lista parametri): tipo di ritorno {stringa di proprietà}

Le regole di visibilità sono quelle già viste per gli attributi. Oltre al no-
me, possiamo definire i parametri formali del metodo (lista parametri) e
il tipo del valore restituito (tipo di ritorno). Anche per i metodi, la stringa
di proprietà consente di definire caratteristiche aggiuntive per il metodo.
Ogni parametro può essere definito in modo simile a un attributo:

<direzione> <nome>: <tipo> = <default>

I parametri non hanno visibilità, ma la direzione indica se il parame-


tro è in input (in), output (out) o entrambi (inout).
Ad esempio, una definizione completa del metodo compieAnni della
Figura 1.1 potrebbe essere: + compieAnni(in data: Date): boolean. Il
metodo è pubblico e richiede una data in input per stabilire se questa
corrisponde al compleanno della persona su cui si chiama il metodo.
I metodi che gestiscono le proprietà (attributi o associazioni) delle
classi non sono solitamente parte del diagramma. L’aggiunta dei metodi
CAPITOLO 1. INTRODUZIONE A UML 2 Page 5

get e set per ogni attributo privato (o associazione) della classe rende-
rebbe il diagramma più complesso senza comunicare nulla al lettore2 . Un
discorso simile vale anche per i costruttori: non serve aggiungere quello di
default, ma sarebbe opportuno elencare eventuali costruttori particolari.

Persona

- nome: String
- cognome: String
- dataNascita: Date
- numPersone: int
+ siSposa(p: Persona): boolean
+ compieAnni(d: Date): boolean

Figura 1.2: Classe Persona con attributo statico.

Attributi e operazioni sono statici quando si riferiscono all’intera clas-


se anziché alla singola istanza. Le proprietà statiche vanno sottolineate.
Ad esempio, se ipotizzassimo di voler contare le persone nel nostro siste-
ma, potremmo aggiungere un attributo numPersone alla classe Persona
(Figura 1.2). Il valore di questa proprietà è condiviso da tutte le istanze.
Ogni volta che viene creata una nuova persona, il costruttore deve in-
crementare questo attributo e il nuovo valore diventa automaticamente
visibile anche a tutte le altre persone.
Il secondo elemento fondamentale di un diagramma delle classi sono
le relazioni. Un’associazione, che si rappresenta con una linea piena, de-
finisce una relazione concettuale tra gli elementi di due classi e può essere
vista come un canale di comunicazione tra due insiemi di oggetti: un og-
getto richiede l’esecuzione di un metodo di un altro oggetto inviando un
messaggio all’oggetto che fornisce il servizio. Le associazioni sono molto
simili agli attributi di una classe e possono essere identificate da un no-
me (minuscolo): un sostantivo o un verbo che “spiega” il legame tra le
due classi. Seguendo la regola per cui è meglio non sovraccaricare un dia-
gramma, i nomi devono essere definiti solo se significativi. Ad esempio,
è inutile definire un’associazione ha per spiegare che una classe ha un at-
tributo il cui tipo è l’altra classe. È preferibile evitare il nome ed eventual-
mente definire ruoli opportuni (descritti nel seguito) per caratterizzare
l’associazione.
2
Chiunque conosca la tecnologia a oggetti sa che un attributo privato richiede metodi
opportuni per poter essere scritto e letto.
Page 6 CAPITOLO 1. INTRODUZIONE A UML 2

Le molteplicità vanno aggiunte a ogni estremità dell’associazione. I


valori possibili sono uguali a quelli degli attributi: data la molteplicità as-
sociata a un estremo, questa definisce il numero di elementi (della clas-
se cui è collegata l’associazione) in relazione con un solo elemento della
classe all’altro lato dell’associazione. Ad esempio, vive della Figura 1.3
definisce una relazione tra la classe Persona, già vista, e la classe Casa,
affermando che una persona vive in una e una sola casa, mentre più per-
sone possono condividere la stessa.

Persona 1..* 1 Casa


vive

Figura 1.3: Esempio di associazione.

A volte, si preferisce non definire nessuna molteplicità, sottintenden-


do un 1, ma questa convenzione limita la leggibilità dei diagrammi, in
quanto le scelte del progettista non sono ovvie ed esplicite.
Oltre alla molteplicità, le estremità di un’associazione possono essere
caratterizzate da ruolo, visibilità e navigabilità. I ruoli servono per ren-
dere espliciti i “compiti” delle classi coinvolte rispetto all’associazione in
questione. Questi sono utili come alternativa al nome dell’associazione,
oppure nei casi in cui la stessa classe svolga ruoli diversi nella stessa as-
sociazione o in associazioni simili. La visibilità è analoga a quanto già
visto per gli attributi e i metodi e definisce la visibilità dell’attributo im-
plicito definito dall’associazione. La navigabilità si rappresenta con una
freccia e definisce il verso di percorrenza dell’associazione per passare da
una classe (oggetto) a quella collegata attraverso l’associazione conside-
rata. Le associazioni mono direzionali ammettono un unico senso di per-
correnza, mentre quelle birezionali sono percorribili in entrambi i sensi.
La bidirezionalità si rappresenta orientando l’associazione nei due ver-
si, oppure usando una linea non orientata. A volte, e solo per questioni
di leggibilità, si usa anche un triangolino nero per identificare il verso da
adottare: questo non preclude la navigabilità dell’associazione, ma ne fa-
cilita la comprensione. Ad esempio, un’associazione informa tra due clas-
si, non chiarisce chi informa chi: il triangolino definisce il verso di lettura
corretto.
Ad esempio, matrimonio della Figura 1.4 definisce un’associazione bi-
direzionale e i due ruoli marito e moglie, che corrispondono anche a due
CAPITOLO 1. INTRODUZIONE A UML 2 Page 7

attributi impliciti della classe Persona.

Persona

- nome: String
- cognome: String
- dataNascita: Date + marito
- numPersone: int 0..1
+ siSposa(p: Persona): boolean
+ compieAnni(d: Date): boolean
+ moglie
0..1
matrimonio

Figura 1.4: Esempio di associazione con ruoli.

I due estremi sono contrassegnati da un + e, quindi, i due attributi


saranno pubblici. Da notare che la scelta dell’associazione matrimonio
non è felicissima. Infatti, in questo modo, il codice generato per la classe
Persona si ritroverà due attributi (marito e moglie) per ogni istanza della
classe. Se l’associazione fosse orientata, ad esempio dal marito alla mo-
glie, la classe avrebbe un solo attributo moglie per tradurre la possibilità
di ricavare la moglie di una certa persona, ma non il marito3 . Un modello
migliore, come vedremo, ci consentirebbe di avere un solo attributo per
implementare quest’associazione.
È evidente che UML offre due modi “simili” per modellare gli attri-
buti di una classe: possiamo definire un attributo all’interno della classe,
oppure un’associazione tra la classe in questione e la classe tipo dell’at-
tributo. Le due soluzioni sono valide entrambe, ma pongono l’accento su
aspetti diversi della modellazione. Il primo caso è da preferirsi quando
il tipo dell’attributo è un tipo primitivo (di UML o del linguaggio di pro-
grammazione scelto) e quando è una classe di libreria (o comunque una
classe che non si vuole enfatizzare nel modello). La seconda soluzione è
preferibile per mettere in evidenza le collaborazioni e le dipendenze tra
classi. La seconda soluzione è migliore anche quando la classe da usarsi
come tipo è già parte del diagramma.

3
Il verso di percorrenza diventa ancor più importante quando l’associazione collega
due classi diverse e gli eventuali attributi aggiunti definiscono la possibilità di muoversi
dagli oggetti di una classe ai corrispondenti dell’altra.
Page 8 CAPITOLO 1. INTRODUZIONE A UML 2

Oltre alle associazioni, in UML possiamo utilizzare aggregazioni e com-


posizioni. Le aggregazioni, rispetto alle associazioni, comunicano una re-
lazione part-of tra le parti della relazione. Graficamente, oltre alla linea
piena, si aggiunge un piccolo rombo vuoto dalla parte del contenitore. Ad
esempio, la Figura 1.5 descrive la classe Automobile come un aggregato di
Telaio, Motore e quattro Ruota.

1 Telaio

Automobile 1 Motore
1

4 Ruota

Figura 1.5: Esempio di aggregazione.

La differenza tra associazione e aggregazione non è chiarissima e l’uso


dell’una o dell’altra nel progetto riguarda semplicemente l’enfatizzazione
della relazione di contenimento. Ci sono casi —come quello della Figu-
ra 1.5— in cui la scelta è ovvia, e molti in cui spesso resta nella testa del
progettista. Booch [Boo93] suggeriva l’uso dell’aggregazione per comu-
nicare: contenimento fisico (il libro sullo scaffale), membership (il gioca-
tore di una squadra di calcio) e composizione funzionale (esempio della
Figura 1.5).
L’uso della composizione, al contrario, è definito meglio e impone l’u-
nicità della relazione tra le istanze delle classi considerate. Una classe può
essere componente di molte classi, ma ogni istanza può essere contenu-
ta in un solo oggetto. Ad esempio, il diagramma della Figura 1.6 rappre-
senta il fatto che una Ruota può essere parte di un’Automobile o di una
Bicicletta, ma la singola ruota (l’oggetto) appartiene a uno solo dei due
mezzi. L’uso della composizione porta alla “dipendenza esistenziale” del
contenuto rispetto al contenitore: gli oggetti contenuti sono implicita-
mente cancellati quando si elimina l’oggetto contenitore4 . Questa dipen-
4
Come questo avvenga in concreto dipende dalla tecnologia implementativa
utilizzata.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 9

denza ci consente di non definire le cardinalità delle composizioni dal lato


del contenitore (che potrebbero solo essere 0..1).

Automobile 0..1 Ruota 0..1 Bicicletta

Figura 1.6: Esempio di composizione.

Una classe di associazione è una classe che serve per aggiungere at-
tributi e metodi alle associazioni: si usa una linea tratteggiata per lega-
re la classe e l’associazione cui si riferisce. Nel modello entità-relazioni
possiamo aggiungere solo attributi alle relazioni, in un diagramma delle
classi sia attributi che metodi. Se consideriamo le classi Studente e Corso
(Figura 1.7), il voto dello studente e la sua frequenza non possono esse-
re proprietà delle due classi, ma caratterizzano l’associazione5 . La classe
DatiCorso riassume queste proprietà e consente loro di avere un compor-
tamento specifico: ad esempio, il metodo votoMassimo calcola il massimo
cui ogni studente può ambire in funzione della frequenza al corso.

Studente Corso
1..* 1..*

DatiCorso
- voto: int
- frequenza: int
+ votoMassimo(): int

Figura 1.7: Esempio di classe di associazione.

Le classi di associazione sono uno strumento di modellazione utile a


livello concettuale, ma pongono problemi a livello implementativo. L’u-
so di una classe di associazione impone che ci sia sempre uno e un solo
oggetto di questa classe per ogni coppia di oggetti delle classi legate dal-
l’associazione. Nell’esempio della Figura 1.7, questo significa che ci deve
essere un solo oggetto DatiCorso per ogni coppia studente/corso. Questo
5
Se voto e frequenza appartenessero alla classe Corso tutti gli studenti avrebbero
lo stesso voto e la stessa frequenza per un certo corso. Se appartenessero alla classe
Studente, ogni studente avrebbe sempre lo stesso voto e la stessa frequenza per ogni
corso.
Page 10 CAPITOLO 1. INTRODUZIONE A UML 2

vincolo deve essere opportunamente tradotto in codice e spesso vale la


pena trasformare la classe di associazione in una classe normale e rendere
espliciti i vincoli con le classi a essa collegate.
Le relazioni di generalizzazione non riguardano la definizione di at-
tributi impliciti, ma la struttura stessa delle classi coinvolte. Anche se
il concetto di generalizzazione potrebbe essere interpretato in modo di-
verso (ad esempio [GJ97]), in questo libro adottiamo una prospettiva da
linguaggio di programmazione e interpretiamo le generalizzazioni attra-
verso l’ereditarietà tra classi: una sottoclasse (o classe derivata) eredita
tutte le caratteristiche (attributi, associazioni e metodi) della superclasse
(o classe base). L’unica regola che suggeriamo è il rispetto del principio
di sostituzione [LG00]: gli oggetti di una sottoclasse devono poter essere
sostituiti ovunque siano usati oggetti della superclasse, senza influenzare
il comportamento del codice che li usa. Graficamente (Figura 1.8) la ge-
neralizzazione si rappresenta con una linea piena orientata con un trian-
golo vuoto dalla parte della superclasse. In questo capitolo non ci preoc-
cupiamo di definire le regole per un uso corretto delle relazioni di gene-
ralizzazione, ma rimandiamo il lettore a [LG00,LC03] per una trattazione
dettagliata dell’argomento.

Veicolo Veicolo
Terrestre APedali

Triciclo Bicicletta

Figura 1.8: Esempi di ereditarietà singola e multipla.

Parliamo di ereditarietà singola quando una classe eredita da una so-


la classe base: ad esempio, la Figura 1.8 definisce Triciclo come clas-
se derivata di Bicicletta. Introduciamo l’ereditarietà multipla quando
una classe eredita da più superclassi: la Figura 1.8 definisce Bicicletta
come sottoclasse sia di VeicoloTerrestre che di VeicoloAPedali. L’ere-
ditarietà multipla è uno strumento di modellazione potente, ma la sot-
toclasse potrebbe ereditare più volte le stesse proprietà (definite da un
antenato comune delle superclassi da cui eredita).
CAPITOLO 1. INTRODUZIONE A UML 2 Page 11

Quando si usa la generalizzazione, le superclassi possono essere astrat-


te (il loro nome deve essere scritto in corsivo). Queste classi non sono de-
finizioni di insiemi di oggetti, ma strumenti per fattorizzare proprietà co-
muni tra classi simili e poterle organizzare in una gerarchia di ereditarietà.
Non potremo mai creare oggetti a partire da una classe astratta, ma pos-
siamo servircene per dare una radice comune a un insieme di classi che
condividono le stesse proprietà e poter quindi sfruttare il polimorfismo e
il binding dinamico nei nostri progetti (e quindi nel nostro codice).

Disegno Figura
1..* 1..*
Geometrica

Quadrato Rettangolo Triangolo

Figura 1.9: Esempio di classe astratta.

Ad esempio, FiguraGeometrica della Figura 1.9 è la superclasse astrat-


ta di Quadrato, Rettangolo e Triangolo. La superclasse non può essere
istanziata e non implementa i suoi metodi astratti. Spetta alle sottoclassi
fornire il codice specifico dei metodi che ereditano dalla superclasse. In
questo modo, il Disegno può pensare di disegnare semplici figure geome-
triche, senza preoccuparsi se queste sono quadrati, rettangoli o triangoli.
L’interfaccia è la medesima, ma l’effettivo comportamento dipende dalla
sottoclasse considerata.
UML ammette anche l’uso di linee tratteggiate orientate per identifi-
care “generiche” dipendenze tra classi (ma anche tra altri elementi del-
la notazione). L’uso di queste relazioni è a carico dell’utente, che deve
stare attento a non fare diagrammi troppo ricchi che sono solitamente
poco leggibili e diventano quindi meno utili di quelli che danno solo le
informazioni essenziali.
Per indicare che una classe realizza il concetto descritto da un’altra
classe, possiamo usare una linea tratteggiata con un triangolo vuoto come
testa (verso l’elemento realizzato). Possiamo anche usare le linee tratteg-
giate —senza ulteriori parole chiave— per identificare dipendenze non
transitive per quanto riguarda la manutenzione (quali classi devono esse-
Page 12 CAPITOLO 1. INTRODUZIONE A UML 2

re modificate a fronte di un cambiamento in una certa classe), ma UML


ammette anche usi più sofisticati con parole chiave quali call, per
identificare che la classe sorgente invoca un metodo della classe desti-
nazione, use, per una semplice relazione d’uso, o create, per dire
che la classe sorgente crea istanze della classe destinazione. Altre parole
chiave (dette stereotipi) verranno introdotte nei prossimi capitoli, ma il
lettore interessato può consultare [Fow03] per una trattazione completa.

1.1.1 Interfacce
Un diagramma delle classi può contenere anche interfacce, cioè classi
senza implementazione. Queste servono per disaccoppiare la definizio-
ne delle operazioni dalla loro implementazione. Per poter usare un cer-
to oggetto (componente) è sufficiente l’interfaccia; non serve conoscer-
ne l’implementazione. Quindi, è importante distinguere tra interfacce
offerte e richieste. Nel primo caso, esiste una classe che implementa, e
quindi offre, una certa interfaccia, mentre nel secondo caso c’è solo la
volontà di usare determinate operazioni, senza conoscerne l’effettiva im-
plementazione. Vedremo che le interfacce non appartengono solamente
alle classi: parleremo di loro anche per caratterizzare il comportamento
dei componenti.

FiguraGeometrica <<interface>> Vector


List
- vertici: List
+ add(o: Object): boolean + add(o: Object): boolean
+ get(index: int): Object + get(index: int): Object

(a) Interfaccia estesa

FiguraGeometrica Vector

- vertici: List
+ add(o: Object): boolean
List + get(index: int): Object

(b) Interfaccia compatta

Figura 1.10: Interfaccia UML estesa (a) e compatta (b).

UML presenta un concetto di interfaccia simile a quello di Java o C# e


propone due modi diversi di rappresentazione. Le interfacce possono es-
sere rappresentate in modo esteso utilizzando la parola chiave interface,
CAPITOLO 1. INTRODUZIONE A UML 2 Page 13

per differenziarle dalle classi, oppure con un cerchio vuoto per darne una
visualizzazione sintetica. Le relazioni d’uso e di realizzazione si rappre-
sentano in modo diverso in funzione della rappresentazione scelta per
l’interfaccia. Ad esempio, potremmo voler realizzare una classe per creare
figure geometriche come liste di vertici. I due diagrammi della Figura 1.10
non modellano il concetto di vettore, ma usano l’interfaccia List di Java
per descrivere il concetto di lista. Questa, a sua volta, è implementata
dalla classe Vector.

1.1.2 Oggetti

Un diagramma degli oggetti fotografa un insieme di oggetti che compon-


gono il sistema in un certo istante. Questi diagrammi sono utili per rap-
presentare alcune configurazioni significative di oggetti che appartengo-
no all’applicazione e, quindi, spiegare in modo esplicito le relazioni tra
questi.
I diagrammi degli oggetti servono per “chiarire i punti oscuri” dei dia-
grammi delle classi. A volte, è molto più semplice capire l’effettiva struttu-
ra di un insieme di oggetti o di un oggetto composto attraverso un esem-
pio, piuttosto che con mille commenti e spiegazioni. Possiamo anche ve-
dere un diagramma degli oggetti come la struttura di un diagramma di co-
municazione (Paragrafo 1.3.2): gli oggetti e le loro relazioni sono gli stessi,
mancano i messaggi scambiati.
Gli elementi principali sono gli oggetti, che sono istanze delle classi
modellate in un opportuno diagramma. La forma completa per definire
un oggetto è nome istanza: nome classe, tutto sottolineato, ma è possibile
identificare un oggetto anche con il solo nome, quando la classe di appar-
tenenza non è definita, o con la sola classe, per far riferimento a un’istan-
za generica di una certa classe. Se necessario, si può anche caratterizzare
l’oggetto attraverso valori particolari per gli attributi e per i collegamenti
con altri oggetti. Non è necessario caratterizzare un oggetto completa-
mente, ma vanno definite solo quelle proprietà che sono importanti per
il diagramma (ovvero, per la configurazione che si sta rappresentando).
Ad esempio, il diagramma della Figura 1.11 sfrutta il diagramma delle
classi della Figura 1.3 per descrivere la situazione in cui Giovanni, Marco e
Mario vivono tutti nella stessa casa di viaVerdi10.
Page 14 CAPITOLO 1. INTRODUZIONE A UML 2

Giovanni: Persona

Marco: Persona viaVerdi10:


Casa

Mario: Persona

Figura 1.11: Esempio di diagramma degli oggetti.

1.1.3 Package

Un diagramma delle classi (o degli oggetti) ci offre un’organizzazione “piat-


ta” degli elementi che contiene. Non è pensabile usare un solo diagramma
per modellare un sistema reale composto da molte classi. Occorre un’or-
ganizzazione gerarchica che ci consenta di scomporre il sistema in parti.
A questo proposito UML propone i package e i diagrammi dei package
per strutturare un modello6 UML. I package sono uno strumento generi-
co per raggruppare elementi UML di qualsiasi tipo, ma solitamente ven-
gono usati per gestire le classi (e questo è il motivo per cui sono presentati
qui).
Un package si rappresenta con un rettangolo dotato di linguetta in al-
to a sinistra, come si vede nella Figura 1.12, e di un nome. Il contenuto del
rettangolo può essere il nome stesso oppure l’insieme delle classi che con-
tiene (e in questo caso il nome del package viene spostato sulla linguet-
ta7 ). Le classi possono essere rappresentate come una sequenza di nomi
oppure con il simbolo di classe, mostrando se necessario anche le altre
caratteristiche delle classi. Le classi di un package possono essere pubbli-
che (+) o private (-): le pubbliche definiscono l’interfaccia del package e
possono essere usate da classi di altri package; le private restano “nasco-
ste” all’interno del package. Le relazioni tra package si rappresentano con
linee tratteggiate.
L’esempio della Figura 1.12 descrive tre semplici package: l’interfacciaGra-
6
Anche se molto spesso in questo libro usiamo le parole modello e diagramma come
sinonimi, sarebbe più corretto dire che un modello è un’entità complessa che si compone
di diversi diagrammi.
7
È evidente l’analogia con la cartelletta porta documenti.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 15

fica dipende dalla logicaApplicativa, la quale a sua volta dipende dal


modelloDeiDati. L’ultimo package mostra le classi al suo interno che, per
semplicità, sono quelle definite nel Paragrafo 1.1.

interfaccia logica modello


Grafica Applicativa DeiDati
+ Persona
+ Casa

Figura 1.12: Esempio di diagramma dei package.

Il risultato della scomposizione è, di solito, una gerarchia di conteni-


mento in cui i package di più alto livello sono scomposti in altri package
che, a loro volta, possono essere suddivisi in package o classi. Le foglie
della gerarchia sono sicuramente delle classi.
Un package definisce anche un namespace e, quindi, ogni classe de-
ve avere un nome univoco all’interno del package che la contiene. Più
classi possono avere lo stesso nome solo se appartengono a package di-
versi. Nei casi di omonimia, possiamo usare i nomi completi e far ri-
ferimento alle classi attraverso la struttura dei package che contengono
la classe. Cosı̀ facendo, la classe Persona della Figura 1.12 diventereb-
be ModelloDeiDati::Persona, dove :: è il separatore tra package o tra
package e classe.
La suddivisione delle classi in package non è affatto banale; non esi-
stono regole precise, ma solitamente ci si rifà a principi abbastanza gene-
rali. Le classi di un package devono avere alta coesione all’interno e poche
interfacce ben definite con l’esterno. Inoltre, dovrebbero condividere le
cause di un eventuale cambiamento e poter essere riusate come un’unica
entità [Mar03]. Qualunque sia la scelta, questa implica delle dipendenze
tra i package in cui abbiamo deciso di scomporre l’applicazione.
Oltre alla relazione di contenimento, le dipendenze tra package tra-
ducono le dipendenze tra le classi che li compongono. Anche se UML
prevede vari tipi di dipendenze tra package, è buona norma limitarsi a
quelle “semplici”. Anche le dipendenze tra package devono sottostare al-
le semplici regole per definire le dipendenze tra gli elementi di UML. Oc-
corre, quindi, cercare di evitare le dipendenze cicliche, ricordare che le
dipendenze non sono transitive e privilegiare le relazioni fondamentali
per l’applicazione che si sta progettando. Da queste relazioni possiamo
anche comprendere che i package con un numero elevato di frecce en-
Page 16 CAPITOLO 1. INTRODUZIONE A UML 2

tranti sono gli elementi caratterizzanti dell’applicazione e la loro qualità


ha un impatto fondamentale su quella dell’intera applicazione.
Una buona struttura dei package rappresenta in modo chiaro e con-
ciso il flusso delle dipendenze. Quando un package è usato da molti altri
package, non ha senso trasformare il diagramma in una ragnatela, ma si
può utilizzare la parola chiave global per etichettarlo.
L’ultimo caso da considerare, riguarda la possibilità che un’interfac-
cia e la sua implementazione stiano in package diversi. Potrebbe anche
essere che l’effettiva implementazione sia fornita da package differenti
in funzione della configurazione del sistema. In questi casi, possiamo
usare la relazione di realizzazione —già vista per le interfacce (Paragra-
fo 1.1.1)— tra il package che fornisce l’interfaccia e quelli che la imple-
mentano. Ad esempio, se considerassimo un’applicazione destinata a gi-
rare su più sistemi operativi, potremmo essere interessati a supportare
diversi sistemi per la gestione delle finestre dell’interfaccia grafica. Sen-
za scendere nei dettagli, potremmo voler considerare il sistema a fine-
stre di Windows, quello di MacOS e quello di Linux. In questo caso (Fi-
gura 1.13) l’interfaccia utente potrebbe essere “virtualizzata” in un pac-
kage interfacciaGrafica e implementata dai tre package per i rispettivi
sistemi operativi.

interfaccia
Grafica

Windows MacOS Linux


GUI GUI GUI

Figura 1.13: Esempio di relazione di realizzazione tra package.


CAPITOLO 1. INTRODUZIONE A UML 2 Page 17

1.2 Casi d’uso


I diagrammi dei casi d’uso catturano i requisiti funzionali di un’applica-
zione. L’utilità di questi diagrammi consiste nell’organizzare i requisiti in
macro funzionalità che devono poi essere ulteriormente dettagliate me-
diante opportuni scenari e, magari, attraverso un diagramma delle clas-
si di dominio. Uno scenario è un esempio d’uso del sistema, è quindi
una sequenza di passi di interazione tra un utente (anche non umano)
e il sistema. Il diagramma delle classi di dominio può servire per defi-
nire un vocabolario comune di termini ed elementi che appartengono al
problema.
I casi d’uso identificano le funzionalità principali offerte dal sistema.
Ogni caso d’uso, rappresentato da un’ellisse, corrisponde a una funziona-
lità, ma possiamo anche dire che un caso d’uso raggruppa un insieme di
scenari “simili” che descrivono le diverse alternative per raggiungere un
obiettivo (per un utente del sistema).
Gli utenti sono esterni al sistema e sono gli attori del diagramma, rap-
presentati da omini stilizzati. Questi definiscono i ruoli assunti dagli uten-
ti rispetto al sistema. Uno stesso utente può svolgere più ruoli, mentre lo
stesso ruolo può essere coperto da diversi utenti. Gli attori non identifi-
cano solamente le persone fisiche che usano il sistema, ma definiscono
anche eventuali sottosistemi software o componenti hardware con cui il
sistema deve interagire. Ad esempio, il semplice diagramma dei casi d’u-
so della Figura 1.14 rappresenta le funzionalità principali offerte da uno
sportello bancomat ai propri clienti. Questi possono prelevare contan-
ti, chiedere il saldo del loro conto corrente, avere i movimenti dell’ultimo
mese e ricaricare i propri telefoni cellulari.
Page 18 CAPITOLO 1. INTRODUZIONE A UML 2

PrelievoSoldi

Saldo

ListaMovimenti
cliente

RicaricaTelefono

Figura 1.14: Esempio di casi d’uso.

Per descrivere un caso d’uso, e quindi i suoi scenari, possiamo usa-


re diverse soluzioni. Ogni caso d’uso può essere associato a un trigger,
che caratterizza l’evento che gli dà inizio, a una pre-condizione, che de-
scrive lo stato in cui dovrebbe trovarsi il sistema prima che possa avere
inizio, e a una post-condizione, che descrive lo stato in cui si troverà il
sistema dopo l’esecuzione. Dobbiamo anche definire una lista di passi
per descrivere lo scenario principale. Questo descrive il caso ottimo in
cui tutto funziona al meglio. Gli scenari alternativi —quelli che identi-
ficano varianti “significative”— vengono rappresentati come estensioni
dello scenario principale. Per fare questo, si identificano dei passi alter-
nativi rispetto allo scenario principale e si descrive il comportamento sia
introducendo nuovi passi, sia riferendosi a quelli esistenti.
L’attore principale di ogni caso d’uso è colui che richiede il servizio al
sistema e, spesso, è anche colui che inizia lo scenario con la prima intera-
zione. Chiaramente, uno scenario può coinvolgere anche altri attori, ma
questi svolgono un ruolo secondario. Ogni passo del caso d’uso dovrebbe
corrispondere a un’interazione “semplice”8 tra attore e sistema e questo
significa che dovrebbe sempre essere possibile descriverla con una frase
breve. Ad esempio, un possibile scenario per il caso d’uso PrelievoSoldi

8
Ciò che è facile da descrivere, e viene percepito come semplice dall’utente, potrebbe
essere molto complesso da realizzare. In questo contesto, la semplicità si riferisce alla
percezione dell’utente.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 19

della Figura 1.14 potrebbe essere:


Caso d’uso PrelievoSoldi
Tipo Primario
Precondizione Avere una tessera bancomat.
Svolgimento Il cliente inserisce la tessera nell’apposita fessura e digita il suo
normale pin. Il sistema riconosce il cliente, che può quindi selezionare
la voce prelievo e la quantità di soldi richiesta. Il sistema auto-
rizza il prelievo e segnala il ritiro della tessera. Il cliente ritira la
tessera, il denaro e la ricevuta nell’ordine.
Svolgimento Se il sistema non riconosce il cliente, questo deve invitare il
alternativo cliente a inserire di nuovo il proprio pin. Se il sistema ricono-
sce il pin, il cliente torna allo scenario principale, altrimenti al
terzo tentativo ritira la tessera.
Postcondizione La cifra richiesta viene prelevata dal conto del cliente,
aggiornando opportunamente il suo saldo.
Descrizione La didatticità dell’esempio non motiva note o commenti
particolari.

Le estensioni, o svolgimenti alternativi, devono sempre iniziare iden-


tificando chiaramente il passo dello scenario principale che deve esse-
re sostituito: ad esempio, nel caso precedente ipotizziamo che il sistema
non riconosca la tessera (o il pin) del cliente.
I casi d’uso devono essere pochi, brevi e ben strutturati. Un numero
eccessivo di casi d’uso, ma anche scenari molto lunghi e articolati, sareb-
bero inutili e controproducenti. Chiaramente, tutto deve essere commi-
surato alla reale complessità dell’applicazione. Se una descrizione diven-
ta molto complessa, è fondamentale usare le relazioni tra gli elementi per
gestire e modularizzare lo scenario.
Oltre alla relazione che unisce i casi d’uso e gli attori, per rappresenta-
re che un certo attore ha un ruolo in quel caso d’uso, esistono altre possi-
bilità. Se un passo di un caso d’uso diventa troppo complesso, possiamo
considerarlo come un ulteriore caso d’uso e usare una freccia tratteggiata
e la parola chiave include per dire che il primo caso d’uso include il se-
condo. Ad esempio, il frammento di diagramma della Figura 1.15 mostra
la trasformazione del passo 4 dello scenario principale in un caso d’uso
specifico.
L’inclusione è un modo per scomporre la funzionalità principale in un
insieme di sottofunzionalità. È utile per non avere passi singoli troppo
complessi, ma anche per fattorizzare gli elementi comuni che potrebbe-
ro concorrere alla realizzazione di più scenari. L’inclusione può essere
rappresentata anche sugli scenari, evidenziando, magari con una sottoli-
Page 20 CAPITOLO 1. INTRODUZIONE A UML 2

PrelievoSoldi

cliente
<<include>>

SelezioneVoce
Interfaccia

Figura 1.15: Esempio di relazione di inclusione tra casi d’uso.

neatura, il passo che rimanda a un caso d’uso specifico.


UML offre anche le relazioni di generalizzazione ed estensione. Que-
ste devono essere usate con assoluta parsimonia, senza rendere il dia-
gramma troppo complesso e inutilmente sofisticato.
Una relazione di generalizzazione potrebbe servire per dire che due
casi d’uso offrono funzionalità simili, ma il caso d’uso figlio specializza
quanto offerto da quello padre. La relazione è rappresentata usando una
freccia con tratto pieno e punta grossa e vuota dal lato del caso padre. Il
figlio eredita il comportamento del padre e lo può estendere, modificando
o aggiungendo opportuni passi elementari, per rappresentare situazioni
più specifiche. La generalizzazione può avvenire anche attraverso rela-
zioni di estensione e inclusione con altri casi d’uso. L’unico vincolo è che
il caso figlio deve conservare l’intento del padre.
Un’estensione rappresenta il fatto che un caso d’uso è l’estensione di
un altro caso d’uso. Il concetto è simile al precedente, ma qui l’esten-
sione avviene identificando uno o più punti e chiarendo quali di questi
siano effettivamente estesi. La rappresentazione grafica usa una freccia
tratteggiata con la parola riservata extend e l’elenco dei punti di esten-
sione: la Figura 1.16 mostra il caso in cui l’acquisto di un prodotto con
carta di credito estende l’acquisto generico per quanto riguarda la moda-
lità di pagamento, ma non rispetto alla forma di spedizione della merce.
La relazione di estensione definisce quindi un metodo più rigoroso per
caratterizzare le variazioni —e quindi le similitudini— tra due casi d’uso.
Un diagramma dei casi d’uso è un sommario grafico delle funziona-
lità del sistema. Gli scenari, che non sono rappresentati graficamente,
contengono molte più informazioni per chi deve progettare il sistema. È
importante anche ragionare sul livello d’astrazione con cui definiamo i
CAPITOLO 1. INTRODUZIONE A UML 2 Page 21

AcquistoCon
CartaDiCredito
<<extend>>
AcquistoProdotto
Extension Points
modalità di pagamento

Figura 1.16: Esempio di relazione di estensione tra casi d’uso.

casi d’uso: il rischio è di considerare funzionalità di livello troppo elevato,


oppure elementi troppo di dettaglio.

1.3 Interazioni
I diagrammi di interazione descrivono la cooperazione tra un insieme di
oggetti che collaborano per realizzare un fine comune (spesso gli scena-
ri evidenziati con i casi d’uso). Questi diagrammi possono essere usati
sia come strumento di progettazione, che come mezzo di convalida. Nel
primo caso, le interazioni servono per ragionare sul sistema che si sta pro-
gettando e per identificare le classi che costruiranno la soluzione; nel se-
condo caso, le interazioni sono modellate utilizzando le classi a disposi-
zione (definite in precedenza) e quindi i diagrammi diventano un mezzo
di “convalida” delle classi identificate.

1.3.1 Sequenze
UML offre diverse forme di diagrammi di interazione, ma i più usati so-
no sicuramente i diagrammi di sequenza. Tipicamente, un diagramma di
sequenza rappresenta l’interazione tra oggetti, ma in UML 2 si preferisce
usare il termine più generico di partecipante per non vincolare troppo la
natura degli elementi che interagiscono. Per mantenere la genericità, si
usano nomi singoli per identificare i partecipanti, ma è possibile anche
una sintassi più completa in cui si specifica la classe di appartenenza di
ogni elemento (nome: Classe). Qualunque sia la scelta fatta, i nomi non
devono essere sottolineati (cosa che avveniva in UML 1.x).
Ogni partecipante è raffigurato da un rettangolo e da una riga in ver-
ticale, detta linea di vita. I messaggi scambiati tra i partecipanti sono
ordinati dall’alto verso il basso. Le linee di vita possono essere comple-
tate con barre di attivazione per indicare quando il partecipante è attivo
nell’interazione.
Page 22 CAPITOLO 1. INTRODUZIONE A UML 2

I partecipanti possono esistere all’inizio dell’interazione, oppure esse-


re creati durante lo scambio di messaggi: si usa un messaggio speciale che
punta direttamente al rettangolo che corrisponde al nuovo elemento. Per
distruggere gli oggetti, si usa una grande X posta in corrispondenza dell’i-
stante in cui il partecipante viene cancellato. Questo può accadere perché
l’elemento termina il proprio compito all’interno della sequenza conside-
rata, ma anche perché l’invocazione di un messaggio diretto all’elemento
provoca la sua cancellazione.

Volo
Cliente
Richiesta

aggiungi

conferma

Figura 1.17: Esempio di diagramma di sequenza.

L’esempio della Figura 1.17 descrive una semplice interazione per ag-
giungere un nominativo alla lista d’attesa di un volo. In questo caso, il
Cliente, che supponiamo sia una attore esterno al sistema, crea una nuo-
va Richiesta, la quale viene aggiunta al Volo. Non appena la Richiesta
riceve risposta affermativa, questa ha terminato il proprio compito e può
quindi essere distrutta.
I partecipanti interagiscono scambiandosi messaggi; se il diagramma
rappresenta oggetti che sono istanze di classi già identificate, i messaggi
diventano invocazioni dei metodi forniti dai diversi oggetti. Ogni invoca-
zione (linea piena) ammette un messaggio di ritorno (linea tratteggiata).
Non è buona norma appesantire un diagramma di sequenza con tutti i
messaggi di ritorno. È sempre preferibile concentrarsi, e quindi rappre-
sentare, solo quelli più significativi, quelli che potrebbero condizionare il
flusso d’esecuzione dello scenario.
I diagrammi di sequenza potrebbero iniziare con un messaggio (found
message) che non deriva dal alcun partecipante. In questi casi, il dia-
gramma diventa la risposta del sistema al messaggio iniziale: è un mo-
CAPITOLO 1. INTRODUZIONE A UML 2 Page 23

do per descrivere il comportamento dei partecipanti alla ricezione di un


messaggio.
Ad esempio, il diagramma di sequenza della Figura 1.18 descrive l’in-
terazione tra le varie parti che compongono un sistema di autonoleggio
quando questo riceve una richiesta di prenotazione. Il messaggio prenota
arriva dall’esterno e fa sı̀ che l’Autonoleggio individui il Cliente per co-
noscerne le preferenze, prima di inoltrare la richiesta di disponibilità
alla Sede presso cui il cliente ritirerà la vettura. A questo punto, sup-
ponendo che esista un’Autovettura disponibile, la Sede crea una nuova
Prenotazione che si lega (associa) alla vettura selezionata.

Autonoleggio Cliente Sede Autovettura

prenota

preferenze

disponibilità

Prenotazione

associa

Figura 1.18: Esempio di diagramma di sequenza con messaggio iniziale.

L’interazione tra gli oggetti della Figura 1.18 è implicitamente sincro-


na: un partecipante deve aspettare il messaggio di ritorno prima di poter
continuare e fare altro. Questo perché in UML le frecce con la punta piena
indicano messaggi sincroni; quelli asincroni, con i partecipanti che pos-
sono continuare senza aspettare messaggi di ritorno, vengono identificati
da frecce la cui punta è fatta da due parti separate (Figura 1.19). Queste
convenzioni complicano l’identificazione della natura dei messaggi e im-
pongono un’attenzione eccessiva alle punte delle frecce. In alcuni casi, è
ancora preferibile la vecchia convenzione che usava le frecce con mezza
punta per identificare i messaggi asincroni.
UML 2 introduce novità significative per quanto riguarda la gestio-
ne di cicli e di condizioni nei diagrammi di sequenza, e propone anche
la possibilità di modularizzare e comporre i diagrammi definiti9 . Que-
9
Quest’ultimo aspetto è gestibile attraverso diagrammi di interazione generale, che
Page 24 CAPITOLO 1. INTRODUZIONE A UML 2

messaggio sincrono

messaggio asincrono

Figura 1.19: Tipi di messaggi.

sto è possibile attraverso frame di interazione, cioè parti del diagramma


incorniciate ed etichettate in modo opportuno nell’angolo in alto a sini-
stra. L’uso della parola chiave loop, con una condizione tra parentesi qua-
dre oltre l’etichetta, indica che l’insieme di messaggi deve essere ripetuto
finché la condizione è vera. Similmente, per citare solo le principali10 ,
l’etichetta ref definisce un riferimento a un’altra sequenza (diagramma),
alt, con condizione associata, identifica due blocchi che devono essere
eseguiti in alternativa, mentre opt e condizione definiscono un blocco la
cui esecuzione è opzionale (l’insieme di messaggi viene eseguito solo se
la condizione è vera). Con l’etichetta par si caratterizzano due o più bloc-
chi che si eseguono in parallelo, mentre neg identifica un’interazione non
valida, che quindi non dovrebbe accadere.
Queste nuove funzionalità ci consentono di estendere l’esempio della
Figura 1.18, inserendo la possibilità di prenotare più macchine contem-
poraneamente e considerando anche clienti non registrati. La Figura 1.20
presenta il diagramma risultante. La semantica dinamica del modello
non è definita in modo formale, ma il buon senso e la pratica aiutano a
dirimere tutti i casi critici.

1.3.2 Comunicazioni
I diagrammi di sequenza enfatizzano lo scambio di messaggi tra i parteci-
panti, mentre i diagrammi di comunicazione —che in UML 1.x si chiama-
vano diagrammi di collaborazione— privilegiano lo scambio di dati e le
relazioni tra i partecipanti. Capita spesso che i collegamenti tra gli oggetti
non siano dovuti solamente allo scambio di messaggi, e quindi corrispon-
dano ad associazioni nei diagrammi delle classi, ma si riferiscano al fatto
che un oggetto è una variabile locale di un’operazione fornita da un altro
oggetto, oppure è usato come parametro attuale nell’invocazione del me-

non sono però descritti in questo libro.


10
Per la lista completa di parole chiave, il lettore può far riferimento a [Fow03].
CAPITOLO 1. INTRODUZIONE A UML 2 Page 25

Autonoleggio Cliente Sede Autovettura

prenota

loop [per ogni auto ordinata]

opt [se il cliente è registrato]

preferenze

disponibilità

Prenotazione

associa

Figura 1.20: Esempio di diagramma di sequenza con frame di interazione.

todo. In questi casi, l’organizzazione di un diagramma di sequenza non ci


sarebbe d’aiuto e dobbiamo preferire un diagramma di comunicazione.
I partecipanti, rappresentati ancora come rettangoli, sono disposti li-
beramente e gli archi ne evidenziano i legami. La libertà di disporre gli
oggetti nello spazio preclude l’identificazione immediata della sequenza,
ma consente di rappresentare meglio situazioni quali il contenimento tra
oggetti e gli usi visti sopra. Gli estremi di ogni arco possono essere eti-
chettati con il ruolo che l’oggetto svolge nel diagramma (ad esempio, un
ruolo potrebbe essere il nome di paramento attuale usato per invocare un
metodo).
UML 2 impone che la sequenza di messaggi sia definita con un siste-
ma di numerazione decimale nidificata. L’uso della nidificazione (Figu-
ra 1.21) consente di identificare il contesto in cui un messaggio viene in-
viato —cioè, in altre parole, il contesto in cui il metodo viene eseguito.
Una notazione piatta (1, 2, 3, ...) non darebbe alcuna informazione e po-
trebbe significare che tutti i metodi sono chiamati all’interno del metodo
principale, cioè dal primo. Al contrario, se partissimo dall’invocazione
1.2: metodo(), l’uso di una nuova cifra decimale (1.2.1) significherebbe
che la nuova chiamata avviene nel contesto di metodo e non in quello di
chi lo ha chiamato. Cosı̀ facendo, in ogni diagramma di comunicazione ci
sarà sempre almeno un livello di annidamento.
Un oggetto può condizionare l’invio di messaggi, usando opportuni
Page 26 CAPITOLO 1. INTRODUZIONE A UML 2

1: prenota 1.1: preferenze


Autonoleggio Cliente

1.2: disponibilità

1.3: <<create>>
Sede Prenotazione

1.3.1: associa

Automobile

Figura 1.21: Esempio di diagramma di comunicazione.

predicati prima del messaggio stesso, e può iterare l’invio del medesimo
messaggio a tutti gli oggetti che “possono riceverlo” (premettendo un * al
messaggio e la condizione che deve essere verificata tra parentesi quadre).

1.4 Macchine a stati


L’uso di automi (o macchine) a stati per modellare il comportamento degli
elementi che compongono un sistema software è pratica diffusa da anni.
UML offre una variante degli statechart di David Harel [HN96], chiamata
diagramma delle macchine a stati, per modellare il comportamento dei
singoli oggetti.
Mentre i diagrammi di interazione sono utili per descrivere il compor-
tamento di un insieme di oggetti che cooperano per ottenere un unico
risultato, le macchine a stati descrivono il comportamento dei singoli og-
getti. In realtà, un diagramma caratterizza il comportamento di una clas-
se di oggetti, ma concettualmente non c’è alcuna differenza tra la defini-
zione del comportamento di un singolo oggetto o di un’intera classe. Non
è necessario definire una macchina a stati per ogni classe del sistema; vale
sempre la regola di caratterizzare solo i comportamenti più significativi.
Una macchina a stati è composta da stati, rappresentati da rettangoli
con gli angoli smussati, e da transizioni, cioè archi che collegano le cop-
pie di stati. Ogni automa deve avere uno stato iniziale e zero o più stati
CAPITOLO 1. INTRODUZIONE A UML 2 Page 27

finali. Lo stato iniziale si rappresenta con un cerchio nero, mentre per lo


stato finale si usa una circonferenza con un cerchio al suo interno.
Le transizioni possono essere decorate con un evento, che indica co-
sa deve accadere per il passaggio di stato, una condizione, che definisce
un vincolo aggiuntivo (oltre all’evento) per dar luogo alla transizione, e
un’azione, che indica un’attività eseguita in corrispondenza del passag-
gio di stato. Nessuna delle tre parti è obbligatoria: se manca l’evento, ab-
biamo una transizione di stato spontanea, cioè il sistema si porta nello
stato destinazione non appena si trova nello stato sorgente (e l’attività as-
sociata termina). L’assenza di condizione, implica che il verificarsi del-
l’evento è sufficiente per dar luogo alla transizione di stato. L’assenza
dell’azione significa che il sistema cambia stato senza fare nulla.
In UML, è possibile avere eventi e attività anche all’interno di uno sta-
to (Figura 1.22). Questo significa che il sistema è in grado di reagire a
eventi anche senza cambiare il proprio stato. Gli eventi particolari entry
ed exit consentono di associare attività particolari all’ingresso e all’usci-
ta dagli stati dell’automa. La differenza, quindi, tra un evento interno allo
stato e una transizione, subordinata al medesimo evento, che esce e rien-
tra nello stesso stato, è l’eventuale presenza di questi eventi (attività) par-
ticolari. All’interno di uno stato, è possibile anche avere attività non istan-
tanee11 (il cui tempo d’esecuzione non è trascurabile rispetto ai tempi del
sistema). Queste sono caratterizzate dalla parola chiave do, usata come
se fosse un evento interno, e indicano cosa fa il sistema mentre si trova in
quello stato. Gli stati, per i quali definiamo il comportamento relativo allo
pseudo evento do, sono chiamati stati di attività.

Stato

entry/ attività1
exit/ attività2
do/ attività3
evento1 [condizione]/ attività4
evento2/ attività5

Figura 1.22: Esempio di attività interne.

Come esempio, possiamo considerare la macchina a stati della Figu-


ra 1.23 che descrive il comportamento di una semplice macchina foto-
11
UML 1.x distingueva tra azioni istantanee e attività durature. In UML 2 si parla solo di
attività.
Page 28 CAPITOLO 1. INTRODUZIONE A UML 2

grafica digitale. Questo significa che dopo l’accensione, la macchina si


porta nello stato di Attesa. Appena l’utente inquadra un soggetto e pre-
me il tasto di scatto, la macchina può avere due comportamenti diversi
che dipendono dall’illuminazione dell’ambiente. Se la luce è sufficien-
te, la macchina scatta la fotografia, si porta nello stato di Memorizzazione
e dopo aver fatto quanto necessario per salvare l’immagine, torna nello
stato di Attesa. Se la luce non è sufficiente, non scatta, avvisa l’utente
(stato Warning) e si riporta in attesa. È importante notare che la transi-
zione tra Memorizzazione e Attesa non ha evento, ma Memorizzazione è
uno stato di attività e, quindi, la transizione può avvenire solamente do-
po aver completato l’attività interna. Anche la transizione tra Warning e
Attesa non ha evento, ma in questo caso c’è solo un’attività (istantanea)
associata all’ingresso nello stato. Questo significa che istantaneamente
la macchina raggiunge lo stato, segnala l’anomalia e si rimette in attesa.
L’utente può spegnere la macchina quando è in Attesa di scattare una
fotografia oppure durante la Memorizzazione. Non ha senso pensare di
spegnere la macchina fotografica mentre si trova in Warning perché è uno
stato istantaneo.

Attesa Warning
entry/visualizza
scatto [non luce]

scatto [luce]
spegni

Memorizzazione
spegni
do/memorizza

Figura 1.23: Esempio di macchina a stati.

Le analogie con gli statechart ci consentono di scomporre gerarchica-


mente gli stati di un automa. In altri termini, è possibile descrivere un sin-
golo stato attraverso un automa di livello inferiore. La potenza espressiva
di un automa “piatto” e di uno scomposto gerarchicamente è la medesi-
ma; ciò che cambia è la leggibilità e modularità del diagramma. Ad esem-
pio, la Figura 1.24 mostra la scomposizione dello stato Memorizzazione
della Figura 1.23 in Preparazione, Salvataggio e Notifica. Questi tre
sotto-stati sono “parte” di Memorizzazione, ma il processo inizia sempre
CAPITOLO 1. INTRODUZIONE A UML 2 Page 29

da Preparazione, e quindi è necessario un sotto-stato iniziale: possiamo


tornare in Attesa solo dopo aver completato la Notifica e possiamo spe-
gnere la macchina in qualunque momento (freccia che esce direttamente
da Memorizzazione).

Warning Attesa
spegni
entry/visualizza scatto [non luce]

scatto [luce] spegni

Memorizzazione

Preparazione Salvataggio Notifica


do/memorizza

Figura 1.24: Esempio di scomposizione gerarchica.

Gli stati possono anche essere suddivisi in più diagrammi ortogonali


che evolvono in parallelo. La Figura 1.25 presenta un semplice esempio
per chiarire il significato della scomposizione. Un convertitore di valute
può essere acceso o spento. Se è acceso, può essere in attesa di ricevere
la cifra da convertire (Attendi), oppure può visualizzare il valore ottenu-
to (visualizza). È vero anche che lo strumento può convertire da euro
a dollari o viceversa. Le due possibilità sono mutuamente esclusive, ma
è fondamentale sapere in quale configurazione si trova il sistema. Que-
sto può essere rappresentato con un secondo diagramma, in parallelo al
precedente, per tenere traccia della modalità corrente.
Lo stato di history —rappresentato da una H cerchiata— consente di
tener traccia della configurazione corrente al momento dello spegnimen-
to per poterla ripristinare non appena il convertitore dovesse essere riac-
ceso. La history può essere aggiunta a ogni stato che contiene sotto-stati12
e ci consente di variare lo stato iniziale del sistema in funzione della storia.
La freccia che esce dallo stato di history indica la configurazione iniziale
del sistema, quella da usare in assenza di storia.
Volutamente, in questo capitolo non trattiamo i sotto-automi, ovvero
l’uso di un intero automa all’interno di un altro, la terminazione esplicita
12
Questo vale sia per la scomposizione gerarchica che per la scomposizione in
diagrammi concorrenti.
Page 30 CAPITOLO 1. INTRODUZIONE A UML 2

On

Attendi invio Visualizza


invio

ConvertiEuro ConvertiDollari
euro dollari
H

spegni accendi

Off

Figura 1.25: Esempio di scomposizione in parallelo.

e la sincronizzazione tra flussi concorrenti. Il lettore interessato può far


riferimento a [Fow03] per una trattazione approfondita.
Prima di concludere il paragrafo, dobbiamo osservare che spesso, quan-
do si parla di stato di un oggetto, si fa riferimento a una particolare com-
binazione di valori per i suoi attributi. Un diagramma delle macchine a
stati non descrive tutte le possibili combinazioni, ma gli stati dell’oggetto
a un livello d’astrazione superiore, raggruppando eventualmente diverse
combinazioni in un unico stato logico.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 31

1.5 Attività

I diagrammi delle attività di UML 2 sono diventati molto più complessi


e sofisticati rispetto ai loro predecessori in UML 1.x. Essi descrivono la
logica procedurale, da qui la loro similitudine con i diagrammi di flusso,
ma anche processi di business e workflow.
I singoli passi —attività in UML 1.x— ora si chiamano azioni. Oltre
a un nodo iniziale, ed eventualmente uno finale, le azioni possono esse-
re organizzate in sequenza, in parallelo o in flussi alternativi. Una fork
ci consente di passare da un singolo flusso di esecuzione a più flussi che
evolvono in parallelo. L’eventuale sincronizzazione deve essere gestita da
un join. In entrambi i casi, usiamo una barra orizzontale, ma nel primo
abbiamo un flusso in ingresso e n in uscita; nel secondo abbiamo l’oppo-
sto. Inoltre, accanto a un join possiamo aggiungere una specifica di join
che definisce eventuali vincoli aggiuntivi per l’unificazione dei flussi pa-
ralleli; il predicato va scritto tra parentesi quadre. In modo simile, una
decisione ci consente di passare da un flusso sequenziale a una scelta tra
due alternative. I cammini in mutua esclusione si ricongiungono attra-
verso un merge. È buona norma avere azioni con un solo arco in ingres-
so e uno in uscita e indicare sempre in modo esplicito eventuali merge.
Questo serve per evitare fraintendimenti sul significato di archi multipli
in ingresso a un’azione.
Ad esempio, la Figura 1.26 mostra un semplice diagramma delle atti-
vità che identifica i passi principali per l’acquisto di un libro da un nor-
male sito di commercio elettronico. Dopo ScegliLibro, i tre flussi in pa-
rallelo corrispondono alla selezione della modalità di pagamento (carta
di credito o bonifico bancario), alla scelta del tipo di spedizione (posta o
corriere) e alla registrazione nel sistema per definire l’indirizzo cui reca-
pitare la merce. Il diagramma presenta volutamente queste tre attività in
parallelo per sottolineare che dal punto di vista del modello di business
non ci sono vincoli sulla loro “serializzazione”. Semplicemente, il sistema
richiede che vengano completate tutte prima di poter eseguire l’azione
EvadiOrdine.
I diagrammi di base possono essere arricchiti con molte altre informa-
zioni. Come prima cosa, possiamo usare le partizioni (dette anche swim-
lanes) per identificare il responsabile di un certo numero di azioni del
diagramma. Ogni corsia identifica un ruolo e la disposizione delle attività
nelle diverse corsie indica chi fa che cosa. Ad esempio, la Figura 1.27 rior-
ganizza una parte del processo d’acquisto della Figura 1.26 distinguen-
Page 32 CAPITOLO 1. INTRODUZIONE A UML 2

Scegli Libro

Scegli Scegli
Pagamento Spedizione

ImmettiDati

PagaCon PagaCon SpedisciCon Spedisci


Bonifico CartaDiCredito Corriere PerPosta

EvadiOrdine

Figura 1.26: Esempio di diagramma delle attività.


CAPITOLO 1. INTRODUZIONE A UML 2 Page 33

do tra le attività del Cliente, quelle della Banca e quelle dell’istituto che
emette la CartaDiCredito.

Cliente Banca CartaDiCredito

Scegli
Pagamento

PagaCon PagaCon
Bonifico CartaDiCredito

Figura 1.27: Esempio di diagramma delle attività con partizioni.

Oltre alle partizioni, possiamo caratterizzare meglio le singole attività


e usare simboli specifici per segnali temporali, ovvero attività che servo-
no per la sincronizzazione temporale del processo, segnali di accettazio-
ne, per evidenziare la ricezione di un evento dall’esterno oppure segnali
inviati, per identificare l’invio di eventi verso l’esterno (e magari verso al-
tri diagrammi delle attività). Ad esempio, la Figura 1.28 raffina il problema
della gestione del pagamento con carta di credito della Figura 1.26 eviden-
ziando l’invio della richiesta di autorizzazione (InviaRichiesta) e un’at-
tesa massima di cinque minuti per la conferma dell’autorizzazione, prima
di richiedere al cliente un’altra carta di credito (RichiestaAltraCarta).
Page 34 CAPITOLO 1. INTRODUZIONE A UML 2

PagaCon
CartaDiCredito

Ricevi
Invia Conferma
Richiesta
Richiesta
AltraCarta
Aspetta
5 min

Figura 1.28: Esempio di segnali.

Le singole attività possono essere scomposte per evidenziare quello


che entra, quello che esce e i passi necessari per trasformare gli ingressi
in uscite. Questo livello di dettaglio, in molti casi, appesantirebbe inu-
tilmente il diagramma; potrebbe essere utile invece evidenziare i dati o
gli oggetti che vengono scambiati dalle diverse attività. Anche se UML 2
propone almeno un paio di alternative per descrivere questi concetti, il
modo più semplice —e anche il più classico— quello di rappresentare
le informazioni scambiate attraverso nodi oggetto e di aggiungere archi
appositi che colleghino le attività ai nodi oggetto e quindi identifichino
il flusso dei dati all’interno dell’attività rappresentata nel diagramma. Ad
esempio, la Figura 1.29 mostra lo scambio di un oggetto Pagamento tra le
azioni PagaConCartaDiCredito e EvadiOrdine della Figura 1.26.

PagaCon
Pagamento EvadiOrdine
CartaDiCredito

Figura 1.29: Esempio di flusso di dati.

1.6 Componenti
Riguardo ai componenti in UML 2, occorre distinguere tra diagramma dei
componenti e diagramma di struttura composta. Nel primo caso, rap-
presentiamo un insieme di componenti, le loro interfacce implementa-
te e richieste e le relazioni tra queste. Nel secondo caso, descriviamo la
struttura interna di un componente.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 35

La differenza tra componenti e classi è una questione che non verrà


sicuramente risolta da questo libro. Anche il problema di partizionare
un’applicazione in componenti non ha una sola soluzione ed è spesso
frutto di una serie di compromessi dettati dalla volontà di riusare ele-
menti esistenti o tecnologie specifiche. UML 2 interpreta il concetto di
componente in modo più ampio rispetto alle versioni precedenti, dove i
componenti erano le strutture fisiche del sistema. Ora un componente
è una parte “significativa” di software. UML 2 propone anche una rap-
presentazione diversa. Il simbolo tipico dei componenti di UML 1.x resta
per continuità, ma non è più corretto. La nuova versione modella i com-
ponenti con un rettangolo, che si differenzia da quello usato per le classi
per il vecchio simbolo nell’angolo in alto a destra oppure per l’uso della
parola chiave component.

<<component>>
Interfaccia
Utente

<<component>>
<<delegate>> ServerInformazioni

<<component>> <<component>> <<component>>


<<delegate>>
Gestore Driver Agenzia
Informazioni Informazioni

Figura 1.30: Esempio di diagramma dei componenti.

Con quest’accezione, un diagramma dei componenti serve per de-


scrivere, o documentare, la struttura interna di un’applicazione o di una
sua parte. Ad esempio, la Figura 1.30 identifica i componenti principali
di un’applicazione per la diffusione di informazioni. L’utente interagisce
con il sistema attraverso un componente dedicato (l’InterfacciaUtente)
che utilizza i servizi del GestoreInformazioni, il quale a sua volta, prele-
va le informazioni dalle agenzie esterne (rappresentate dal componente
AgenziaInformazioni). L’interazione non avviene direttamente, ma at-
traverso un Driver specifico che si occupa di gestire i problemi di tradu-
zione e trasformazione delle informazioni ricevute dalle agenzie esterne.
Il GestoreInformazioni e il Driver definiscono, a loro volta, il
ServerInformazioni. I quadratini posti sui bordi dei componenti iden-
Page 36 CAPITOLO 1. INTRODUZIONE A UML 2

tificano le porte del componente e definiscono opportuni “punti di con-


tatto”. Ogni porta può offrire o richiedere diverse interfacce.

1.6.1 Strutture composte


UML 2 offre la possibilità di scomporre gerarchicamente una classe o un
componente, evidenziandone la struttura interna. Questi diagrammi so-
no utili quando si vuole caratterizzare una classe (componente) in termini
di organizzazione interna, interfacce ed, eventualmente, porte.

ISelezione
IDisplay
Prodotti

SceltaProdotto IContaSoldi

Distributore
Caffè Controllo
Pagamento
IGettoniera
SelezioneBevanda

ILatte ICaffè

Figura 1.31: Esempio di diagramma di struttura composta.

La Figura 1.31 mostra le interfacce della classe DistributoreCaffè, or-


ganizzate in porte per raggruppare interfacce simili. La SceltaProdotto
fornisce l’interfaccia per la ISelezioneProdotti13 , ma richiede la possi-
bilità di controllare il IDisplay. Similmente, il ControlloPagamento offre
l’interfaccia IContaSoldi e richiede il controllo della IGettoniera per i
resti. Infine, la SelezioneBevanda controlla l’erogazione del ILatte e del
ICaffè.
La classe può essere “esplosa” per modellare gli elementi interni. La Fi-
gura 1.32 è lo spaccato della Figura 1.31 e mostra che il DistributoreCaffè
è composto da quattro classi (non oggetti) che gestiscono e controllano le
diverse parti. Il diagramma evidenzia anche le molteplicità dei compo-
nenti (numero in alto a destra) e le deleghe (delegate), cioè l’elemen-
to interno che offre o richiede l’interfaccia particolare. In questo caso, la
classe Prodotto gestisce l’interazione con l’utente (ISelezioneProdotti
e IDisplay) e coordina poi le altre parti: Soldi, per la gestione dei paga-
menti e Latte e Caffè per miscelare i due prodotti.
13
Per semplicità di lettura, i nomi delle interfacce iniziano sempre con la I maiuscola.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 37

ISelezione IDisplay
Prodotti

<<delegate>> <<delegate>>

1
Prodotto

IContaSoldi
<<delegate>>
1
Soldi
ILatte <<delegate>>
<<delegate>> 1
Latte
IGettoniera

ICaffè
<<delegate>> 1
Caffè

DistributoreCaffè

Figura 1.32: Spaccato di una struttura composta.

1.7 Deployment
Se l’applicazione software non è banale ed è distribuita, l’allocazione dei
pezzi del sistema sugli elementi di calcolo diventa rilevante. UML risol-
ve il problema proponendo i diagrammi di deployment. Questi, anche
se molto semplici, fotografano la distribuzione del sistema e sono uti-
li per progettare l’allocazione dei componenti, ma anche per chiarire la
configurazione (software) di sistemi complessi.
I nodi rappresentano gli elementi preposti all’esecuzione del software
e si dividono in: dispositivi, computer o componenti hardware più sem-
plici in grado di eseguire software, e ambienti di esecuzione, elementi
software in grado di eseguire altro software (ad esempio, un processo del
sistema operativo). I nodi contengono altri nodi e gli elaborati (artifact),
tipicamente i file di cui si compone l’applicazione. File eseguibili, pagine
HTML, file di configurazione e file dati sono alcuni esempi di elaborati.
I nodi sono collegati da canali di comunicazione per specificare le in-
terconnessioni tra gli elementi di calcolo del sistema. Questi possono es-
sere anonimi, oppure si possono usare etichette specifiche, tipo http/LAN
per identificare il protocollo e l’infrastruttura di comunicazione. Nodi ed
elaborati possono anche essere etichettati (tagged values) per fornire in-
Page 38 CAPITOLO 1. INTRODUZIONE A UML 2

Browser WebServer
http/Internet {webServer = apache}

MS Explorer
mioWeb.war

Java RMI/LAN

ServerPrincipale

Application
DataServer
Server
ODBC
mioArchivio mioBase.ear

Figura 1.33: Esempio di diagramma di deployment.

formazioni aggiuntive sull’elemento: ad esempio, il numero di istanze del


singolo nodo, il tipo e la versione del sistema operativo o il tipo di ser-
ver, se stiamo parlando di ambienti di esecuzione, oppure il componente
implementato o la particolare tecnologia utilizzata, se ci riferiamo a un
elaborato.
Ad esempio, la Figura 1.33 presenta la tipica organizzazione di un’ap-
plicazione web distribuita su tre calcolatori. L’utente utilizza un Browser
sul proprio PC. Questo interagisce con il WebServer, che sta su un server
dedicato, il quale a sua volta interagisce con l’ApplicationServer. L’ulti-
ma macchina si fa carico sia dell’ApplicationServer che del DataServer,
cioè del componente software (base di dati) preposto all’immagazzina-
mento dei dati.

1.8 OCL
Purtroppo, non sempre un diagramma UML riesce a descrivere la solu-
zione proposta con il livello di precisione che sarebbe richiesto. Alcune
volte, è un problema di modellazione, ma spesso dobbiamo arrenderci ai
limiti degli elementi grafici visti finora. Ad esempio, se volessimo istanzia-
re il diagramma delle classi della Figura 1.34, sappiamo che ogni Persona
lavora per una sola Azienda e può avere un superiore, ma il diagramma
CAPITOLO 1. INTRODUZIONE A UML 2 Page 39

non impone il vincolo ovvio che una persona e il suo capo devono lavora-
re per la stessa azienda. Le informazioni contenute nel diagramma delle
classi non ci consentono di escludere istanziazioni in cui una persona e il
suo capo lavorano per aziende diverse.

dirige

subalterni 0..*

Persona Azienda
superiore - nome: String
- nome: String
0..1 - cognome: String 0..* lavora 1 - fatturato: int
- anni: int - numDipendenti: int
dipendenti azienda
+ assume(p: Persona): int
...

Figura 1.34: Diagramma delle classi d’esempio.

Come già sappiamo, UML ci consente di aggiungere commenti agli


elementi di un diagramma e, quindi, i vincoli appena descritti potrebbero
diventare commenti opportuni. Non sempre, però, una frase in italiano —
o in una qualunque altra lingua— riesce a essere sufficientemente chiara
e precisa e, di conseguenza, il vincolo resterebbe vago e interpretabile in
modi diversi.
Per evitare tutto questo, UML propone OCL (Object Constraint Lan-
guage [WK03]), un linguaggio testuale, preciso e rigoroso, per esprimere
vincoli ed espressioni sugli elementi di un modello a oggetti. In questo
libro, non vogliamo dire che un diagramma UML non è completo senza
un certo numero di vincoli OCL, ma ci sembra opportuno proporne l’uso
per migliorare il livello di precisione dei modelli descritti.
OCL è un linguaggio di specifica con una sintassi simile a quella dei
principali linguaggi a oggetti. La valutazione di un’espressione OCL non
ha effetti sul sistema e non cambia alcun valore. Un vincolo definisce
solamente lo stato in cui dovrebbe trovarsi un certo oggetto, oppure il
comportamento di una certa operazione. Se uno o più vincoli non do-
vessero essere verificati, potremmo solamente dire che il comportamento
del sistema non è conforme alle specifiche. La causa dell’inconsistenza, e
l’eventuale soluzione, sono a carico del progettista.
Un vincolo è un’espressione booleana che pone delle condizioni su
uno o più valori. Le espressioni usano i soliti operatori matematici, i con-
nettori logici (and, or, not, implies, ecc.), i metodi definiti nel modello
considerato e altre funzioni specifiche: ad esempio, allInstances() re-
stituisce l’insieme di oggetti creati a partire da una classe e oclInState()
Page 40 CAPITOLO 1. INTRODUZIONE A UML 2

ci permette di sapere se un oggetto si trova in un certo stato. Inoltre, OCL


offre i tipi predefiniti: Integer, Boolean, Real e String.
Il vincolo più semplice che possiamo definire è un invariante su un
singolo attributo. Se consideriamo l’attributo anni della classe Persona
della Figura 1.34, possiamo scrivere:

context Persona
inv: anni >= 0

Ogni vincolo OCL è sempre definito in un contesto e viene valutato


per un’istanza particolare del contesto stesso. Ad esempio, un invarian-
te è sempre associato a una classe, ma è valutato per ogni suo ogget-
to. La parola self identifica l’istanza generica su cui applicare il vincolo.
Nell’esempio appena visto, l’espressione ha come contesto l’intera clas-
se Persona e l’invariante (inv) impone che ogni istanza della classe deb-
ba sempre avere anni maggiore o uguale a zero. In questo caso, abbia-
mo usato la forma contratta, invece di self.anni >= 0, per semplicità e
compattezza.
Le espressioni possono anche far riferimento agli oggetti associati. Se
volessimo dire che ogni persona deve lavorare per un’azienda con un
fatturato maggiore di 10.000 euro, potremmo scrivere:

context Persona
inv: self.azienda.fatturato > 10.000

il ruolo azienda —dell’associazione lavora— identifica l’Azienda per cui


self lavora e fatturato è un attributo dell’azienda. La molteplicità del-
l’associazione lavora verso Azienda ci permette di sapere che per ogni
persona esiste una e una sola azienda.14 Chiaramente, la proprietà rela-
tiva al fatturato avrebbe potuto essere definita anche come un invariante
della classe Azienda.
A questo punto, possiamo anche usare OCL per formalizzare il vincolo
che una Persona e il suo superiore devono lavorare per la stessa Azienda:

context Persona
inv: self.azienda = self.superiore.azienda

Quando le molteplicità delle associazioni sono maggiori di 1, il vinco-


lo OCL deve gestire opportunamente le collezioni di oggetti riferiti. OCL
14
Le espressioni OCL possono usare indifferentemente il nome di un ruolo, di un’as-
sociazione o della classe associata, scritta in minuscolo. L’importante è non generare
confusione e ambiguità.
CAPITOLO 1. INTRODUZIONE A UML 2 Page 41

usa la freccia (->), e non il punto (.) per richiamare le operazioni sulle col-
lezioni di oggetti. La lista di operazioni offerte da OCL per la gestione di
insiemi di elementi è abbastanza ampia, ma la natura introduttiva di que-
sto paragrafo non ci consente di analizzare ogni singolo dettaglio. Un’e-
spressione può predicare sull’intera collezione o sui singoli membri. Nel
primo caso, considerando il ruolo dipendenti dell’associazione lavora
del diagramma della Figura 1.34, possiamo scrivere:

context Azienda
inv: self.dipendenti->size() <= 1000

per dire che ogni azienda non deve mai avere più di mille dipendenti. L’o-
perazione size() restituisce la cardinalità dell’insieme considerato. Se
volessimo analizzare gli oggetti che definiscono l’insieme di dipenden-
ti potremmo usare l’operazione includes(), per sapere se un certo og-
getto appartiene alla collezione, i quantificatori universale (forAll()) ed
esistenziale (exists()) per definire proprietà che devono valere per tutti
gli elementi, o almeno per un elemento, della collezione. Per dire, quin-
di, che ogni dipendente dell’azienda deve essere maggiorenne, possiamo
scrivere:

context Azienda
inv: self.dipendenti->forAll(p: Persona | p.anni >= 18)

considerando la didatticità dell’esempio, abbiamo usato l’espressione com-


pleta all’interno del quantificatore. In generale, se non si creano ambi-
guità, possiamo omettere la dichiarazione delle variabili parzialmente (p
| p.anni >= 18) o completamente (p.anni >= 18).
Volendo “unire” i due invarianti di Azienda appena descritti, possiamo
usare un and per definire un’unica espressione come la congiunzione del-
le precedenti, oppure possiamo semplicemente associare più invarian-
ti alla stessa classe. Il risultato e’ esattamente uguale nei due casi: tutte
le espressioni OCL definite devono sempre valere per qualunque oggetto
della classe Azienda.
Oltre alle operazioni appena viste, per “ragionare” sulle collezioni pos-
siamo anche usare: sum() per fare la somma di tutti gli elementi della
collezione, select() per filtrare (selezionare) gli elementi della collezio-
ne, isUnique() per controllare che il parametro abbia un valore diverso
per ogni elemento dell’insieme, iterate() per iterare sugli elementi della
collezione, union() per fare l’unione con altre collezioni, isEmpty() per
sapere se la collezione è vuota e notEmpty() per sapere se non è vuota.
Page 42 CAPITOLO 1. INTRODUZIONE A UML 2

Le pre- e post-condizioni predicano sulle singole operazioni, definen-


do opportune condizioni sugli attributi e le associazioni dell’oggetto che
esegue l’operazione. Una pre-condizione definisce una condizione che
deve essere verificata all’atto dell’invocazione dell’operazione e può an-
che far riferimento ai suoi parametri attuali. Una post-condizione defini-
sce una condizione che deve valere dopo aver eseguito l’operazione e può
usare la parola result, per identificare il risultato dell’operazione, e pre,
per ottenere il valore di una proprietà prima dell’esecuzione dell’opera-
zione. Come esempio, possiamo definire la pre e post-condizione per il
metodo assume della classe Azienda:

context Azienda::assume(p: Persona): int


pre: not self.dipendenti->includes(p)
post: self.dipendenti->includes(p) and
result = self.dipendenti->size()@pre + 1

Il contesto dei vincoli è il metodo che vogliamo definire. La pre-


condizione controlla che p non sia già un dipedente dell’azienda. La post-
condizione aggiunge p all’insieme dei dipendenti e restituisce il nuovo
numero di dipendenti dell’azienda, calcolato sommando uno alla vecchia
(@pre) dimensione dell’insieme dipendenti.
Volendo aumentare il grado di precisione, rispetto a quanto descrit-
to nel Paragrafo 1.1, OCL ci consente anche di definire espressioni per
formalizzare le regole di derivazione per attributi e associazioni. Per dire
che numDipendenti dev’essere pari alla cardinalità dell’insieme di Persona
legate all’Azienda dall’associazione dipendenti, possiamo scrivere:

context Azienda::numDipendenti: int


derive: self.dipendenti->size()

il contesto dell’espressione è l’attributo che vogliamo definire e la parola


derive introduce la regola di derivazione. Oltre alle regole di derivazione
possiamo usare OCL anche per descrivere i valori iniziali degli attribu-
ti e delle associazioni del modello. Per dire che l’attributo anni di ogni
Persona appena nata è zero, possiamo scrivere:

context Persona::anni: int


init: 0

mentre per dire che un’Azienda appena creata non ha dipendenti, pos-
siamo inizializzare l’associazione lavora —usando il ruolo dipendenti—
dicendo:
CAPITOLO 1. INTRODUZIONE A UML 2 Page 43

context Azienda::dipendenti: Set(Persona)


init: Set{}

dipendenti è tradotto come un insieme di persone (Set(Persona)) e che


il suo valore iniziale è l’insieme vuoto.

Persona Prestito
- nome: String - ammontare: int
- cognome: String 1 0..* - rata: int
- codFiscale: String - dataInizio: Date
intestatario prestiti
- stipendio: int - dataFine: Date
+ chiedePrestito(somma: int): void
...
0..* clienti 0..* prestiti

1 banca

0..* Banca
banche

Figura 1.35: Diagramma delle classi d’esempio.

Come esempio conclusivo, è interessante ragionare sulle classi Persona,


Prestito e Banca della Figura 1.35. Considerando la semplicità del pro-
blema, sappiamo che:

• per ottenere un prestito da una banca bisogna esserne, o diventarne,


clienti;
• la data di inizio del prestito deve precedere quella di fine;
• il codice fiscale dei clienti deve essere unico;
• una banca dovrebbe concede prestiti solo ai clienti in grado di ono-
rarli.

Per l’ultimo punto, possiamo ipotizzare che la banca conceda un nuo-


vo prestito solo se la somma delle rate mensili dei prestiti già accesi dal
richiedente non superano il 30% del suo stipendio.
Tutte queste informazioni, fondamentali per un corretto utilizzo del
modello, non possono essere rappresentate direttamente nel diagramma
delle classi. Attualmente, molto spesso ci accontentiamo di commenti
scritti in italiano, e quindi paghiamo l’informalità della lingua utilizza-
ta. Con OCL potremmo rappresentare i vincoli elencati in precedenza in
modo preciso e formale.
Page 44 CAPITOLO 1. INTRODUZIONE A UML 2

Un prestito è concesso a un cliente della banca se:

context Prestito
inv: self.banca.clienti->includes(self.intestatario)

l’intestatario del prestito (self) è contenuto nell’insieme dei clienti


della banca (self.banca) che eroga il prestito.
La data di inizio di un prestito precede quella di fine se:

context Prestito
inv: self.dataInizio < self.dataFine

le due date del prestito (self) sono nella giusta relazione d’ordine.
Il codice fiscale dei clienti deve è unico se:

context Persona
inv: Persona.allInstances()->isUnique(codFiscale)

il valore del codice fiscale (codFiscale) di self è unico (operazione isUnique)


all’interno di tutti gli oggetti della classe Cliente; l’operazione allInstances
restituisce l’insieme delle istanze di una classe. In alternativa, possiamo
riformulare il vincolo precedente usando un quantificatore universale al
posto dell’operazione isUnique:

context Persona
inv: Persona.allInstances()->forAll(p1, p2: Persona |
p1.codFiscale = p2.codFiscale implies p1 = p2)

l’uso del quantificatore universale ci consente di dire che per ogni coppia
(c1 e c2) di clienti, se il loro codFiscale è uguale allora —implicazione
(implies)—i due clienti sono uguali, cioè sono lo stesso cliente.
Una banca concede prestiti solo ai clienti in grado di onorarli se:

context Persona::chiedePrestito(somma: int) : void


pre: self.prestiti.rata->sum() <= self.stipendio * .30

la pre-condizione del metodo chiedePrestito impone che la somma del-


le rate dei prestiti in corso del cliente (self) che chiede un nuovo prestito
non superi il 30% del suo stipendio.
Page 45

Capitolo 2

Esercizi introduttivi

La conoscenza di UML non è sufficiente per realizzare un “buon” progetto


a oggetti. Oltre agli aspetti sintattici, dobbiamo risolvere i problemi di
modellazione e, tra questi, il primo riguarda l’identificazione dell’insieme
di classi che risolvono il problema posto.
UML non offre alcun metodo per l’identificazione delle classi: è un
linguaggio standard per rappresentare i risultati delle attività di model-
lazione. Il metodo per arrivare al risultato —i diagrammi UML— dipen-
de da molti fattori: il problema da risolvere, l’esperienza e le abitudini
del progettisti, la qualità del documento dei requisiti da cui si parte e an-
che, eventualmente, la tecnologia che si prevede di utilizzare per realizza-
re l’applicazione. A questo proposito, prima di presentare un approccio
strutturato, questo capitolo fornisce alcune semplici regole pratiche che
possiamo utilizzare per identificare le classi (e gli altri elementi che ri-
teniamo opportuni per realizzare le nostre applicazioni) e un insieme di
esercizi esemplificativi di difficolta’ crescente.
L’obiettivo è duplice: richiamare i diversi elementi dei modelli e forni-
re un punto di partenza per il lettore che non abbia grande dimestichezza
con il linguaggio. La semplicità degli esercizi consente di fare esperienza
dell’uso dei diversi diagrammi ed elementi offerti da UML, rimandando
ai capitoli successivi l’attenzione per gli aspetti metodologici, progettuali
e “creativi” della modellazione.
In questi esercizi introduttivi, l’obiettivo principale è identificare le
classi per risolvere il problema proposto. A questo fine, dobbiamo ricor-
dare che ogni classe deve identificare un concetto ben preciso. Il nome
deve essere sintetico e caratterizzare al meglio tutte le possibili istanze
della classe. I nomi composti potrebbero celare classi strane: l’uso di un
Page 46 CAPITOLO 2. ESERCIZI INTRODUTTIVI

sostantivo e aggettivo è normale, ma nomi quali CaneGatto1 sono indica-


tori di cattiva modellazione. La classe non deve essere un monoblocco di
proprietà. Più la classe è piccola e sfrutta le relazioni con le altre classi, più
è usabile. Questo non significa che tutte le classi devono essere piccolis-
sime, ma è un suggerimento per trovare il giusto rapporto tra dimensioni,
numero di classi e riuso. Inoltre, la classe è spesso funzione del problema
che si vuole risolvere: classi “generiche” facilitano il riuso, ma impongono
lunghe gerarchie di ereditarietà. Il giusto equilibrio tra specificità e gene-
ricità non è facile, ma l’obiettivo è risolvere il problema e non usare tutti
gli strumenti della modellazione a oggetti che si conoscono.
Per semplicità, ma anche per consuetudine, quando si presentano mo-
delli al livello di dettaglio considerato in questo capitolo, i diagrammi pro-
posti non contengono i costruttori “standard” delle classi e i metodi per
leggere e modificare gli attributi privati.

2.1 Valutazione di polinomi


Progettiamo un programma per la valutazione di polinomi. Un polino-
mio, identificato da una lettera minuscola dell’alfabeto, è dato dalla som-
ma di uno o più monomi. Un monomio è caratterizzato da un segno,
un coefficiente e un grado. Il programma deve leggere il nome del po-
linomio p e il valore della variabile per il quale si vuole calcolare il valo-
re di p. Ad esempio, dato il polinomio f : 3x4 − 5x3 + 2x + 1, l’utente
deve inserire da tastiera: f 2 e il programma deve stampare 13, poiché
f (2) = 3 × 24 − 5 × 23 + 2 × 2 + 1.

Soluzione Anche se l’esercizio è molto semplice, può servire come pun-


to di partenza per capire come applicare un approccio sintattico per l’i-
dentificazione delle classi.
Come punto di partenza, questo approccio richiede un buon docu-
mento di specifica dei requisiti o una buona descrizione degli scenari:
tutti i sostantivi che si incontrano sono candidati per diventare oggetti o
classi, mentre i verbi potrebbero identificare relazioni tra classi od opera-
zioni che dovrebbero essere fornite dalle classi. Un approccio puramente
meccanico non dà risultati significativi; l’uso dei nomi presenti nella spe-
cifica rappresenta un buon inizio, ma la qualità del risultato dipende dalla
qualità e della completezza del testo di partenza.
1
L’esempio è volutamente esagerato per enfatizzare il problema.
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 47

Concentrandosi sull’esercizio, la frase: “Un polinomio, identificato da


una lettera minuscola dell’alfabeto, è dato dalla somma di uno o più mo-
nomi.” ci suggerisce la necessità di una classe Polinomio e di una clas-
se Monomio. La relazione tra le due potrebbe essere una semplice asso-
ciazione, ma vista la dipendenza preferiamo caratterizzare la relazione e
usare una composizione. Un’aggregazione andrebbe bene se volessimo
monomi indipendenti (esistenzialmente) rispetto al polinomio cui appar-
tengono. La seconda frase: “Un monomio è caratterizzato da un segno,
un coefficiente e un grado.” definisce gli attributi della classe Monomio. Il
coefficiente e il grado sono chiaramente due numeri interi. Il segno me-
rita una valutazione più attenta: se seguissimo le specifiche alla lettera,
queste ci porterebbero alla definizione di un attributo segno (ad esempio,
un enumerativo i cui valori potrebbero essere ‘+’ e ‘-’), ma quest’ipotesi
complicherebbe inutilmente la soluzione. Il segno può essere facilmente
nascosto nell’attributo coefficiente, semplificando la gestione dell’arit-
metica dei coefficienti. L’ultima frase: “Il programma deve leggere il nome
del polinomio p e il valore della variabile per il quale si vuole calcolare
il valore di p.” descrive l’operazione principale che deve svolgere il pro-
gramma. Possiamo, quindi, prevedere un metodo valore associato sia
alla classe Polinomio che alla classe Monomio. Il primo metodo, chiamato
su un polinomio p, usa il secondo per avere i valori dei monomi che com-
pongono p e li somma. L’ultima frase è un esempio di funzionamento del
programma e non contribuisce all’identificazione delle classi.
Il risultato è riassunto dal diagramma delle classi della Figura 2.1. Pur
non volendo modellare l’interfaccia per definire i polinomi e visualizza-
re i risultati, il diagramma propone due costruttori particolari per crea-
re un Polinomio a partire da due array di interi, che corrispondono ai
coefficienti e ai gradi dei monomi, e un Monomio dati il coefficiente e il
grado.

Polinomio Monomio
- nome: char 1..* - coefficiente: int
- grado: int
+ Polinomio(ac: int[], ag: int[]) + Monomio(c: int, g: int)
+ valore(v: int): int + valore(v: int): int

Figura 2.1: Diagramma delle classi per la risoluzione di polinomi.


Page 48 CAPITOLO 2. ESERCIZI INTRODUTTIVI

2.2 CAD tridimensionale


Progettiamo un’applicazione di CAD tridimensionale. L’applicazione per-
mette di gestire un modello tridimensionale composto da una serie di og-
getti. Ogni oggetto è caratterizzato da una posizione tridimensionale, da
un colore e da un materiale. Esistono tre oggetti di base: sfere, parallele-
pipedi e tetraedri. È altresı̀ possibile avere oggetti complessi composti da
un insieme di oggetti (a loro volta complessi o di base). Ad esempio, due
sfere potrebbero creare un rudimentale “pupazzo di neve”. Deve essere
possibile la creazione di oggetti complessi, l’aggiunta di nuovi elementi a
un oggetto complesso, l’aggiunta di nuovi oggetti al modello tridimensio-
nale, lo spostamento di oggetti e la loro rotazione rispetto a un asse (x, y o
z).

Soluzione Il testo dell’esercizio non è sufficientemente dettagliato e dob-


biamo aggiungere un paio di vincoli prima di iniziare a lavorare a una
possibile soluzione:
• i parallelepipedi hanno sempre le facce parallele ai piani cartesiani;

• il triangolo di base dei tetraedri è sempre equilatero e parallelo a uno


dei piani identificati dagli assi cartesiani.
Anche in questo caso possiamo ragionare sulla specifica per identifi-
care le classi necessarie. La classe Modello3D è il punto di partenza e con-
tiene (relazione di composizione) una serie di elementi di classe Oggetto,
ognuno caratterizzato da una posizione tridimensionale, un colore e un
materiale. Il primo attributo impone una nuova classe Posizione3D, i
cui attributi sono tre interi che corrispondono alle coordinate x, y e z. L’u-
so di una classe specifica ci consente di “delegare” tutti gli aspetti relativi
alla gestione dei punti nello spazio e, quindi, otteniamo un modello più
pulito.
La classe Oggetto deve essere specializzata in Sfera, Parallelepipedo
e Tetraedro e diventa astratta. La posizione della Sfera è il centro e l’u-
nico attributo specifico è il raggio. Ogni Parallelepipedo è caratteriz-
zato dai tre lati (latoX, latoY e latoZ); la posizione è il centro del soli-
do. Gli attributi asse, altezza e lato (del triangolo di base) definiscono i
Tetraedri; la posizione di un Tetraedro è il vertice del solido.
La possibilità che un oggetto composto contenga altri oggetti, sem-
plici o composti, introduce la classe OggettoComposto e le relazioni con
Oggetto descritte nella Figura 2.2.
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 49

Oggetto Oggetto
Composto * 1..*

Figura 2.2: Relazioni tra Oggetto e OggettoComposto.

In questo modo, un OggettoComposto è una sottoclasse di Oggetto, ma


è anche vero che è un aggregato di Oggetti, quindi sia semplici che com-
posti. Questa coppia di relazioni (ereditarietà e aggregazione) rappresen-
ta una soluzione ricorrente per descrivere le situazioni in cui vogliamo
modellare una gerarchia di contenimento tra elementi.
L’ultima parte del testo definisce le operazioni possibili su un Modello3D.

• Per creare oggetti complessi possiamo far ricorso al costruttore della


classe OggettoComposto2 , cui passiamo un array di elementi Oggetto
come parametro: questi sono i componenti del nuovo oggetto da
creare.

• Per aggiungere nuovi oggetti a un oggetto composto, abbiamo de-


finito il metodo aggiungi() associato alla classe OggettoComposto,
ma considerando la gerarchia di ereditarietà, la stessa definizione
di metodo deve essere associata anche alla classe Oggetto. Que-
sta non fornisce alcuna implementazione del metodo perché è una
classe astratta, ma in questo modo possiamo avere polimorfismo e
binding dinamico su oggetti il cui tipo statico è Oggetto, ma quello
dinamico è OggettoComposto.

• Per aggiungere nuovi oggetti a un modello tridimensionale, abbia-


mo definito il metodo inserisci() per la classe Modello3D, che ri-
chiede l’oggetto (o) da aggiungere come parametro.

• Per spostare un oggetto, il metodo sposta() della classe Modello3D


richiede l’oggetto da spostare (o), l’asse (a) lungo cui spostate l’og-
getto e la lunghezza (l) dello spostamento. Questo metodo chiama
il metodo sposta() della classe Oggetto, il quale a sua volta chiama
il metodo sposta() della classe Posizione3D.

• Per ruotare un oggetto, il metodo ruota() della classe Modello3D ri-


chiede l’oggetto da ruotare (o) e l’asse (a) attorno al quale l’oggetto
2
In questo caso esplicitiamo il costruttore perché non è quello standard, ma richiede
parametri particolari.
Page 50 CAPITOLO 2. ESERCIZI INTRODUTTIVI

deve essere ruotato di 180 gradi. Questo metodo chiama il metodo


ruota() della classe Oggetto, il quale a sua volta chiama il metodo
ruota() della classe Posizione3D.

Il risultato finale è riassunto dal diagramma delle classi della Figu-


ra 2.3.

Modello3D

+ inserisci(o: Oggetto): boolean


+ sposta(o: Oggetto, a: char, l: int): boolean
+ ruota(o: Oggetto, a: char): boolean

Oggetto
OggettoComposto Posizione3D
- colore: String
1 - posX: int
- materiale: String 1 posizione
- posY: int
+ OggettoComposto(c: Object[]) * 1..* + aggiungi(o: Oggetto): boolean - posZ: int
+ aggiungi(o: Oggetto): boolean + sposta(a: char, l: int): boolean + sposta(a: char, l: int): boolean
+ ruota(a: char): boolean + ruota(a: char): boolean

Sfera Parallelepipedo Tetraedro


- raggio: int - latoX: int - asse: char
- latoY: int - altezza: int
- latoZ: int - lato: int

Figura 2.3: Diagramma delle classi per il CAD tridimensionale.

2.3 Rivendita di auto usate


Progettiamo un’applicazione per la gestione di una rivendita di auto usa-
te. Un automezzo viene identificato dalla targa, dal numero di telaio e da
un certo numero di caratteristiche specifiche, quali il colore, la cilindra-
ta, il tipo di carburante e gli optional. Ogni automezzo è caratterizzato da
una “carta di identità” che definisce l’anno d’immatricolazione, il nume-
ro di chilometri e la data dell’ultima revisione. Il sistema gestisce anche
camion e van, che si differenziano dalle automobili per la capacità di ca-
rico (quintali o persone). Il sistema cataloga anche i clienti, con le solite
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 51

caratteristiche: nome, cognome, indirizzo e codice fiscale. Un cliente può


stipulare uno o più contratti per acquistare uno o più automezzi. Ogni
contratto deve avere una data di stipula, un ammontare, una data di ini-
zio validità ed eventuali dilazioni di pagamento pattuite tra le parti. Il
sistema deve anche gestire lo storico dei diversi automezzi, cioè la storia
del mezzo che contiene tutti i passaggi di proprietà noti al rivenditore.

Soluzione L’esercizio proposto è un tipico esempio di applicazione for-


temente basata sui dati: il problema principale è la modellazione delle
relazioni tra classi e dei loro attributi, rispetto alla definizione delle ope-
razioni associate. Un’ipotesi di lavoro è di partire da uno schema entità-
relazioni (che è uno strumento tipico per la modellazione dei dati) e di
aggiungere i metodi in un secondo tempo, quando la struttura dei dati è
ben definita.
In questi casi, ipotizzando alcune regole di corrispondenza, le entità
diventano classi cui dobbiamo aggiungere il comportamento, cioè i meto-
di. Le relazioni solitamente diventano associazioni, ma potrebbero anche
essere viste come aggregazioni o composizioni; la scelta dipende dal mes-
saggio che si vuole comunicare con la relazione. Le relazioni con attribu-
ti sono, invece, tradotte usando opportune classi associazione. Durante
la traduzione, dobbiamo anche fare attenzione alle diverse convenzioni
per descrivere le cardinalità associate agli estremi delle relazioni (asso-
ciazioni). Gli attributi composti diventano classi e si aggiunge un’asso-
ciazione per legare la classe attributo alla classe entità. Generalizzazio-
ni e sottoinsiemi restano generalizzazioni. Nel primo caso, la traduzione
è ovvia, mentre nel secondo caso dovremo aggiungere un vincolo (sotto
forma di commento). Gli identificatori interni, esterni e misti non vanno
tradotti. Ogni oggetto ha un’identità propria e non occorre definire regole
particolari per identificare le diverse istanze delle entità.
Stabilite queste semplici regole, i requisiti dell’esercizio possono esse-
re modellati con lo schema della Figura 2.4. Il diagramma entità-relazioni
si traduce quasi automaticamente nel diagramma delle classi della Figu-
ra 2.5.
Ogni Automezzo è caratterizzato da: numTelaio, targa, cilindrata,
carburante e colore. Van e Camion sono sottoclassi di Automezzo e ag-
giungono alla superclasse la portata come numPersone e quintali, rispet-
tivamente.
Ogni Automezzo è caratterizzato da una CartaDiIdentità, che con-
tiene l’annoImmatricolazione, il numChilometri e la dataRevisione, cioè
Page 52 CAPITOLO 2. ESERCIZI INTRODUTTIVI

annoImmatricolazione
numChilometri
dataRevisione

Carta
DiIdentità descrizione

colore ID nome
targa
carburante
numTelaio
cilindrata 1..n
inclusione Optional
0..n
Automezzo

1..n 1..n 0..n


oggetto Contratto con-dil

0..1
dataInizio codice
Camion Van 1..1
dataStipula ammontare
stipula Dilazione

quintali numPersone 1..n


codice
codiceFiscale
ammontare
nome Anagrafica Cliente data

cognome

Indirizzo

numero
CAP
via
città

Figura 2.4: Schema entità-relazioni per la rivendita di automobili.


CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 53

la data dell’ultima revisione. In questa soluzione, abbiamo anche scel-


to di rappresentare gli optional attraverso una classe opportuna. Ogni
Optional ha un nome e una descrizione. Una soluzione alternativa, e
più compatta, avrebbe rappresentato i diversi optional come un attribu-
to della classe Automezzo, il cui tipo avrebbe potuto essere un array di
stringhe.

CartaDiIdentità Dilazione

- annoImmatricolazione: int - ammontare: int


- numChilometri: int - data: Date
- dataRevisione: Date

Van 1 1..*
- numPersone: int

1 1

Automezzo Contratto
- targa: String 1..* 1..* - ammontare: int
- numTelaio: String - dataStipula: Date
- colore: String - dataInizio: Date
- cilindrata: int
Camion - carburante: String
1..*
- quintali: int
1..*

1
*
Cliente
Optional
- nome: String
- nome: String - cognome: String
- descrizione: String - codiceFiscale: String

1..*

Indirizzo
- via: String
- numero: String
- città: String
- CAP: String

Figura 2.5: Diagramma delle classi per la rivendita di automobili.

Ogni Cliente ha un nome, un cognome e un codiceFiscale. Ancora,


rappresentiamo l’Indirizzo come una classe esterna (che contiene il no-
me della via, il numero civico, la città e il CAP), ma in alternativa avremmo
potuto definire l’indirizzo come attributo —della classe Cliente— di tipo
stringa o un array di stringhe.
Un Contratto lega un Cliente a uno o più Automezzi. Ogni Contratto
contiene una dataStipula, un ammontare e una dataInizio. Inoltre, le
parti possono prevedere un insieme di Dilazioni, ognuna caratterizzata
da un ammontare e una data di pagamento.
Page 54 CAPITOLO 2. ESERCIZI INTRODUTTIVI

Il diagramma delle classi non presenta operazioni specifiche poiché le


specifiche del problema non richiedono metodi particolari oltre alle soli-
te funzionalità per la gestione dei dati. Probabilmente un’applicazione di
questo tipo potrebbe essere realizzata utilizzando una base di dati per ge-
stire la persistenza delle informazioni, ma queste scelte, e quindi l’intro-
duzione di soluzioni specifiche per l’interazione con un archivio esterno,
esulano dalle finalità dell’esercizio.

2.4 MyAir
Progettiamo un’applicazione per la gestione del programma fedeltà della
compagnia aerea MyAir. Chi si iscrive al programma, ogni volta che vola
con MyAir, accumula punti (miglia) che danno diritto a premi. Ad esem-
pio, bisogna volare per almeno 25.000 miglia per avere diritto a un volo
gratuito in Europa; ci vogliono 65.000 miglia per un volo negli Stati Uniti;
bastano 5.000 miglia per un buono acquisto in un negozio convenzionato.
Il sistema deve gestire i clienti della compagnia che partecipano al pro-
gramma. I partecipanti sono organizzati in tre fasce di merito in funzione
delle miglia volate durante un anno solare: tutti appartengono al primo
livello. Se si volano 35.000 miglia si passa al secondo livello; si accede al
terzo livello con 100.000 miglia volate in un anno. I tre livelli danno di-
ritto a facilitazioni e premi differenziati. Oltre ai clienti, il sistema deve
gestire i tipi di premi (volo gratuito, soggiorno gratuito, buono sconto), il
numero di miglia necessarie per ogni premio particolare (un volo gratuito
a New York richiede più miglia di un volo per Roma) e lo storico dei clienti:
quanti voli ha effettuato ogni cliente, quante miglia ha guadagnato, quali
premi ha già riscosso e quante miglia gli restano da “spendere”. Tenia-
mo presente che le miglia scadono dopo 5 anni dal momento in cui sono
state acquisite, cioè dalla data del volo. Il sistema deve essere in grado di
aggiornare la posizione di ogni cliente in funzione di ogni volo effettuato
e di ogni premio richiesto. Deve anche gestire l’effettiva disponibilità dei
premi. Ad esempio, un volo gratuito potrebbe non essere soddisfacibile
se il volo richiesto fosse già pieno.

Soluzione In questo caso, preferiamo adottare una soluzione basata sui


casi d’uso e sulla definizione di scenari per mezzo di diagrammi di se-
quenza.
Le specifiche del problema evidenziano l’Utente come attore princi-
pale. Il diagramma dei casi d’uso della Figura 2.6 definisce le azioni che
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 55

l’utente può fare sul sistema. Il diagramma distingue anche tra l’Utente,
che può solo registrarsi al programma di fidelizzazione, e l’UtenteRegistrato,
specializzazione del precedente, che può accumulare punti e richiedere
premi.

Registrazione
Programma

Utente

AccreditoPunti

Utente RichiestaPremio
Registrato

Figura 2.6: Attori dell’applicazione.

Gli attori identificati sono direttamente desumibili dalle specifiche.


L’aggiunta di un attore Sistema consentirebbe di modellare come casi
d’uso le attività di gestione del sistema —ad esempio, l’aggiornamento
annuo delle posizioni dei clienti e il controllo della scadenza dei punti—
facendole dipendere dal gestore dell’applicazione. In questo caso, ab-
biamo deciso di non adottare questa soluzione perché le attività di ge-
stione sono comunque collegate alle azioni degli utenti e anche per non
appesantire eccessivamente il diagramma dei casi d’uso.
Gli scenari associati ai diversi casi d’uso possono essere modellati con
diagrammi di sequenza. La semplicità e la didatticità dell’esercizio ci sug-
geriscono di definire un insieme limitato di scenari, ma in generale valgo-
no le regole introdotte nel Capitolo 1 e le esemplificazioni che verranno
presentate nel Capitolo 3.
Volendo lavorare con i diagrammi di sequenza, possiamo anche pro-
porre alcune linee guida per la realizzazione degli scenari.

• Ogni diagramma deve essere semplice. La rappresentazione di inte-


razioni troppo complesse precluderebbe la leggibilità del diagram-
ma. Troppe alternative e iterazioni rendono il diagramma troppo
complesso: in questi casi sarebbe conveniente spezzarlo in più parti
per descrivere le diverse possibilità in modo indipendente.
Page 56 CAPITOLO 2. ESERCIZI INTRODUTTIVI

• I diagrammi di interazione devono essere consistenti con il diagram-


ma delle classi. I messaggi scambiati dovrebbero essere metodi (pro-
prietà) definite nel diagramma delle classi. Due oggetti in un dia-
gramma di interazione possono scambiarsi messaggi solamente se
esiste un’associazione (relazione) tra le classi di appartenenza.

• Quando si usano messaggi sincroni, non è necessario rappresentare


tutte le risposte in modo esplicito. Bisogna evidenziare solo le rispo-
ste necessarie e non ovvie: quelle che effettivamente aggiungono
qualcosa al diagramma.

La Figura 2.7 presenta lo scenario relativo all’utente non registrato (iden-


tificato come un attore esterno) che vuole iscriversi al programma. L’inte-
razione, banale, crea un nuovo elemento Cliente, inizializzato con i dati
dell’utente che chiede la registrazione, e una nuova Situazione associata
al Cliente appena creato. Gli attributi di Situazione sono inizializzati a
zero, a eccezione del livello di merito che è posto a uno.
Scegliendo l’interazione diretta tra l’attore e la creazione dell’oggetto,
abbiamo scelto di non introdurre un oggetto (fittizio) che funga da in-
terfaccia tra gli utenti e gli elementi del sistema. Una scelta diversa, co-
munque corretta, richiederebbe un elemento Interfaccia per mediare
lo scambio di messaggi tra gli attori esterni e i componenti interni del-
l’applicazione. La nostra scelta deriva dal fatto che solitamente l’inter-
faccia utente non viene modellata con UML e dipende dalle particolari
tecnologie (come Java Swing oppure pagine web) scelte per realizzare le
interfacce utente.

Utente
Cliente

Situazione

Figura 2.7: Registrazione utente.


CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 57

Dopo essersi iscritto, l’UtenteRegistrato può iniziare ad accumulare


punti. Questo avviene ogni volta che si presenta a un banco d’accetta-
zione della compagnia aerea e richiede la carta d’imbarco per un certo
volo. Solitamente l’acquisto del biglietto non è sufficiente per l’accredi-
to dei punti. L’UtenteRegistrato richiede al Cliente —che la rappre-
sentazione dell’utente all’interno del sistema— l’accredito dei punti cor-
rispondenti al biglietto acquistato. Nel sistema realizzato, l’utente non
interagirà direttamente con il sistema, ma l’accredito avverrà attraverso
un operatore.
La procedura d’accredito richiede che il sistema identifichi il codi-
ce del volo per il quale l’utente richiede l’accredito. Il volo è sufficien-
te per calcolare il numero di punti da assegnare al cliente e creare un
nuovo Accredito. Il sistema associa il nuovo Accredito alla Situazione
dell’utente e la aggiorna.

Cliente Volo Situazione


Utente
Registrato
accreditoPunti

miglia

cambioPunti

Accredito

Figura 2.8: Accredito miglia.

Un terzo scenario (Figura 2.9) descrive la richiesta di un premio. Lo


scenario distingue tra le richieste che provengono da utenti con un nu-
mero sufficiente di punti e utenti che non hanno punti a sufficienza. An-
cora una volta, l’UtenteRegistrato interagisce direttamente con la sua
“immagine” all’interno del sistema. L’elemento Cliente controlla la sua
Situazione:

• se punti è maggiore dei punti necessari per il premio richiesto (attri-


buto punti di DescrizionePremio), il sistema crea un nuovo
PremioOttenuto, lo aggiunge alla lista dei premi riscossi dal Cliente
e aggiorna la Situazione; alla fine, il Cliente notifica l’Utente
Registrato che il premio è stato concesso;
Page 58 CAPITOLO 2. ESERCIZI INTRODUTTIVI

• se punti è inferiore ai punti necessari per il premio richiesto, il Cliente


non fa nulla e notifica l’UtenteRegistrato che il saldo non è suffi-
ciente.

Descrizione
Cliente Situazione
Premio
Utente
Registrato
premio

punti

alt [Cliente.Situazione.punti >= DescrizionePremio.punti]


situazione

cambioPunti

Premio
premioDisponibile
Ottenuto

saldoInsufficiente

Figura 2.9: Richiesta premio.

La didatticità dell’esercizio ci consente di limitare la nostra attenzione


a questi tre scenari per identificare un primo insieme di classi (diagram-
ma della Figura 2.10). Rispetto agli scenari, il diagramma aggiunge:
• la specializzazione della classe DescrizionePremio, che diventa una
classe astratta, in BigliettoAereo, Soggiorno e BuonoSconto con i
relativi attributi e metodi;
• la fattorizzazione di Accredito e PremioOttenuto nella superclasse
Operazione, che diventa una classe astratta e serve per semplificare
la gestione della Situazione associata a ogni cliente.
Inoltre, l’applicazione non gestisce l’intero piano di volo della compa-
gnia aerea per sapere se la richiesta di un biglietto premio per una certa
data può essere evasa. Il modello proposto ipotizza che il metodo
disponibilità() della classe BigliettoAereo possa interagire con il si-
stema di prenotazione della compagnia aerea per conoscere la disponibi-
lità del posto richiesto. Ragionamento analogo vale per la prenotazione
dei Soggiorni e la gestione dei posti negli alberghi convenzionati.
Per definire meglio la procedura per l’assegnazione di un biglietto ae-
reo (o altro premio), possiamo utilizzare un diagramma delle attività per
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 59

Situazione
Volo Cliente
- / punti: int
- codice: String - nome: String - / puntiAnno: int
- miglia: int 1..n 1..n - cognome: String 1 1 - livello: int
- indirizzo: String - / numPremi: int
+ miglia(): int - tessera: int - / puntiUsati: int
+ premio(r: DescrizionePremio): boolean + situazione(): int
+ accreditoPunti(v: Volo): boolean + cambioPunti(p: int): int
+ aggiornamentoAnnuo(): int
+ cambioLivello(l: int): boolean
BigliettoAereo 1
- partenza: String richiesta
- arrivo: String
+ disponiblità(d: Date): boolean 1..n

Operazione
- data: Date
Soggiorno DescrizionePremio - punti: int

- hotel: String - punti: int


- durata: int
+ disponiblità(d: Date): boolean
+ disponiblità(d: Date): boolean + punti(): int

BuonoSconto
- ammontare: int
-.negozio: String Accredito PremioOttenuto
- codiceVolo: String - descrizione: String

Figura 2.10: Diagramma delle classi per il sistema MyAir.


Page 60 CAPITOLO 2. ESERCIZI INTRODUTTIVI

modellarne i diversi passi. Il processo della Figura 2.11 inizia con la rice-
zione dell’evento RichiestaPremio e continua con il ControlloPuntiAnno.
Se i punti non sono sufficienti, il processo invia l’evento SaldoInsufficiente,
altrimenti passa a controllare la disponibilità del biglietto richiesto. Se il
biglietto non è disponibile, il processo invia l’evento PremioNonDisponibile,
altrimenti passa a AggiornaSituazione e quindi alla notifica PremioDis-
ponibile.

Richiesta
Premio

Controlla
PuntiAnno

suff. insuff.

Controlla Saldo
Disponibilità Insufficiente

disp. non disp.

Aggiorna PremioNon
Situazione Disponibile

Premio
Disponibile

Figura 2.11: Processo per l’emissione di un biglietto premio.

La politica di gestione dei clienti rispetto alle miglia volate ogni anno
può essere rappresentata in UML attraverso opportuni commenti asso-
ciati alle classi interessate, può essere codificata con espressioni OCL, ma
può anche essere rappresentata con un diagramma a stati finiti. La Fi-
gura 2.12 descrive le possibili transizioni tra i diversi livelli rappresentati
come stati. Ogni Cliente parte dal Livello I e può accumulare miglia
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 61

(metodo accreditoPunti()) e richiedere premi (metodo premio()).

accreditoPunti() accreditoPunti() accreditoPunti()


richiestaPremio() premio() premio()

aggiornamentoAnnuo()
aggiornamentoAnnuo() [puntiAnnuo < 100.000 &&
[puntiAnnuo < 35.000] puntiAnno >= 35.000]
Livello I Livello II Livello III

aggiornamentoAnnuo() aggiornamentoAnnuo()
[puntiAnnuo >= 35.000 && [puntiAnnuo >= 100.000]
puntiAnno < 100.000]

aggiornamentoAnnuo()
[puntiAnnuo >= 100.000]

aggiornamentoAnnuo()
[puntiAnnuo < 35.000]

Figura 2.12: Gestione dei livelli di un cliente.

Le richieste di premi, e quindi i punti a disposizione, non fanno cam-


biare stato, ma ogni anno il sistema deve controllare gli Accrediti di ogni
cliente —attraverso il metodo aggiornamentoAnnuo()— per determinare
il nuovo livello. Il passaggio al Livello II avviene a fronte di puntiAnno
≥ 35.000. In modo simile possiamo modellare gli altri due stati Livello
II e Livello III tenendo conto che la soglia per il passaggio dal secondo
al terzo livello è 100.000. Il protocollo utilizzato ci porta anche a dire che
l’esecuzione del metodo aggiornamentoAnnuo() è l’unico evento che può
causare una transizione di stato.

2.5 Distributore di merendine


Progettiamo il controllore software di un distributore automatico di me-
rendine. Il cliente può richiedere brioche, succhi di frutta, cracker o pa-
tatine. Ogni prodotto ha un proprio codice identificativo e un proprio
prezzo. Il sistema deve identificare gli utenti in funzione del loro codice
personale, deve tenere traccia di quanto e cosa acquistano e anche cerca-
re di anticipare il cliente nelle sue scelte, selezionando automaticamente
il prodotto acquistato con maggior frequenza. Ancora, è il sistema che
tiene traccia del credito di ogni cliente: la scheda, o dispositivo magne-
tico, serve solo come strumento di riconoscimento; debiti e crediti sono
memorizzati e gestiti dal sistema.
Page 62 CAPITOLO 2. ESERCIZI INTRODUTTIVI

Soluzione In questo caso, UML ci serve per modellare un tipico pro-


blema di controllo dove l’interazione tra le parti è più importante della
modellazione dei dati su cui l’applicazione deve lavorare.
Il controllore del distributore di merendine suggerisce di iniziare a con-
centrarci sull’intero processo che deve essere gestito dal software (dia-
gramma delle attività della Figura 2.13). Il flusso di controllo inizia identi-
ficando l’utente attraverso il riconoscimento della scheda inserita nel di-
stributore (IdentificaUtente). La prima attività consente al sistema di
conoscere le preferenze dell’utente (LeggiPreferenze) e di proporre un
prodotto (VisualizzaConsiglio). A questo punto, attraverso un apposito
tastierino l’utente deve selezionare il prodotto scegliendo tra quello con-
sigliato, attraverso un semplice OK, o un altro prodotto offerto, digitando
il codice corrispondente (ScegliProdotto). Se l’utente seleziona un pro-
dotto diverso (rispetto al consiglio proposto), il sistema deve aggiungere
la scelta fatta (MemorizzaSelezione) per tener traccia dei gusti del cliente.
Identificato il prodotto, il sistema controlla il credito del cliente e la di-
sponiblità del prodotto stesso. Per semplicità ipotizziamo che le due azio-
ni siano fuse in una sola attività (ControllaCreditoEDisp.), ma in gene-
rale dovrebbero essere due attività diverse che potrebbero anche evolvere
in parallelo. Se il credito non è sufficiente, il sistema segnala l’anoma-
lia (SegnalaCreditoInsuf.), altrimenti aggiunge l’ordine alle preferenze
(RegistraOrdine), decrementa il credito (GestisciCredito) e predispone
i componenti hardware per distribuire il prodotto richiesto (ErogaProdotto).
Il diagramma delle attività suggerisce la necessità di due tipi di classi:
per controllare i dispositivi hardware del distributore e per memorizzare
i dati relativi ai clienti, ai prodotti e alle preferenze. Iniziando dal secon-
do insieme, nel diagramma delle classi della Figura 2.14, ogni Prodotto
è caratterizzato da un codice, un prezzo e una quantità. Un Cliente
è rappresentato da un id, un credito e un insieme di preferenze. Ogni
Preferenza si riferisce ad un Prodotto e conta il numero di volte in cui il
Cliente ha scelto quel prodotto.
Per quanto riguarda le classi di controllo, possiamo iniziare con un
diagramma di struttura interna per identificare le interfacce dell’elemen-
to controllore. Il diagramma della Figura 2.15 comprende due interfacce
offerte, per la gestione delle schede (IScheda) e per l’interazione con la
pulsantiera (ITastierino), e due interfacce richieste per il IDisplay e per
l’IErogatore. Le quattro interfacce possono essere modellate utilizzando
classi che contengono i metodi richiesti/offerti.
Le quattro classi (interfacce) sono gestite da un’opportuna classe
Controllore che realizza la logica di controllo. Questa interagisce an-
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 63

Identifica
Utente

Leggi
Caratteristiche

Visualizza
Consiglio

Scegli
Prodotto

OK selezione

Memorizza
Selezione

Controlla
CreditoEDisp.

insuff. suff.

Segnala Registra
CreditoInsuf. Ordine

Gestisci
Credito

Eroga
Prodotto

Figura 2.13: Processo di controllo del distributore di merendine.


Page 64 CAPITOLO 2. ESERCIZI INTRODUTTIVI

Prodotto Cliente
- codice: int - id: int
- prezzo: real - credito: real
- quantità: int
+ decrementaQta(): int + aggiungiSelezione(id: int): void
+ decrementaCredito(v: real): real
1 1..*

1..*

Preferenza
- volte: int

+ incrementa(): int

Figura 2.14: Classi per la gestione dei dati.

IScheda

Autenticazione
IDisplay

Interazione
Controllore

Erogazione ITastierino

IErogatore

Figura 2.15: Struttura del controllore.


CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 65

che con la classe Cliente, per poter selezionare il cliente appropriato e


gestirne le preferenze, e con la classe Prodotto, per conoscere i prezzi
e la disponibilità dei diversi prodotti. Il risultato finale —per la parte di
controllo— è riassunto nella Figura 2.16.

<<interface>>
IScheda

<<interface>>
Cliente Controllore IDisplay
1..* 1 + cliente(id: int): Cliente
+ leggiProdotto(): int
1 + visualizzaProdotto(id: int): void

1..*
<<interface>> <<interface>>
Prodotto IErogatore ITastierino

+ eroga(id: int): boolean

Figura 2.16: Classi di controllo.

2.6 MegaGym
Progettiamo un’applicazione per la gestione della palestra MegaGym. A
ogni cliente, la palestra chiede nome, cognome, età, indirizzo e professio-
ne, nonché la tipologia di abbonamento e i servizi richiesti. La palestra
offre abbonamenti mensili e annuali (13 mesi). I clienti possono usufrui-
re della sala pesi, della piscina, della parete da arrampicata, della sauna e
del bagno turco. Sala pesi, sauna e bagno turco sono compresi nell’abbo-
namento base; gli altri servizi sono opzionali e venduti singolarmente. A
ogni cliente è associata una scheda —per la sala pesi— che definisce gli
esercizi da compiere, il numero di ripetizioni e la frequenza. La scheda
può essere organizzata su una visita alla palestra a settimana oppure su
due. Nel caso i clienti siano abbonati a servizi aggiuntivi (ad esempio, la
piscina), la scheda può considerare anche questi servizi e, di conseguen-
za, il numero di visite aumenta. La palestra offre, a chi fosse interessa-
to, uno staff medico per assistere i clienti, stabilirne il grado di forma e
Page 66 CAPITOLO 2. ESERCIZI INTRODUTTIVI

definire diete opportune. Chi richiede questo servizio, deve sottoporsi a


controlli mensili e i risultati vengono opportunamente archiviati: peso,
massa grassa, frequenza cardiaca a riposo e sotto sforzo. Il sistema deve
essere in grado di elaborare il bilancio complessivo (entrate da abbona-
menti) della palestra sia mese per mese, che alla fine di ogni anno, e deve
sollecitare eventuali clienti morosi o che devono sottoporsi al controllo
mensile.

Soluzione Il diagramma delle classi del sistema per la gestione della pa-
lestra è mostrato nella Figura 2.17.

Entrata Scheda Esercizio Palestra


* 1..*
- numVisiteSettimanali: int - descrizione: String 1..*
- data: Date
+ entrateMese(mese: Date): int
+ entrateAnno(d: Date): int
+ stampaMorosi(): void
*
+ stampaControlliDovuti(): void
+ verificaImporto(cf: String, mese:Date): RisultatoVerifica
SchedaEsercizi - trovaAbbonamento(cf: String): Abbonamento
- numero: int
- frequenza: String

Cliente
1..*
- nome: String
- dataNascita: Date Abbonamento 1..* serviziBase 1..* Servizio
- indirizzo: String 1..*
- professione: String - meseInizio: Date - nome: String
- CF: String - costoBase: int - costoAggiuntivo: int
1..* serviziAggiuntivi *
+ moroso(mese: Date): boolean + costoMensile(): int
+ deveFareControllo(mese: Date): boolean + entrateMese(mese: Date): int
* + importoDovuto(mese: Date): int

RisultatoControlloMedico
- meseControllo: Date
- peso:float
- massaGrassa: float
- freqCardiacaRiposo: int AbbonamentoMensile Pagamento AbbonamentoAnnuale
- freqCardiacaStress: int 0..1 0..13
- meseConclusione: Date - mesePagato: Date - meseConclusione: Date
- importoPagato: int
+ costoMensile(): int + costoMensile(): int
+ costoAnnuale((): int

Figura 2.17: Diagramma delle classi per la gestione della palestra.

La classe Palestra rappresenta MegaGym ed è associata ai servizi of-


ferti (classe Servizio), agli esercizi disponibili (classe Esercizio) e agli
abbonamenti disponibili (classe Abbonamento). Questa classe mette a di-
sposizione le operazioni richieste dalle specifiche: calcolo delle entrate
mensili e annuali (metodi entrateMese e entrateAnno), stampa dell’e-
lenco dei clienti in arretrato con i pagamenti (metodo stampaMorosi) e
stampa dell’elenco dei clienti che devono sottoporsi a controllo medico
(metodo stampaControlliDovuti).
Ogni Abbonamento è associato a un cliente e alla palestra e inoltre è
associato a un insieme di servizi base (associazione serviziBase) e a un
insieme di servizi aggiuntivi (associazione serviziAggiuntivi). La classe
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 67

Abbonamento è astratta, essendo specializzata nelle sottoclassi


AbbonamentoMensile e AbbonamentoAnnuale. Tutti gli abbonamenti han-
no una data d’inizio (attributo meseInizio) e un costo base (attributo
costoBase). Inoltre hanno tutti l’operazione entrateMese che calcola il
numero di entrate del titolare dell’abbonamento nel mese dato come ar-
gomento. L’operazione costoMensile è comune a tutti i tipi di abbona-
mento, ma è definita diversamente negli abbonamenti mensili e in quelli
annuali (dove è pari al costo annuale diviso 13). Le sottoclassi di Abbonamento
si differenziano anche per un diverso calcolo della data di fine e per il fat-
to che nel caso dell’abbonamento annuale è definito un costo annuale in
aggiunta al costo mensile.
La classe Pagamento rappresenta i pagamenti (mensili) effettuati dal
titolare di un abbonamento. È associata alle sottoclassi di Abbonamento
anziché alla superclasse per la necessità di specificare che a ogni istanza
di abbonamento annuale sono associate fino a 13 istanze di Pagamento,
mentre a ogni istanza di abbonamento mensile ne è associata al più una.
La classe Cliente non necessita di commenti per quanto riguarda il
contenuto. È associata all’insieme delle entrate e a quello dei controlli
medici sostenuti dal cliente. Inoltre è associata alla scheda personale del
cliente. La classe Scheda riporta il numero di entrate settimanali previste
ed è associata all’insieme di esercizi prescritti. L’associazione con gli eser-
cizi riporta l’indicazione del numero di volte che ciascun esercizio deve
essere ripetuto e con quale frequenza deve essere eseguito.
Il sistema di gestione della palestra non ha una dinamica degna di
nota. Più interessante è specificare alcuni vincoli non adeguatamente
rappresentati dalla notazione grafica e il significato di alcune operazio-
ni importanti. Cominciando dalla classe Palestra, nel sistema ne esiste
sempre un’unica istanza. Questo vincolo è esprimibile in modo chiaro e
univoco, ma per essere più precisi, possiamo far ricorso a OCL:

context Palestra
inv: Palestra.allInstances()->size = 1

Quest’espressione indica che la cardinalità dell’insieme delle istanze


della classe Palestra è sempre uno; cioè esiste sempre una sola istanza di
Palestra.
È interessante notare che in assenza di questo vincolo —se cioè il siste-
ma potesse comprendere più istanze di Palestra— occorrerebbe consi-
derare ulteriori vincoli. Ad esempio, le coppie di istanze di Abbonamento e
Servizio, collegate mediante un’associazione serviziBase o
Page 68 CAPITOLO 2. ESERCIZI INTRODUTTIVI

serviziAggiuntivi, dovrebbero essere associate alla medesima palestra.


Se esiste un’unica istanza di palestra, questo vincolo è banalmente verifi-
cato, altrimenti occorre specificarlo esplicitamente. Anche in questo caso,
usando OCL, possiamo scrivere:
context Abbonamento
inv: self.serviziBase->forAll(SB: Servizio |
SB.palestra = self.palestra)
and
self.serviziAggiuntivi->forAll(SA: Servizio |
SA.palestra = self.palestra)

per dire che, dato un abbonamento, tutti i servizi base a esso associati si
riferiscono alla stessa palestra cui si riferisce l’abbonamento, e lo stesso
vale per i servizi aggiuntivi.
Le istanze della classe Cliente e le istanze di altre classi associate de-
vono rispettare altri vincoli.
• Una condizione che desideriamo sia sempre verificata è che un clien-
te deve presentarsi a un controllo medico in una certa data solo se
ha un abbonamento valido nella data che prevede il servizio
ControlloMedico.

context Cliente
inv: self.risultatoControlloMedico->forAll(rcm:
RisultatoControlloMedico |
self.abbonamento->exists(ab: Abbonamento |
ab.meseInizio <= rcm.meseControllo and
ab.meseConclusione >= rcm.meseControllo and
ab.derviziAggiuntivi->select(nome = "ControlloMedico")->
notEmpty()))

cosı̀ diciamo che per tutti i controlli medici associati a un cliente de-
ve esistere un abbonamento che abbia un periodo di validità al cui
interno cade la data del controllo e che preveda il controllo medi-
co tra i servizi aggiuntivi. L’ultima parte della condizione è valutata
selezionando tra i servizi aggiuntivi i controlli medici e verificando
che l’insieme non sia vuoto.
• Uno stesso Cliente non può avere abbonamenti “sovrapposti”, cioè
che abbiano un periodo in comune. Ad esempio, se un abbonamen-
to del cliente Rossi si estende da gennaio 2005 a gennaio 2006, non
potrà esserci un abbonamento dello stesso cliente che va da marzo
2005 a marzo 2006. Scriviamo:
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 69

context Cliente
inv: self.abbonamento->forAll(a1, a2: Abbonamento |
a1 <> a2 implies a1.meseInizio > a2.meseConclusione or
a1.meseConclusione < a2.meseInizio)

per dire che, dato un cliente, tutte le possibili coppie di suoi abbona-
menti sono tali che il primo inizia dopo la conclusione del secondo
o il primo finisce prima dell’inizio del secondo.
• Un cliente non deve pagare due volte per lo stesso mese. Cioè non
devono esserci due registrazioni di pagamento relative allo stesso
cliente per lo stesso mese.

context Pagamento
inv: Pagamento.allInstances()->forAll(p1, p2: Persona |
(p1 <> p2 and p1.mesePagato = p2.mesePagato) implies
p1.abbonamento.cliente <> p2.abbonamento.cliente)

significa che, data una qualunque coppia di pagamenti distinti che


si riferisce allo stesso mese, i clienti titolari dei due abbonamenti
devono essere diversi.
Dopo aver analizzato alcuni vincoli sulle classi, passiamo ora a defini-
re meglio alcune operazioni.
• Un cliente deve sottoporsi ai controlli medici se nel mese corrente è
abbonato al servizio ControlloMedico e non ha controlli associati.

context Cliente::deveFareControllo(mese: Date): Boolean


pre: self.abbonamento->exists(ab: Abbonamento |
ab.meseInizio <= mese and ab.meseConclusione >= mese and
ab.serviziAggiuntivi->select(nome="ControlloMedico")->
notEmpty()))
post: result = self.risultatoControlloMedico->
select(meseControllo = mese)->isEmpty()

• Un cliente è moroso se nel mese indicato dal parametro è abbonato


e non ha pagato.

context Cliente::moroso(mese: Date): Boolean


pre: self.abbonamento->exists(ab: Abbonamento |
ab.meseInizio <= mese and ab.meseConclusione >= mese)
post: result = self.abbonamento->exists(ab: Abbonamento |
ab.meseInizio <= mese and ab.meseConclusione >= mese and
ab.pagamento->select(mesePagato = mese)->isEmpty()))
Page 70 CAPITOLO 2. ESERCIZI INTRODUTTIVI

• Il calcolo delle entrate mensili è la somma dei pagamenti effettuati


nel mese considerato.

context Palestra::entrateMese(mese: Date): Integer


post: result =
self.abbonamento->iterate(a: Abbonamento;
answer: Integer = 0 |
if a.meseInizio <= mese and a.meseConclusione >= mese
answer = answer+a.entrataMese(mese)
else answer
endif)

context Abbonamento::entrataMese(mese: Date): Integer


post: result = self.pagamento->select(mesePagato = mese)->
collect(importoPagato)->sum()

ClienteReale Sistema OperatorePalestra

Pagamento

VerificaImporto

[not importoSufficiente]

Richiesta
ImportoCorretto

DaResto

[importoEccessivo]
Registra
Pagamento

Figura 2.18: Diagramma delle attività per i pagamenti della palestra.

Per finire, la Figura 2.18 mostra il diagramma delle attività che illustra
il processo di pagamento. Il diagramma non è particolarmente comples-
so, tuttavia è utile per illustrare che soltanto alcune delle attività che com-
pongono il processo di pagamento sono svolte con l’ausilio del sistema in-
formatico. In particolare, nel diagramma ci sono tre corsie: ClienteReale,
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 71

Sistema e OperatorePalestra: la sola attività Verifica Importo è svolta


dal sistema (metodo verificaImporto della classe Palestra). Notiamo
(Figura 2.17) che tale metodo ha un risultato di tipo RisultatoVerifica,
che è un enumerativo —non mostrato nei diagrammi— che ammette i
valori importoEccessivo, importoEsatto e importoInsufficiente.

2.7 Automi a stati finiti


Progettiamo un programma in grado di simulare un automa a stati fini-
ti deterministico. L’automa viene considerato un riconoscitore di strin-
ghe appartenenti al linguaggio corrispondente alla definizione dell’auto-
ma stesso. Un automa riconoscitore è una quintupla < Q, I, δ, q0 , F >,
dove

• Q è un insieme di stati;

• I è un insieme finito di simboli (caratteri) di ingresso;

• δ : Q × I → Q è la funzione di transizione: è una funzione par-


ziale che dato uno stato e un carattere appartenente all’alfabeto di
ingresso individua un (unico) stato;

• q0 ∈ Q è lo stato iniziale (unico);

• F ⊆ Q è un insieme di stati finali.

Una stringa x ∈ I ∗ è riconosciuta dall’automa se e solo se δ ∗ (q0 , x) ∈ F .


La funzione δ ∗ : Q × I ∗ → Q è definita come segue ( essendo la stringa
vuota):

• δ ∗ (q, ) = q

• δ ∗ (q, xc) = δ(δ ∗ (q, x), c) con x ∈ I ∗ e c ∈ I

L’esecuzione di un’automa è definita come segue.

1. Inizialmente l’automa si trova in una “configurazione” < q0 , x >,


contraddistinta dallo stato iniziale e dall’intera stringa d’ingresso.

2. L’automa effettua una transizione < qc , cy >`< qp , y > se δ(qc , c) =


qp . Questo significa che se l’automa si trova nello stato corrente qc e
Page 72 CAPITOLO 2. ESERCIZI INTRODUTTIVI

deve ancora esaminare una porzione della stringa di ingresso costi-


tuita dal carattere c seguito dalla stringa y, e se la funzione di tran-
sizione δ(qc , c) = qp , allora l’automa si porta in una nuova configu-
razione, caratterizzata dallo stato qp e dal fatto che la stringa y deve
ancora essere letta.

3. Se l’automa è nella configurazione < qc , cx > e δ(qc , c) =⊥, cioè se la


funzione di transizione non è definita per la coppia stato corrente-
carattere in ingresso, allora l’esecuzione si ferma con un fallimento
(cioè la stringa data non è accettata dall’automa). Questo indipen-
dentemente dalla parte di stringa d’ingresso non esaminata.

4. Poiché la stringa d’ingresso è di lunghezza finita, applicando itera-


tivamente il procedimento descritto al punto 2, si possono raggiun-
gere solo due possibili esiti: si verifica il caso descritto al punto 3,
e la stringa non è accettata, oppure si arriva a una configurazione
< q,  >, in cui la stringa data è stata letta interamente. In questo
caso, la stringa è accettata se q ∈ F , non è lo se q 6∈ F .

Il programma deve essere in grado di costruire un automa di cui viene


data la descrizione. Quindi deve simulare l’esecuzione di tale automa per
una qualunque stringa di caratteri.

Definizione
Automa Inserimento
StringaIngresso
<<include>>

Esecuzione
Automa
<<include>>

Esecuzione
Utente
PassoPasso

<<include>>

Visualizzazione <<include>> Visualizzazione


Esecuzione Configurazione

Figura 2.19: Diagramma dei casi d’uso per la simulazione di automi a stati finiti.

Soluzione Il diagramma dei casi d’uso della Figura 2.19 è sufficiente-


mente semplice da non richiedere spiegazioni. Sottolineiamo soltanto
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 73

che con “esecuzione passo passo” intendiamo l’esecuzione di una tran-


sizione alla volta, con la visualizzazione immediata della configurazione
che si raggiunge.
La Figura 2.20 presenta il diagramma delle classi per l’applicazione
proposta. Benché in teoria sia possibile definire un automa vuoto, que-
st’eventualità è stata considerata non significativa nel nostro caso.

+ statoIniziale

Stato Automa Alfabeto


- nome: String 1..*
- finale: boolean
+ statoProssimo(): Stato
+ esecuzione(): void

+ statoProssimo + statoCorrente
1..*

1..*
* Transizione Simbolo
* + carattereCorrente
- s: char
*

PosInStringa
*
- posizione: int

StringaIngresso
- lunghezza: int
Configurazione Esecuzione
- posizioneTestina: int
*
- posizione: int - lunghezza: int + reset(): void
- / inputRestante: String * + leggi(): Simbolo
- fallita: bollean
+ finita(): boolean
+ init(): void + lunghezza(): int
+ passo(): void + contenuto(): String
+ passoPossibile(): boolean + avanza(): void
*

Figura 2.20: Diagramma delle classi per la simulazione di automi a stati finiti.

Pertanto i nostri automi hanno sempre almeno uno stato: ciò è garan-
tito dalla molteplicità della relazione di composizione tra Automa e Stato.
A ogni Automa associamo un insieme di stati (istanze della classe Stato),
dei quali alcuni finali, un unico statoIniziale e un insieme di transizioni
(istanze della classe Transizione). Inoltre, un automa può essere ogget-
to di zero o più esecuzioni (istanze di Esecuzione). La classe Alfabeto,
definita come insieme di simboli (istanze della classe Simbolo), non è
strettamente necessaria, ma si potrebbe collegare direttamente la classe
Simbolo alla classe Automa. La classe Stato rappresenta degli stati. La rela-
zione di composizione che lega Automa a Stato definisce l’insieme Q, cioè
quali istanze di Stato sono possibili stati dell’automa. Lo stato iniziale
è definito mediante una relazione uno-a-uno con l’automa. Poiché qua-
lunque stato potrebbe essere finale, questo fatto si esprime utilizzando
l’attributo finale.
Page 74 CAPITOLO 2. ESERCIZI INTRODUTTIVI

La classe Transizione è una tripla <statoCorrente, carattereCorrente,


statoProssimo>. Le associazioni verso lo stato corrente, lo stato prossi-
mo e il simbolo letto definiscono la tripla. Ciascuna istanza della clas-
se Transizione è dotata di un’istanza delle tre associazioni per ciascun
elemento della tripla.
Il resto del diagramma riguarda l’esecuzione dell’automa. Poiché si
richiede che il programma sia in grado di supportare diverse esecuzio-
ni, memorizzandole tutte, abbiamo definito la classe Esecuzione: le sue
istanze rappresentano le diverse esecuzioni. L’attributo lunghezza indica
il numero di passi che sono stati eseguiti ed equivale al numero di con-
figurazioni associate, non contando la configurazione iniziale. La clas-
se StringaIngresso è definita come sequenza di simboli (con l’attributo
posizione della classe d’associazione PosInStringa che indica la posizio-
ne delle istanze di Simbolo nella sequenza).
L’attributo posizioneTestina rappresenta la posizione del carattere in
ingresso, cioè la posizione del carattere che verrà preso in considerazione
dalla prossima transizione. Esiste esattamente un’istanza di
StringaIngresso per ciascuna istanza di Esecuzione.
La classe Configurazione corrisponde al concetto di configurazione
descritto in precedenza. L’attributo posizione indica che le configura-
zioni formano una sequenza nel contesto di un’esecuzione. Notiamo che
la posizione dei simboli nella stringa d’ingresso viene modellata con una
classe d’associazione, perché ogni simbolo (cioè ogni istanza di Simbolo)
può appartenere a stringhe diverse, occupando in ciascuna posizioni di-
verse. Viceversa, la posizione di una configurazione nella rispettiva ese-
cuzione è modellata con un attributo, perché ogni configurazione appar-
tiene a un’unica esecuzione, quindi la sua posizione può essere vista co-
me una proprietà della configurazione, anziché dell’esecuzione o della
relazione che lega configurazione ed esecuzione.
Notiamo anche che l’attributo inputRestante, che rappresenta la por-
zione di stringa d’ingresso ancora da esaminare, è in realtà derivato, poiché
corrisponde alla porzione associata all’esecuzione che va dalla posizione
corrente alla fine: infatti la posizione della configurazione corrisponde
alla posizione del carattere letto dalla stringa d’ingresso, poiché ogni pas-
so d’esecuzione consuma esattamente un simbolo e genera esattamente
una configurazione.
Per esemplificare il funzionamento del nostro modello, la Figura 2.21
propone un automa e la sua rappresentazione. In particolare, la figura
riporta in alto l’automa che riconosce il linguaggio formato dalle stringhe
che iniziano con il carattere ‘0’ e sono seguite da un qualunque numero
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 75

di ‘1’ (anche nessuno) ma da nessun altro ‘0’. L’automa è rappresentato


secondo le consuetudini grafiche consolidate.
In basso nella figura è riportato un diagramma degli oggetti, in cui
compaiono le istanze delle classi e delle associazioni definite nel model-
lo delle classi (diagramma degli oggetti). Gli oggetti mostrati sono quelli
che contribuiscono alla definizione dell’automa; sono invece assenti gli
oggetti riguardanti le esecuzioni.

0
q0 q1 1

S0: Simbolo

s = ‘0’

statoIniziale
: Automa : Alfabeto

S1: Simbolo

s = ‘1’

Q0: Stato Q1: Stato


statoProssimo
T2: Transizione carattereCorrente
finale = false finale = true statoCorrente

statoProssimo
T1: Transizione carattereCorrente
statoCorrente

Figura 2.21: Un automa a stati finiti e la sua rappresentazione come diagramma


degli oggetti.

Il modello proposto può essere reso più preciso e rigoroso aggiungen-


do un insieme di vincoli OCL alle classi e ai metodi proposti. Di seguito
elenchiamo alcuni vincoli semplici e significativi.
• La definizione di automi a stati finiti proposta prevede che ogni au-
toma sia dotato di almeno uno stato finale. È però facile verificare
che il diagramma della Figura 2.20 non impone che tale condizio-
ne sia sempre rispettata. L’esistenza di almeno uno stato finale in
ogni definizione di automa può essere indicata con un opportuno
commento oppure imposta dalla seguente espressione OCL:

context Automa
Page 76 CAPITOLO 2. ESERCIZI INTRODUTTIVI

inv: self.stato->select(finale=true)->notEmpty()

• Lo stato corrente deve appartenere all’insieme degli stati dell’auto-


ma. Anche questo vincolo può essere espresso facilmente in OCL:

context Automa
inv: self.stato->includes(self.statoCorrente)

• Ogni stringa d’ingresso deve essere composta esclusivamente da sim-


boli appartenenti all’alfabeto.

context StringaIngresso
inv: self.simbolo->forAll(s: Simbolo |
self.esecuzione.automa.alfabeto->includes(s))

• Per ogni esecuzione esiste una e una sola configurazione iniziale


comprendente lo stato iniziale e l’intera stringa d’ingresso.

context Esecuzione
inv: self.configurazione->select(c: Configurazione |
c.posizione = 1 and
c.statoCorrente = self.automa.statoIniziale and
c.inputRestante =
self.esecuzione.stringaIngresso.contenuto())->
size()=1

• Poiché le configurazioni formano una sequenza, l’insieme di con-


figurazioni corrispondenti a una data esecuzione è caratterizzato
da istanze aventi l’attributo posizione crescente. Inoltre l’attribu-
to posizione cresce “senza salti”, cioè ad esempio la configurazione
avente posizione i sarà preceduta da quella avente posizione i − 1
(a meno che non sia la prima). L’espressione OCL seguente impone
che nel contesto di un’esecuzione, per ogni configurazione —tranne
la prima— esista una e una sola configurazione immediatamente
precedente, cioè che si trova nella posizione precedente.

context Esecuzione
inv: self.configurazione->forAll(c1: Configurazione |
c1.posizione > 1 implies self.configurazione->
select(c2: Configurazione |
posizione = c1.posizione-1)->size() = 1)
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 77

La specifica dell’operazione che definisce un passo di esecuzione in


OCL è piuttosto complessa: qui, ci limitiamo a una breve spiegazione.
In generale, un passo di esecuzione può essere intrapreso solo se l’o-
perazione non è già fallita e se la stringa d’ingresso non è stata esaminata
completamente. Durante il passo d’esecuzione, si deve selezionare lo sta-
to corrente, quello in cui ci si trova al momento della chiamata del meto-
do passo(). Lo stato corrente è quello iniziale se l’esecuzione è all’inizio
(lunghezza nulla); altrimenti è dato dallo stato corrente della configura-
zione corrente. La configurazione corrente è quella che si trova nell’ulti-
ma posizione della sequenza, quindi in posizione uguale alla lunghezza
dell’esecuzione più uno (per tener conto della configurazione iniziale).
L’esecuzione deve definire anche l’insieme delle transizioni possibili par-
tendo dallo stato corrente e con il carattere corrente. Essendo l’automa
deterministico, l’insieme può contenere al massimo una sola transizione.
Dobbiamo anche distinguere tra due casi possibili. Nel primo caso esi-
ste una transizione definita per lo stato corrente e il simbolo della stringa
di ingresso sotto la testina di lettura: occorre quindi effettuare il passo di
esecuzione. Nel secondo caso bisogna solo dichiarare fallita l’esecuzione
aggiornando opportunamente l’attributo fallita. Il passo di esecuzione
consiste nell’esecuzione della transizione e verifica di quattro condizio-
ni: la lunghezza dell’esecuzione è incrementata di uno; all’insieme delle
configurazioni si è aggiunta la nuova configurazione raggiunta in conse-
guenza del passo appena concluso; la posizione della testina di lettura
della stringa di ingresso è avanzata di una posizione; l’esecuzione è fallita
se la stringa d’ingresso è stata letta completamente e lo stato raggiunto
non è finale.

2.8 Esercizi
La soluzione degli esercizi proposti in questo paragrafo sarà disponibile
sul sito www.elet.polimi.it/upload/baresi/libroUML/.

Parcheggio a pagamento

Realizziamo il sistema di controllo di un parcheggio a pagamento per 100


autovetture. L’ingresso degli autoveicoli è controllato da un semaforo:
quando è verde e un mezzo si presenta in prossimità della sbarra, questa
si alza e il veicolo può entrare; quando il semaforo è rosso non succede
nulla.
Page 78 CAPITOLO 2. ESERCIZI INTRODUTTIVI

Il parcheggio prevede 20 posti per clienti vip. Questi vengono concessi


ai clienti normali solo dopo 15 tentativi “normali” di accedere al parcheg-
gio. L’uso dei posti vip, se disponibili, per clienti normali serve per evitare
di sprecare posti.
Un cliente può diventare vip in due modi: comprando lo status per
uno o più anni, oppure acquisendolo dopo aver speso 1000 euro per il par-
cheggio in un anno solare. All’inizio dell’anno il sistema deve cancellare i
privilegi acquisiti nell’anno precedente.
Per quanto riguarda le modalità di pagamento, il parcheggio prevede
la solita tariffa a ore, abbonamenti settimanali o mensili, tessere a consu-
mo e anche dispositivi particolari montati direttamente sulle macchine
(per i clienti che comprano lo status vip). Il dispositivo, simile a un te-
lepass, può essere precaricato con un certo credito oppure l’ammontare
può essere addebitato mensilmente sulla carta di credito dell’intestatario
del contratto.
Tutti i clienti devono saldare il conto alla cassa e ritirare il tagliando
per l’uscita prima di raggiungere il proprio automezzo. I clienti dotati di
dispositivo per il pagamento intelligente non devono recarsi alle casse,
ma l’importo viene addebitato direttamente mentre l’auto esce dal par-
cheggio. Per gli addebiti su carta di credito, non ci sono problemi, se il
dispositivo è precaricato, il programma deve controllare che il credito ri-
masto sia sufficiente per pagare il parcheggio e deve bloccare la sbarra in
caso contrario.

Esame di informatica

Progettiamo un’applicazione per l’auto-valutazione degli elaborati d’esa-


me di un corso base di programmazione. Il sistema deve consentire la re-
gistrazione degli studenti, i quali forniscono nome, cognome, numero di
matricola e sessione d’esame nella quale hanno superato lo scritto oppure
si presenteranno per sostenere la prova. Ogni studente può sottoporre un
solo progetto (programma); è possibile però che il progetto venga cam-
biato dallo studente stesso entro tre giorni dalla prima presentazione. Per
fare questo ogni studente deve essere dotato di username e password per
potersi autenticare.
Nel sistema sono state impostate anche alcune regole di uguaglianza.
Banalmente, due programmi sono uguali se sono identici, ma potrebbe-
ro essere ritenuti tali anche se gli algoritmi fossero i medesimi, ma con
nomi di variabili diversi. Supponiamo, per semplicità, che le regole di
CAPITOLO 2. ESERCIZI INTRODUTTIVI Page 79

uguaglianza debbano essere semplicemente modellate come stringhe di


testo.
Il sistema produce per ogni programma (studente) un resoconto che
attesta il grado di similitudine tra il programma in questione e gli altri
progetti; vengono considerati solo i 10 progetti più simili. Un resocon-
to potrebbe essere vuoto se il grado di similitudine è inferiore al 10% per
ogni progetto considerato. Quando viene sottomesso un progetto, il si-
stema deve calcolare il grado di similitudine rispetto a tutti i progetti gi
presentati.
Il sistema deve mandare anche un messaggio di posta elettronica al-
l’autore e al professore ogni volta in cui si trova una coppia di programmi
il cui grado di similitudine è superiore al 50%.

Mense universitarie
Progettiamo un programma per la gestione del servizio mensa di un cam-
pus universitario. Il campus dispone di cinque diverse mense dove gli
studenti possono recarsi a colazione e pranzo, ma solo tre sono aperte
per la cena. Ogni mensa ha orari diversi, un numero di posti a sedere, un
numero massimo di coperti per pasto e alcune caratteristiche particolari:
c’è la mensa per vegetariani, quella internazionale e quella che serve solo
cibi della tradizione mediterranea. Ogni mensa ha un giorno a settimana
in cui i pasti costano il 25% in meno.
Gli studenti possono consumare i pasti dove vogliono; l’unico vincolo
è il rispetto delle regole. Ogni studente è dotato di una tessera mensa, una
carta a punti prepagata, per comprare i pasti. Le diverse mense richiedo-
no una quantità diversa di punti: lo studente può mangiare a quella men-
sa se la sua carta ha punti a sufficienza. L’ingresso è vietato anche quando
una mensa è completa: il numero di persone nella mensa è pari al nume-
ro di posti a sedere. L’addetto deve avvisare lo studente ogni volta che i
punti residui sulla tessera non sono più sufficienti a consumare un pasto;
neppure il pi economico.
Le tessere possono essere ricaricate. Il minimo sono 50 punti, il mas-
simo 1000 punti. Il sistema deve essere in grado di monitorare l’occu-
pazione delle mense in tempo reale: i monitor informativi dell’università
devono poter “guidare” gli studenti verso le mense meno affollate, oppure
devono indicare il tempo d’attesa stimato. Il sistema deve produrre anche
consuntivi mensili sull’uso delle diverse mense al fine di poter monitorare
e migliorare la qualità dei servizi.
Page 81

Capitolo 3

Analisi dei requisiti

In questo capitolo illustriamo l’uso di UML per la modellazione del domi-


nio del problema e dei requisiti utente. Il capitolo è suddiviso in due parti
principali: la prima descrive un sistema per la gestione di una biblioteca,
la seconda un sistema che regola il funzionamento di un ascensore.
L’esempio dedicato alla biblioteca contiene anche alcune indicazioni
metodologiche sull’utilizzo di UML e sul processo di specifica dei requisi-
ti utente. Tali indicazioni sono invece omesse nell’esempio relativo all’a-
scensore che presenta il modello dei requisiti senza particolari commenti
su come esso possa essere ottenuto.

3.1 Biblioteca
I responsabili di una biblioteca di dimensioni non molto grandi —ad esem-
pio la biblioteca comunale di una cittadina— desiderano passare da una
gestione manuale a una assistita dal calcolatore.
L’applicazione informatica dovrà servire sia al personale della biblio-
teca per gestire il lavoro amministrativo (prestiti, inventario, registrazio-
ne delle acquisizioni, ecc.) sia agli utenti (reperimento volumi, assistenza
nelle ricerche bibliografiche, ecc.).

3.1.1 Requisiti informali


La biblioteca è organizzata in settori, a ciascuno dei quali corrisponde
un argomento (storia, narrativa, saggistica, artigianato, scienza, tecnolo-
gia, fantascienza, musica, ecc.). I settori contengono documenti di va-
rio genere: libri, riviste, materiale audio (dischi, CD, cassette) e video (vi-
Page 82 CAPITOLO 3. ANALISI DEI REQUISITI

deocassette e DVD). I documenti risiedono su scaffali opportunamente


numerati.
La biblioteca concede i documenti in prestito. Un prestito può durare
fino a 15 giorni. È possibile un singolo rinnovo di una settimana. I prestiti
sono concessi agli utenti registrati. La registrazione è effettuata automati-
camente su domanda dell’utente. All’utente registrato viene associato un
codice numerico.
Se i documenti in prestito non vengono restituiti nel termine stabili-
to, la biblioteca avverte l’utente sollecitandone la restituzione. Al solleci-
to segue un’ingiunzione con multa. Se anche questa azione non sortisce
effetto, l’utente viene sospeso dal servizio prestiti.
Non tutti i documenti possono essere concessi in prestito, per vari mo-
tivi (sono rari o preziosi o devono essere sempre disponibili). Le riviste
contengono raccolte di articoli, i cui dati sono memorizzati singolarmen-
te nel sistema. Le riviste sono presenti in copia unica e sono riservate alla
consultazione.
Il sistema dovrà fornire le seguenti prestazioni.

• I documenti dovranno essere provvisti di una descrizione analoga


a quella assicurata nella gestione manuale dagli appositi cartellini
contenuti in schedari che gli utenti possono consultare. Ogni car-
tellino riporta: titolo, autore, editore, anno di emissione del docu-
mento e altre informazioni per ciascun documento.

• Il sistema dovrà supportare il lavoro di amministrazione della bi-


blioteca, il servizio prestiti e le ricerche di materiale da parte del-
l’utente.

• Il sistema dovrà altresı̀ gestire l’elenco degli utenti con tutte le infor-
mazioni che possano risultare utili.

• Il sistema sarà in grado di registrare i prestiti e supporterà le azioni


nei confronti degli utenti che trattengono i documenti in prestito
oltre il lecito.

• Il sistema conterrà l’elenco dei documenti presenti nella biblioteca e


consentirà di manipolare tale elenco per tenere conto della vendita
o smarrimento di documenti, acquisizione di nuovi documenti, ecc.

• Il sistema dovrà consentire le ricerche bibliografiche da parte de-


gli utenti permettendo l’utilizzo di diversi criteri. Ogni documento
CAPITOLO 3. ANALISI DEI REQUISITI Page 83

dell’insieme deve riportare, oltre alle proprie caratteristiche che ser-


vono a identificarlo (titolo, autore, ecc.), anche una descrizione del
contenuto e le informazioni relative alla sua localizzazione, in modo
da facilitare il reperimento.

• Inizialmente si vuole fornire all’utente uno strumento di ricerca che


sia sufficientemente potente e flessibile, senza per questo risultare
eccessivamente costoso sia in termini di realizzazione che di presta-
zioni.

3.1.2 Casi d’uso e scenari


Utilizziamo i diagrammi dei casi d’uso per rappresentare i requisiti del
sistema descritti in precedenza.
Nel diagramma della Figura 3.1 sono descritti i casi d’uso che coin-
volgono l’utente della biblioteca relativamente ai prestiti (con esclusio-
ne quindi delle ricerche bibliografiche che sono trattate in un caso d’uso
specifico). A rigore anche i solleciti e le multe coinvolgono gli utenti della
biblioteca, tuttavia qui intendiamo modellare solo i casi d’uso che sono
originati, causati e iniziati direttamente dagli utenti.

Aggiornamento
<<include>> RegistroUtenti

Registrazione

<<include>> VerificaRegistrazione

<<include>>

RichiestaPrestito
<<include>> VerificaAbilitazione

<<include>>
Utente

<<include>>
VerificaDisponibilità

Restituzione

<<include>>
AggiornamentoSituazione

Figura 3.1: Diagramma dei casi d’uso: funzioni richieste dall’utente (escluse le
ricerche bibliografiche).
Page 84 CAPITOLO 3. ANALISI DEI REQUISITI

Gli utenti possono eseguire tre operazioni principali: richiedere la re-


gistrazione, chiedere un prestito, effettuare la restituzione dei documenti
precedentemente presi in prestito.
Notate che il caso d’uso non fornisce una descrizione sufficientemen-
te dettagliata dei requisiti. Questa è una caratteristica ben nota dei casi
d’uso, che privilegiano una rappresentazione grafica, astratta e intuitiva
dei requisiti, anziché approfondire i dettagli. Vediamo dunque quali sono
le informazioni che il diagramma contiene e quelle che invece non sono
fornite, benché siano necessarie alla completa e corretta comprensione
dei requisiti.
Il diagramma specifica che gli utenti possono richiedere tre servizi.

• Richiedere la registrazione. Quest’operazione comprende la verifica


della registrazione e l’aggiornamento del registro utenti.

• Chiedere un prestito. Quest’operazione comprende la verifica del-


la registrazione, la verifica dell’abilitazione, la verifica della dispo-
nibilità del documento richiesto e l’aggiornamento della situazione
della biblioteca (tipicamente per memorizzare i dati del prestito, e
in particolare il fatto che il documento prestato non è più presente
nella biblioteca).

• Effettuare la restituzione dei documenti precedentemente presi in


prestito. Quest’operazione comprende l’aggiornamento della situa-
zione della biblioteca (per registrare che il documento precedente-
mente prestato è nuovamente presente nella biblioteca e disponibi-
le per ulteriori prestiti).

Dalla lettura del diagramma non si ricavano molte informazioni uti-


li. Ad esempio, non appare esplicitamente che un prestito può essere
concesso solo agli utenti registrati. Ciò è solo suggerito dal fatto che la
richiesta di un prestito comprende la verifica della registrazione dell’u-
tente. Analogamente, non è precisato che l’aggiornamento del registro
utenti viene eseguito solo se la verifica della registrazione ha esito negati-
vo (un utente già registrato non verrà registrato una seconda volta). An-
che il significato del caso d’uso Registrazione non è descritto. Ad esem-
pio, non si dice quali informazioni dovranno essere memorizzate nel si-
stema in conseguenza della registrazione. Anche le modalità della regi-
strazione non sono dettagliate, e questo punto è particolarmente delicato,
perché la procedura di registrazione può svolgersi in molti modi diversi:
l’utente potrebbe dover versare una cauzione a garanzia dei prestiti, op-
CAPITOLO 3. ANALISI DEI REQUISITI Page 85

pure potrebbe essere richiesta la residenza nel comune cui appartiene la


biblioteca, oppure potrebbe dover comunicare il proprio codice fiscale.
Il diagramma rappresentato nella Figura 3.1 lascia dunque al lettore il
compito di interpretare il significato esatto di ciascun caso d’uso. Questo
modo di procedere è in generale molto pericoloso perché si presta a catti-
ve interpretazioni dei requisiti. Rischiamo infatti che il lettore non ”azzec-
chi” l’interpretazione giusta e capisca i requisiti in modo non corretto. Se
chi legge il diagramma è —come normalmente accade— il responsabile
della progettazione e implementazione del sistema, è chiaro che interpre-
tazioni sbagliate dei requisiti portano direttamente a costruire sistemi che
non soddisfano le esigenze degli utenti.
Di conseguenza risulta opportuno completare i diagrammi dei casi
d’uso allegandovi delle specifiche testuali. Vediamo quindi quali posso-
no essere le informazioni da allegare ai casi d’uso che compaiono nel dia-
gramma della Figura 3.1: il caso d’uso Registrazione è descritto nella Ta-
bella 3.3. I casi d’uso VerificaRegistrazione e AggiornamentoRegistro
Utenti —funzionali alla registrazione— sono descritti rispettivamente nel-
le Tabelle 3.1 e 3.2.
Notate che le tabelle citate adottano una modalità di descrizione dei
casi d’uso leggermente diversa da quella indicata nel Capitolo 1, a ulte-
riore riprova del fatto che —non fornendo UML indicazioni specifiche—
risulta possibile adottare diversi stili di descrizione dei casi d’uso. In par-
ticolare, le differenze rispetto alle indicazioni fornite nel Capitolo 1 sono:

• gli svolgimenti alternativi non sono descritti mediante estensioni,


ma nel testo;

• non si esplicitano i trigger. L’esecuzione è determinata direttamente


dall’utente nel caso dei casi d’uso di tipo “primario”, mentre per i
casi d’uso secondari le condizioni di esecuzione (spesso molteplici)
sono indicate nella descrizione.

Il caso d’uso Restituzione è descritto nella Tabella 3.5, mentre Richie-


staPrestito nella Tabella 3.6. Il caso d’uso secondario AggiornaSitua-
zione è descritto nella Tabella 3.4; VerificaAbilitazione e VerificaDi-
sponibilità non sono riportati per non appesantire la trattazione.
È piuttosto evidente che le descrizioni riportate nelle Tabelle da 3.1
a 3.6 sono sufficientemente ricche e flessibili da integrare utilmente i dia-
grammi dei casi d’uso. Tuttavia UML non prevede alcun “complemento
testuale” ai casi d’uso.
Page 86 CAPITOLO 3. ANALISI DEI REQUISITI

Caso d’uso VerificaRegistrazione


Tipo Secondario (non è sollecitato direttamente dall’utente).
Precondizione Qualche funzione sollecitata dall’utente (richiesta di registra-
zione, richiesta di prestito, ecc.) ha riscontrato la necessità di
effettuare un controllo della registrazione.
Svolgimento L’utente è registrato, quindi la verifica ha esito positivo. In usci-
normale ta viene comunicato il numero identificatore (cfr. Tabella 3.3)
dell’utente.
Svolgimento L’utente non è registrato, quindi la verifica ha esito negativo. In
alternativo uscita viene comunicato l’esito negativo della verifica.
Postcondizione La registrazione è stata verificata e l’esito comunicato. Il registro
utenti non ha subito variazioni.
Descrizione Quest’operazione è funzionale rispetto a varie operazioni. Viene
eseguita quando si verificano le condizioni per effettuare un con-
trollo della registrazione (richiesta di registrazione, richiesta di
prestito, ecc.). La verifica si basa sul contenuto del registro uten-
ti: se l’utente è presente la verifica ha esito positivo, altrimenti ha
esito negativo.

Tabella 3.1: Descrizione del caso d’uso VerificaRegistrazione.

Caso d’uso AggiornamentoRegistroUtenti


Tipo Secondario (non è sollecitato direttamente dall’utente).
Precondizione Qualche funzione sollecitata dall’utente (tipicamente la richie-
sta di registrazione) ha riscontrato la necessità di aggiornare il
registro utenti, tipicamente aggiungendo un nuovo utente.
Svolgimento Un utente non registrato deve essere aggiunto al registro uten-
normale ti. Al registro viene quindi aggiunta una registrazione concer-
nente l’utente. All’utente è associato un identificatore numerico
univoco. L’utente è registrato.
Svolgimento Un utente registrato comunica delle variazioni ai propri dati (ad
alternativo esempio all’indirizzo). La sua registrazione viene cambiata di
conseguenza. L’identificatore numerico dell’utente non cambia.
Postcondizione Il registro è stato modificato come richiesto.
Descrizione Quest’operazione consiste nella modifica del registro utenti per
aggiungere o aggiornare le registrazioni corrispondenti agli uten-
ti. Per il momento non è prevista l’operazione di cancellazione
di un utente. Nel registro sono riportati i dati salienti dell’utente:
identificatore, nome, cognome, codice fiscale, indirizzo.

Tabella 3.2: Descrizione del caso d’uso AggiornamentoRegistroUtenti.


CAPITOLO 3. ANALISI DEI REQUISITI Page 87

Caso d’uso Registrazione


Tipo Primario (rappresenta una funzione del sistema invocata diretta-
mente dall’utente).
Precondizione L’utente richiede di essere registrato.
Svolgimento L’utente non risulta essere registrato, quindi viene registrato. Il si-
normale stema comunica all’operatore il numero identificatore assegnato
al nuovo utente della biblioteca.
Svolgimento L’utente risulta essere già registrato, quindi il sistema non effet-
alternativo tua alcuna modifica, ma si limita a comunicare all’operatore il
numero identificatore dell’utente presso la biblioteca.
Postcondizione L’utente è registrato, cioè compare nel registro utenti con un
identificatore univoco.
Descrizione Per prima cosa si verifica se l’utente è già registrato (caso d’uso
VerificaRegistrazione). Se l’esito è positivo il registro utenti
non viene modificato, altrimenti viene aggiornato con l’aggiunta
di una nuova registrazione corrispondente al nuovo utente (ca-
so d’uso AggiornamentoRegistroUtenti). In ogni caso il siste-
ma comunica all’operatore il numero identificatore dell’utente
presso la biblioteca.

Tabella 3.3: Descrizione del caso d’uso Registrazione.

Nella pratica questo non è un grosso problema, poiché quasi tutti gli
strumenti CASE che supportano UML prevedono la possibilità di aggiun-
gere documentazione testuale ai diagrammi. È quindi possibile aggiunge-
re a ciascun diagramma o direttamente a ciascun caso d’uso il contenuto
delle tabelle riportate sopra, sotto forma di documentazione.
Vediamo ora di completare la descrizione delle funzionalità del siste-
ma. Il diagramma riportato nella Figura 3.2 illustra i casi d’uso relati-
vi alle azioni amministrative. Il significato dei casi d’uso ivi riportati è
abbastanza intuitivo.

• L’operazione di verifica delle scadenze è una funzionalità sollecita-


ta quotidianamente dal bibliotecario. Ogni giorno si verifica qua-
li prestiti sono scaduti: per questi ultimi si procede all’operazione
opportuna (sollecito, multa o disabilitazione dell’utente).

• L’operazione di sollecito riguarda i prestiti scaduti: viene stampato


il sollecito o la multa se la scadenza riguarda il termine del sollecito.

• L’accesso ai dati dell’utente è una funzionalità secondaria. Serve a


recuperare i dati (tipicamente il nome e l’indirizzo) che sono neces-
sari per poter spedire i solleciti agli utenti morosi.
Page 88 CAPITOLO 3. ANALISI DEI REQUISITI

Caso d’uso AggiornaSituazione


Tipo Secondario (non è sollecitato direttamente dall’utente).
Precondizione Nell’ambito di qualche funzione sollecitata dall’utente si è ri-
scontrata la necessità di aggiornare lo stato dei documenti e/o
dei prestiti.
Svolgimento Un insieme di documenti presenti nella biblioteca è stato con-
normale cesso in prestito. Viene registrato il prestito, annotando l’uten-
te beneficiario e l’insieme di documenti prestati. Viene inol-
tre registrato che i documenti in questione non sono presenti in
biblioteca né disponibili in quanto dati in prestito.
Svolgimento Un insieme di documenti è stato restituito alla biblioteca.
alternativo I documenti in questione sono classificati come nuovamen-
te disponibili. Il prestito corrispondente è registrato come
concluso.
Svolgimento Un insieme di nuovi documenti è stato aggiunto alla biblioteca.
alternativo Viene registrato che i documenti in questione sono parte della
biblioteca e sono disponibili al prestito.
Svolgimento Un insieme di documenti è stato eliminato dalla biblioteca. Vie-
alternativo ne registrato che i documenti in questione non sono più parte del
patrimonio della biblioteca.
Postcondizione La descrizione della biblioteca mantenuta dal sistema informati-
co è aggiornata rispetto alla situazione reale.
Descrizione Questo caso d’uso agisce sulla descrizione della biblioteca, e in
particolare sull’elenco dei documenti e sull’elenco dei prestiti, in
modo che tale descrizione rappresenti fedelmente quanto suc-
cede nella realtà (prestiti, restituzioni, aggiunte e alienazioni di
documenti).

Tabella 3.4: Descrizione del caso d’uso AggiornaSituazione.


CAPITOLO 3. ANALISI DEI REQUISITI Page 89

Caso d’uso Restituzione


Tipo Primario (è sollecitato direttamente dall’utente).
Precondizione Un insieme di documenti è stato restituito alla biblioteca.
Svolgimento Viene registrato che i documenti in questione sono nuo-
normale vamente disponibili per il prestito (si veda il caso d’uso
AggiornaSituazione). I prestiti corrispondenti sono classificati
come conclusi.
Postcondizione Lo stato del sistema è stato aggiornato in modo da rappresen-
tare che il prestito si è concluso e il documento restituito è
nuovamente presente in biblioteca e quindi disponibile.
Descrizione Questo caso d’uso si serve del caso d’uso secondario
AggiornaSituazione per agire sulla descrizione della biblio-
teca, e in particolare sull’elenco dei documenti e sull’elenco dei
prestiti, in modo che tale descrizione rappresenti fedelmente la
situazione conseguente alla restituzione.

Tabella 3.5: Descrizione del caso d’uso Restituzione.

• L’operazione di disabilitazione consiste nel marcare l’utente in mo-


do che venga escluso dal servizio di prestito.

• La gestione prestiti è una funzionalità secondaria che aggiorna lo


stato dei prestiti, registrandone le scadenze e associandovi le opera-
zioni (solleciti, multe, ecc.) compiute.

Come nel caso delle funzionalità disponibili per l’utente, illustrate nel-
le Tabelle da 3.1 a 3.6, anche per le azioni amministrative sarebbe possibi-
le (e probabilmente opportuno) integrare i diagrammi dei casi d’uso con
delle descrizioni testuali più dettagliate. Tuttavia omettiamo questo pas-
saggio per ragioni di spazio e anche perché riteniamo che a questo punto
il lettore sia in grado di scrivere queste descrizioni da solo.
Concludiamo l’esposizione dei requisiti del sistema riportando il dia-
gramma dei casi d’uso delle ricerche bibliografiche (Figura 3.3).
Il diagramma della Figura 3.3 ci comunica che le ricerche bibliogra-
fiche possono essere specializzate in tanti tipi di ricerche che si svolgo-
no in base a criteri specifici: quali parametri devono essere forniti, quali
risultati si ottengono, quali tipi di raffinamento possono essere fatti, ecc.
A questo punto possiamo considerare completa la descrizione del si-
stema. Tuttavia occorre osservare che con i casi d’uso si possono descri-
vere bene solo i requisiti funzionali. Qualora il sistema fosse soggetto a
requisiti non funzionali rilevanti occorrerebbe specificarli altrimenti. A
questo proposito sono possibili diverse opzioni. La più banale è natural-
Page 90 CAPITOLO 3. ANALISI DEI REQUISITI

Caso d’uso RichiestaPrestito


Tipo Primario (è sollecitato direttamente dall’utente).
Precondizione Un utente ha richiesto un insieme di documenti.
Svolgimento L’utente registrato e abilitato ha richiesto un insieme di docu-
normale menti che possibile prendere in prestito e che sono disponi-
bili. Il prestito avviene regolarmente e mediante il caso d’uso
AggiornaSituazione lo si registra.
Svolgimento L’utente che ha richiesto il prestito non è registrato. Si segna-
alternativo la che l’utente non è registrato. Non viene apportata alcuna
modifica allo stato del sistema.
Svolgimento L’utente che ha richiesto il prestito non è abilitato. Si segnala che
alternativo l’utente non è abilitato. Non viene apportata alcuna modifica allo
stato del sistema.
Svolgimento L’utente registrato e abilitato ha richiesto un insieme di docu-
alternativo menti non concessi in prestito o non disponibili. Gli viene se-
gnalato che i documenti non possono essere prestati. Non viene
apportata alcuna modifica allo stato del sistema.
Svolgimento L’utente registrato e abilitato ha richiesto un insieme di docu-
alternativo menti, dei quali solo alcuni sono prestabili e disponibili. Questi
ultimi vengono prestati (come nello svolgimento normale). Per
gli altri viene segnalata l’indisponibilità.
Descrizione Affinché il prestito avvenga bisogna che l’utente sia registra-
to e abilitato, e che i documenti richiesti siano disponibili.
Questi controlli vengono effettuati come descritto nei casi d’u-
so secondari VerificaRegistrazione, VerificaAbilitazione,
VerificaDisponibilità. Se tutte le verifiche hanno suc-
cesso si procede come descritto nel caso d’uso secondario
AggiornaSituazione.

Tabella 3.6: Descrizione del caso d’uso RichiestaPrestito.


CAPITOLO 3. ANALISI DEI REQUISITI Page 91

Sollecito

<<include>> <<include>>
<<include>>

Verifica LetturaDati Gestione


Scadenze Utente Prestiti

Bibliotecario
<<include>>
<<include>> <<include>>

Disabilitazione
Utente

Figura 3.2: Diagramma dei casi d’uso: funzionalità richieste dal bibliotecario.

Ricerca
Bibliografica

Utente <<include>> <<include>>


<<include>>

RicercaPer RicercaPer RicercaPer


Autore Titolo Chiave

Figura 3.3: Diagramma dei casi d’uso: ricerche bibliografiche.


Page 92 CAPITOLO 3. ANALISI DEI REQUISITI

mente la solita specifica testuale, che si può associare ai vari elementi di


UML come documentazione. Per taluni tipi di requisiti relativi alle pre-
stazioni è possibile ricorrere ad annotazioni dei diagrammi di sequenza,
ad esempio per specificare che il tempo tra la richiesta di un servizio e
l’erogazione del servizio stesso deve essere inferiore a un limite dato.

3.1.3 Descrizione dettagliata degli scenari


Benché la descrizione del sistema software possa essere considerata com-
pleta, non è detto che la descrizione del sistema complessivo debba fer-
marsi a questo punto. Infatti occorre ricordare che il sistema informati-
co funzionerà interagendo continuamente con un processo in cui opera-
no persone (ed eventualmente anche sistemi esterni) che non sono sotto
il controllo del sistema software, ma che nondimeno concorrono in ma-
niera decisiva allo svolgimento delle operazioni cui esso contribuisce. Di
conseguenza la descrizione del sistema potrà dirsi completa solo se si spe-
cifica anche il funzionamento del processo in cui il sistema software si
trova immerso. In questo modo risulta possibile ragionare sul compor-
tamento del sistema complessivo e capire quindi se gli effetti desiderati
possono essere ottenuti o meno grazie al sistema informatico.
Consideriamo ad esempio la restituzione dei documenti presi in pre-
stito. Abbiamo visto che il sistema dispone di due funzionalità:
• l’operazione che aggiorna il sistema concludendo il prestito e rimet-
tendo a disposizione i documenti;

• l’operazione di verifica della scadenza dei prestiti e la conseguente


emissione dei solleciti.
Poiché è possibile che il bibliotecario non sia sempre disponibile quando
un utente desidera restituire dei documenti, possiamo ragionevolmente
ipotizzare di organizzare il processo come segue.
1. L’utente restituisce i documenti senza che la restituzione venga re-
gistrata.

2. Il bibliotecario registra le restituzioni concludendo cosı̀ il prestito e


rendendo disponibili i documenti restituiti.

3. Il bibliotecario verifica la scadenza dei prestiti.


Possiamo facilmente comprendere che solo la seconda e la terza ope-
razione sono supportate dal sistema e che la correttezza del processo è
CAPITOLO 3. ANALISI DEI REQUISITI Page 93

garantita solo se l’operazione 2 precede l’operazione 3. Questo fatto non


è comunque assicurato dal sistema: è il bibliotecario che deve ricordarsi
di eseguire sempre l’operazione 2 prima dell’operazione 3, altrimenti si
rischierebbe di considerare ancora in corso un prestito che in realtà si è
concluso nei tempi previsti.
Questo semplice frammento di processo può essere specificato me-
diante un diagramma delle attività, come mostrato nella Figura 3.4.

Utente Bibliotecario

Restituzione

Aggiorna
Situazione

Verifica
Scadenze

Figura 3.4: Diagramma delle attività: restituzione e successive attività.

Alternativamente, un caso d’uso può essere specificato descrivendo


le sue istanze o “scenari”. Ogni scenario esemplifica come possono svol-
gersi le cose nella realtà descrivendo il caso d’uso in una sua particola-
re occorrenza. A uno stesso caso d’uso corrispondono generalmente più
scenari diversi. Per alcuni di questi le differenze saranno inessenziali (ad
esempio l’utente prende in prestito Pinocchio, piuttosto che Tom Saw-
yer), nel senso che il sistema si comporta nello stesso modo per i diversi
scenari. Altre volte le differenze saranno sostanziali (per esempio il libro
richiesto è disponibile o indisponibile) causando diversi comportamenti
del sistema.
Gli scenari possono essere descritti mediante i diagrammi di sequen-
za. Benché sia possibile rappresentare più scenari alternativi in uno stes-
so diagramma di sequenza, è comune il caso in cui gli scenari siano trop-
pi, troppo complessi o troppo differenziati: pertanto un caso d’uso è nor-
malmente descritto da diversi diagrammi di sequenza.
Page 94 CAPITOLO 3. ANALISI DEI REQUISITI

Per descrivere correttamente gli scenari occorre tenere ben presente


un certo numero di caratteristiche dei diagrammi di sequenza.

• I diagrammi di sequenza sono una notazione che presuppone un


modello fondamentalmente orientato agli oggetti: protagonisti dei
diagrammi sono infatti oggetti e messaggi. Al contrario, i diagram-
mi dei casi d’uso non sono specificamente orientati agli oggetti. Ne
consegue che passare da una descrizione basata su casi d’uso a una
basata su diagrammi di sequenza è tutt’altro che banale: implica in-
fatti l’individuazione degli oggetti che partecipano alle diverse fun-
zionalità e la loro caratterizzazione in termini dei messaggi che sono
in grado di scambiarsi (cioè dei servizi che mettono a disposizione
e che richiedono). In conclusione, scrivere buoni diagrammi di se-
quenza richiede il completamento di un livello di analisi orientata
agli oggetti decisamente più approfondito rispetto alle conoscenze
necessarie per scrivere casi d’uso.

• La descrizione delle operazioni avviene a un livello di dettaglio piut-


tosto fine. Non solo occorre conoscere gli oggetti coinvolti in uno
scenario e i messaggi che si scambiano, ma anche sapere quali in-
formazioni questi ultimi possono contenere e da dove possono es-
sere recuperate. Questo implica conoscere i dati associati a ciascun
oggetto (informazioni accessibili direttamente) e le relazioni che le-
gano i vari oggetti e rendono quindi accessibili i dati degli oggetti
associati (informazioni disponibili indirettamente). Ciò rafforza ul-
teriormente l’osservazione suddetta, che è necessario un livello di
analisi orientata agli oggetti decisamente più approfondito rispetto
alle conoscenze necessarie per scrivere casi d’uso.

• Gli oggetti coinvolti in uno scenario non sono in generale solo quelli
del dominio informatico. Ricordiamoci infatti che vogliamo docu-
mentare degli scenari in cui l’obiettivo dell’utente non è l’uso del
sistema fine a se stesso, ma il soddisfacimento di una sua esigenza,
cui l’impiego dello strumento informatico è funzionale. Nel nostro
esempio uno scenario riguarda la corretta registrazione delle resti-
tuzioni e la corretta individuazione dei prestiti scaduti. Uno scena-
rio di questo genere comporterà sicuramente la presenza attiva di
un utente della biblioteca, che pure in questo particolare caso non
ha accesso diretto al sistema informatico. In conclusione, la descri-
zione degli scenari mediante diagrammi di sequenza richiede un’a-
CAPITOLO 3. ANALISI DEI REQUISITI Page 95

nalisi del problema che abbracci tutti gli elementi coinvolti, anche
quelli non appartenenti al dominio della soluzione informatica.

Per illustrare concretamente quanto detto sopra scriviamo il diagram-


ma di sequenza relativo alla gestione delle restituzioni di documenti da
parte degli utenti. Ipotizziamo che l’operazione di restituzione fisica dei
documenti non corrisponda direttamente e immediatamente ad alcuna
operazione svolta dal sistema informatico: come spesso accade nella realtà,
l’utente si limita a depositare i documenti sul banco delle restituzioni.
Poiché il banco non ha la capacità di inviare messaggi al bibliotecario,
costui solo in un secondo tempo e di sua iniziativa potrà (dovrà) comu-
nicare al sistema la conclusione dei prestiti e l’avvenuta restituzione dei
documenti, che diventano quindi nuovamente disponibili.
In questo modo è responsabilità del bibliotecario verificare se un li-
bro è stato restituito prima di controllare la scadenza dei prestiti (tra cui
ci sarà anche il prestito del libro in questione). Se il bibliotecario non si
attiene a questa procedura potrà classificare come scaduti prestiti che in
realtà si sono già conclusi. In sostanza quindi il successo delle operazio-
ni non dipende solo dalla correttezza del sistema informatico, ma anche
dalla correttezza della procedura seguita dal bibliotecario. Questo fatto
non è né nuovo né sorprendente, e lo possiamo accettare senza problemi.
Al massimo, se vogliamo aiutare il bibliotecario a non commettere errori,
possiamo inserire un avviso all’inizio di ogni operazione di controllo della
scadenza dei prestiti, con il quale invitiamo il bibliotecario a controllare
che non ci siano restituzioni da registrare, prima di procedere.
Il diagramma di sequenza riportato nella Figura 3.5 illustra le opera-
zioni di restituzione. Notiamo che tale diagramma ha le caratteristiche
annunciate in precedenza: è orientato agli oggetti, nel senso che vi com-
paiono oggetti e messaggi tra oggetti, è piuttosto dettagliato (mostrando
le iterazioni e le sequenze di operazioni elementari richieste) e coinvolge
numerosi elementi che non fanno parte del sistema informatico. Nel dia-
gramma della Figura 3.5 si sfrutta la possibilità —introdotta da UML 2—
di specificare un’iterazione (loop) di azioni. Nel nostro caso il ciclo può
essere compiuto da 0 a infinite volte, e ha termine quando il banco delle
restituzioni risulta vuoto.
Qualora si ritenga che nei diagrammi di sequenza o delle attività il
lettore possa far fatica a distinguere le classi del dominio informatico da
quelle dell’ambiente, si può indicare esplicitamente quali classi apparten-
gono all’ambiente. Per questo si può usare un diagramma come quello
mostrato nella Figura 3.6.
Page 96 CAPITOLO 3. ANALISI DEI REQUISITI

Interfaccia SistemaGestione
Bibliotecario Biblioteca
Bibliotecario

loop [0,*,not BancoRestituzioni.vuoto()]

aggiornaSituazione

aggiornaSituazione

controllaScadenze

controllaScadenze

Figura 3.5: Diagramma di sequenza: restituzione e successivi controlli sui


prestiti in scadenza.

ambiente

BancoRestituzioni Biblioteca SistemaGestioneBiblioteca


*

+ restituzione():void + aggiornaSituazione():void
+ verificaRestituzione (d:Date):void + controllaScadenze():void
Utente + richiestaRegistrazione (): void

<<interface>>
* InterfacciaBibliotecario

+ aggiornaSituazione():void
+ controllaScadenze():void
Bibliotecario + richiestaRegistrazione (): void

Figura 3.6: Diagramma delle classi: indicazione delle classi che appartengono
all’ambiente (e non al dominio informatico).
CAPITOLO 3. ANALISI DEI REQUISITI Page 97

Alternativamente, il processo di gestione delle restituzioni si può mo-


dellare con un diagramma delle attività analogo a quello della Figura 3.4,
ma più dettagliato: il diagramma della Figura 3.7 indica che è responsa-
bilità del bibliotecario verificare se ci sono restituzioni prima di passare a
controllare le scadenze dei prestiti.

Utente Bibliotecario

Verifica
Restituzione Restituzioni

[not restituzioniPresenti]

[restituzioniPresenti]

Aggiorna
Situazione

Verifica
Scadenze

Figura 3.7: Diagramma delle attività: restituzione e successivi controlli sui


prestiti in scadenza.

Il lettore attento avrà notato che il diagramma della Figura 3.7 presen-
ta un piccolo problema “tecnico”: se l’utente non effettua una restituzio-
ne l’attività VerificaRestituzioni non comincerà mai (infatti il suo ini-
zio dipende dalla terminazione di Restituzione) e quindi l’intera attività
va in stallo. Naturalmente questo genere di problemi va accuratamen-
te evitato: infatti chi si attenesse a specifiche come quella della Figura 3.7
potrebbe costruire un programma che rischia concretamente di bloccarsi.
Il processo corretto (o meglio, uno dei processi corretti) che risolve il
problema è modellato nella Figura 3.8.
Il problema in questo caso è risolto rendendo parallele le operazioni di
restituzione e verifica, ma assicurando che la verifica delle scadenze ab-
bia luogo solo una volta registrate tutte le restituzioni pendenti. A questo
scopo abbiamo separato le restituzioni del giorno precedente, che devono
essere considerate tutte prima di verificare le scadenze, da quelle odierne,
che possono invece seguire la verifica delle scadenze. Per la verità sarebbe
possibile indicare che la verifica delle scadenze e la gestione delle restitu-
Page 98 CAPITOLO 3. ANALISI DEI REQUISITI

Utente Bibliotecario

Apertura
Biblioteca

Restituzione
Verifica
RestituzioniDiIeri

[not restituzioniPresenti]
[restituzioniP
resenti]

Aggiorna
SituazioneDiIeri

Verifica
Scadenze

VerificaRestituzioni [not chiusuraBiblioteca]


Odierne

[not restituzioniPresenti]
[restituzioni
Presenti] [chiusuraBiblioteca]
Aggiorna
Situazione

Figura 3.8: Diagramma delle attività: restituzione e successivi controlli sui


prestiti in scadenza.
CAPITOLO 3. ANALISI DEI REQUISITI Page 99

zioni odierne sono svolte in parallelo, tuttavia nella Figura 3.8 si è deciso
di dare la precedenza al controllo delle scadenze, che in una biblioteca di
piccole dimensioni è un’attività di durata limitata e che si svolge in un’uni-
ca soluzione una volta al giorno (mentre le restituzioni possono avvenire
durante l’intero orario di apertura della biblioteca). È addirittura possi-
bile che la gestione delle restituzioni del giorno precedente e la gestione
delle scadenze siano effettuate prima dell’apertura al pubblico.
Quale sia il criterio utilizzato per determinare il giorno di restituzione
non è detto nel diagramma (dipende da come sono definite internamente
le attività VerificaRestituzioniDiIeri e VerificaRestituzioniDiOggi).
Poiché queste ultime sono attività non demandate al sistema informati-
co, ci basta sapere che nell’ambiente ci sono agenti capaci di eseguirle,
(i dettagli non sono rilevanti per il sistema informatico). Il diagramma
della Figura 3.8 tiene anche conto del fatto che le attività di restituzione
potrebbero essere molte, da parte di utenti diversi.
Nelle Figure 3.9 e 3.10 sono riportati i diagrammi di sequenza che illu-
strano le sequenze di operazioni innescate da una richiesta di registrazio-
ne. I due diagrammi illustrano rispettivamente il caso in cui la registrazio-
ne viene effettuata e quello in cui viene rifiutata in quanto l’utente risulta
già registrato.

Interfaccia SistemaGestione Registro


Bibliotecario Biblioteca Utenti
Utente Bibliotecario

richiestaRegistrazione

richiestaRegistrazione

richiestaRegistrazione

verificaRegistrazione

not registrato

effettuaRegistrazione

IDUtente

IDUtente

IDUtente

IDUtente

Figura 3.9: Diagramma di sequenza: registrazione di un nuovo utente.

Da notare che i diagrammi risultano un po’ semplificati: in realtà le


operazioni di richiesta dovrebbero essere dotate di parametri e in par-
Page 100 CAPITOLO 3. ANALISI DEI REQUISITI

ticolare l’utente deve comunicare i propri dati di identificazione perso-


nale (nome e codice fiscale) e il proprio indirizzo. Queste informazioni
arrivano al registro utenti, dove vengono memorizzate.
Nei diagrammi di sequenza abbiamo indicato esplicitamente che l’u-
tente per la registrazione si rivolge al bibliotecario, che a sua volta utilizza
l’interfaccia del sistema per sollecitare il sistema stesso. Nei diagrammi
dei casi d’uso quest’intermediazione non era rappresentata. Anche que-
sto è un limite dei casi d’uso, nei quali il ruolo degli attori non è definito
in maniera univoca: in questo caso l’attore che compare nel diagramma
(l’utente) è l’iniziatore e il beneficiario della funzionalità considerata, ma
non dialoga direttamente con il sistema informatico.

Interfaccia SistemaGestione Registro


Bibliotecario Biblioteca Utenti
Utente Bibliotecario

richiestaRegistrazione

richiestaRegistrazione

richiestaRegistrazione

verificaRegistrazione

registrato(IDUtente)

KO(IDUtente)

KO(IDUtente)

KO(IDUtente)

Figura 3.10: Diagramma di sequenza: registrazione rifiutata a un utente già


registrato.

3.1.4 Modello statico


La rappresentazione statica degli elementi che fanno parte del dominio
del problema è modellata in UML mediante un diagramma delle classi (o
più di uno, nel caso di problemi complessi).
Un primo diagramma che rappresenta alcune classi appartenenti al-
l’ambiente è stato riportato nella Figura 3.6. Un secondo diagramma, che
descrive gli elementi che fanno naturalmente parte del dominio del pro-
blema e che presumibilmente dovranno essere rappresentati anche nel
sistema di gestione della biblioteca, è riportato nella Figura 3.11. Tale dia-
CAPITOLO 3. ANALISI DEI REQUISITI Page 101

gramma è ancora incompleto, ma rappresenta abbastanza bene il possi-


bile risultato di una prima fase della modellazione statica: abbiamo indi-
viduato un primo insieme di classi e le principali relazioni esistenti tra tali
classi.
Le classi che compaiono nel diagramma della Figura 3.11 hanno un
significato immediatamente compresibile.
• La classe Biblioteca rappresenta la biblioteca.
• La classe Settore rappresenta i settori fisici e tematici della biblio-
teca.
• La collocazione fisica dei libri e degli altri documenti è rappresenta-
ta dalla classe Scaffale.
• La classe UtenteRegistrato rappresenta gli utenti della biblioteca.
Attenzione: questi non sono gli utenti in carne e ossa, bensı̀ le in-
formazioni sugli utenti registrati che sono mantenute nel sistema di
gestione della biblioteca.
• Il RegistroUtenti raccoglie le informazioni relative agli utenti regi-
strati.
• La classe Prestito rappresenta naturalmente i prestiti. Normalmen-
te gli eventi non vengono modellati mediante classi: ad esempio le
restituzioni, le registrazioni, ecc. non corrispondono ad alcuna clas-
se. Tuttavia i prestiti fanno eccezione per diversi motivi: non sono
solo eventi, ma hanno una durata e richiedono di essere descritti da
un insieme di informazioni (data di concessione, beneficiario, do-
cumenti oggetto del prestito, ecc.). Inoltre —come vedremo in det-
taglio più avanti— i prestiti hanno uno stato. Per tutti questi motivi
introduciamo una classe per rappresentarli.
• Le classe AzioneAmministrativa (specializzata in Sollecito e Multa)
rappresenta i provvedimenti che vengono presi nei confronti degli
utenti che trattengono i documenti oltre la scadenza del prestito.
Anche in questo caso avremmo potuto pensare di trattare queste
azioni come operazioni che il sistema svolge, anziché come classi.
Abbiamo invece usato classi per poter memorizzare i dati relativi a
multe e solleciti per tutto il tempo necessario. AzioneAmministra-
tiva è una classe astratta, nel senso che la sua definizione non sarà
completa: solo le specializzazioni sono dotate di una descrizione
completa, e quindi potranno essere istanziate.
Page 102 CAPITOLO 3. ANALISI DEI REQUISITI

UtenteRegistrato RegistroUtenti
1

1
Biblioteca
0..*
(from ambiente)
Azione Prestito
Amministrativa
0..2 1

1
prestitoCorrente 0..1 0..* prestitiConclusi

1 1 1..*

Sollecito Multa Documento Scaffale Settore


1..* 1
1 0..* 1

Descrizione 1 Libro Audio Video Rivista Articolo


Documento 1 1..*

Figura 3.11: Diagramma delle classi preliminare.


CAPITOLO 3. ANALISI DEI REQUISITI Page 103

• La classe Documento rappresenta naturalmente i documenti conser-


vati presso la biblioteca, e in particolare quelli concessi in presti-
to. La classe è specializzata per rappresentare i diversi tipi di do-
cumenti, caratterizzati da proprietà specifiche. Abbiamo quindi le
sottoclassi Libro, Audio, Video, e Rivista. Quest’ultima è a sua vol-
ta definita come una composizione di articoli, modellati dalla classe
Articolo. La classe Documento è astratta: solo le sue specializzazioni
possono essere istanziate.

• La classe DescrizioneDocumento raccoglie collezioni di informazio-


ni relative a un documento. Queste informazioni sono utili in fase
di ricerca.

Il diagramma delle classi riportato nella Figura 3.11 indica anche le


relazioni esistenti tra le classi e la molteplicità delle relazioni stesse. In-
dicare la molteplicità delle relazioni non serve solo a rendere il modello
più completo o a fornire maggiori informazioni agli implementatori. In
realtà specificare correttamente le relazioni e la loro cardinalità porta so-
litamente ad acquisire una conoscenza del problema più dettagliata e ap-
profondita, permettendo cosı̀ di scoprire incongruenze e incompletezze
delle specifiche. Ad esempio, quando consideriamo la molteplicità del-
la relazione tra prestiti e documenti dobbiamo rispondere alla seguente
domanda: un singolo prestito può riguardare più documenti? Le possibi-
li risposte (e soprattutto la loro spiegazione) consentono di approfondire
significativamente la conoscenza del problema, anzi spesso inducono ul-
teriori azioni di raffinamento o estensione delle specifiche. Nel nostro
caso le risposte possibili sono le seguenti.

• Sı̀, un prestito può avere come oggetto diversi documenti. In que-


sto caso occorre prevedere come gestire la possibilità che un utente
prenda in prestito più documenti e li restituisca in tempi diversi: bi-
sogna che il sistema ricordi le “restituzioni parziali”. Consideriamo
il caso in cui l’utente che ha preso in prestito due libri ne restitui-
sca uno, che diventa nuovamente disponibile e viene quindi preso
in prestito da un altro utente. Quando il primo utente restituisce il
secondo libro, occorre che il sistema riconosca questa restituzione
come conclusione del prestito, benché il primo libro sia già oggetto
di un nuovo prestito.
Chiaramente questa ipotesi è relativamente complicata da specifi-
care.
Page 104 CAPITOLO 3. ANALISI DEI REQUISITI

• Sı̀, ma limitatamente a documenti dello stesso tipo. Quest’ipotesi


è giustificata dal fatto che documenti di tipo diverso hanno durate
di prestito diverse. In effetti è ragionevole che i libri vengano pre-
stati per periodi più lunghi rispetto ad esempio alle videocassette o
ai DVD, che richiedono un tempo di fruizione generalmente infe-
riore. L’omogeneità dei prestiti renderebbe possibile imporre all’u-
tente di restituire in un’unica soluzione tutti i documenti corrispon-
denti a un unico prestito. In caso contrario ci si troverebbe in una
situazione analoga a quella descritta al punto precedente.

• No, ogni prestito riguarda esattamente un documento. Questa si-


tuazione è la più facile da specificare e da gestire, perché richiede
semplicemente che quando un utente prende in prestito più docu-
menti in un colpo solo vengono creati altrettanti oggetti di classe
Prestito. Quest’operazione può essere affidata al sistema di ge-
stione, che è chiaramente in grado di effettuarla automaticamente,
senza aggravi di lavoro per il bibliotecario.

Il caso dei prestiti esemplifica piuttosto chiaramente le problematiche


connesse con l’attribuzione della molteplicità delle relazioni.
Vediamo ora le altre relazioni e le loro molteplicità.

• La relazione tra Biblioteca e RegistroUtenti indica composizio-


ne, nel senso che il registro utenti non potrebbe esistere se non as-
sociato alla biblioteca. Questa relazione è piuttosto particolare, in
quanto di entrambe le classi esisterà un’unica istanza, e pertanto la
relazione non può che essere uno-a-uno. Le relazioni di questo ti-
po sono da valutare attentamente, perché in questi casi è sempre
possibile “fondere” le due classi, ottenendone una sola avente tutte
le proprietà delle classi di partenza. Nel caso della biblioteca pos-
siamo pensare a un’evoluzione che richieda l’esistenza di molteplici
registri, ad esempio per classificare utenti di tipo diverso: potrebbe
esserci un registro degli utenti-studenti, uno degli utenti-ricercatori,
uno degli utenti normali, ecc. Quest’evoluzione non cambierebbe la
natura della relazione, ma la molteplicità diverrebbe uno-a-molti.
Per rendere possibile ciò senza dover modificare la struttura del dia-
gramma, manteniamo distinte le classi Biblioteca e RegistroU-
tenti.

• La relazione tra RegistroUtenti e UtenteRegistrato è di composi-


zione e —come accade generalmente per questo tipo di relazioni—
CAPITOLO 3. ANALISI DEI REQUISITI Page 105

è uno-a-molti. Il registro può contenere un numero qualunque di


utenti (anche nessuno), mentre ogni utente compare in esattamente
un registro.

• Le relazioni tra Biblioteca e Settore e tra Settore e Scaffale sono


anch’esse relazioni di composizione. La biblioteca conterrà sempre
almeno un settore e ogni settore conterrà almeno uno scaffale.

• La relazione tra Documento e Prestito è già stata parzialmente di-


scussa riguardo alla possibilità di associare più documenti allo stes-
so prestito. A tal proposito preferiamo associare un solo documen-
to a ciascun prestito, non limitando peraltro il numero di prestiti
contemporaneamente associabili a un utente. Considerando l’altro
verso dell’associazione, dobbiamo stabilire se un documento può
essere associato a molteplici prestiti. Se considerassimo il proble-
ma in modo superficiale saremmo immediatamente portati a con-
cludere che i casi sono due: o il documento è in prestito, e allora è
in relazione solo con quel prestito, oppure non lo è, e allora non è
legato ad alcun prestito. Quindi la molteplicità di questo estremo
della relazione sarebbe 0..1. In realtà questo ragionamento è corret-
to solo rispetto al prestito corrente. Se consideriamo anche i prestiti
ormai conclusi, un documento può tranquillamente essere in rela-
zione con più prestiti, essendo stato associato a prestiti diversi in
tempi diversi. In conclusione, la relazione viene classificata come
uno-a-molti se il sistema deve memorizzare solo il prestito corrente
e come molti-a-molti se si desidera tenere traccia anche dei presti-
ti conclusi. Considerando che tra i requisiti utente potrebbe esserci
anche la necessità di produrre statistiche sull’utilizzo della biblio-
teca, la seconda ipotesi appare essere preferibile. Per comodità di-
stingueremo la relazione con il prestitoCorrente da quelle con i
prestitiConclusi.

• La relazione tra UtenteRegistrato e Prestito è relativamente ba-


nale: a un utente possono essere associati più prestiti (compresi
quelli conclusi), mentre a un prestito è associato un unico benefi-
ciario. Potrebbe esistere un limite al numero di prestiti di cui uno
stesso utente può beneficiare a un dato istante. Poiché la relazione
non fa distinzione tra prestiti attivi e prestiti conclusi, non c’è mo-
do di esprimere questo limite nel diagramma delle classi. Agire sulla
molteplicità della relazione, limitando a 10 il numero di istanze di
prestiti associabili a ogni utente, sarebbe scorretto, perché impliche-
Page 106 CAPITOLO 3. ANALISI DEI REQUISITI

rebbe che, una volta che un utente abbia beneficiato di 10 prestiti,


non può più prendere in prestito i documenti della biblioteca. Que-
sto non è certo quello che si vuole dire! Il problema si risolve facil-
mente facendo ricorso a OCL, come illustrato nel paragrafo dedicato
alla specifica dei vincoli cui deve sottostare il sistema.

• La relazione tra Prestito e AzioneAmministrativa presenta proble-


matiche analoghe alla relazione precedente. Infatti, se da un par-
te è indiscutibile che ogni provvedimento si applica al prestito sca-
duto corrispondente, dall’altra a un prestito possono corrispondere
nessun provvedimento (quando il prestito è in uno stato regolare),
un sollecito (prestito scaduto da poco) o un sollecito e una multa
(prestito scaduto da molto). Tuttavia, il diagramma delle classi non
esclude i casi in cui a un prestito sia associata solo una multa op-
pure due solleciti. Per limitare il comportamento del sistema ai casi
desiderati si ricorre nuovamente a OCL, come illustrato più avanti.

• La relazione tra Documento e DescrizioneDocumento è uno-a-molti.


Il fatto che ogni documento abbia associata la sua propria descri-
zione non è sorprendente. Invece il fatto che ciascuna descrizione si
applichi a più documenti è dovuto a diverse cause, ad esempio nella
biblioteca possono essere presenti più copie dello stesso documen-
to: una sola descrizione sarà quindi associata a tutte le istanze che
rappresentano copie dello stesso documento. Teniamo presente an-
che che descrizioni molto astratte potrebbero applicarsi addirittura
a copie di libri diversi: ad esempio, una descrizione che dicesse so-
lo che si tratta di un libro per ragazzi sarebbe applicabile a molti
libri. Inoltre, descrizioni anche abbastanza dettagliate potrebbero
applicarsi sia a un libro che al film tratto dal libro.

• La relazione tra Rivista e Articolo è una relazione di composizione


uno-a-molti. Questo non ci sorprende, poiché una rivista è compo-
sta di articoli, e un articolo non esiste autonomamente, ma solo nel
contesto della rivista cui appartiene.

Le relazioni di generalizzazione presenti nel modello hanno un signi-


ficato abbastanza ovvio. Ricordiamo che ha senso introdurre una spe-
cializzazione se e solo se la classe specializzata ha un significato sostan-
zialmente diverso rispetto alla classe base. Nel nostro caso ciascuna clas-
se specializzata introduce nuovi attributi propri non presenti nella classe
base, giustificando cosı̀ senza dubbio la relazione di specializzazione.
CAPITOLO 3. ANALISI DEI REQUISITI Page 107

A questo punto abbiamo una prima versione del diagramma delle clas-
si. È un buon momento per sottoporre il nostro modello a un vaglio criti-
co, in modo da farne emergere (e conseguentemente correggere) gli even-
tuali difetti. In particolare possiamo fare le seguenti considerazioni.

• Nel primo modello abbiamo trascurato il fatto che un documento


può trovarsi nello scaffale delle novità. A questo scopo introducia-
mo la classe ScaffaleNovità e le relative relazioni. Per specifica-
re che un documento deve necessariamente trovarsi o sullo scaffale
delle novità o sul suo scaffale di residenza dovremo ancora una volta
ricorrere a OCL.

• Un’altra cosa che abbiamo dimenticato è che i settori possono es-


sere organizzati in sottosettori. Ciò può essere rappresentato sem-
plicemente introducendo una relazione di aggregazione della classe
Settore con se stessa. L’opzionalità vale in entrambi i sensi: un set-
tore non deve necessariamente avere sottosettori, né deve apparte-
nere necessariamente a un settore di livello superiore (può appar-
tenere direttamente alla biblioteca). Notate ancora che in conse-
guenza della nuova relazione diventa opzionale anche la relazione
tra Settore e Biblioteca (un settore può appartenere a un altro set-
tore invece che alla biblioteca). Per dire che un settore deve però op-
tare comunque per una delle due appartenenze ricorreremo ancora
a OCL. Con la nuova organizzazione occorre riconsiderare anche la
relazione tra settori e scaffali: poiché un settore può avere sottoset-
tori, ma nessuno scaffale proprio, la molteplicità degli scaffali non è
più 1..*, ma 0..*.

• Nel diagramma della Figura 3.11 gli articoli ereditano la relazione


con gli scaffali: ciò appare poco sensato, visto che ogni articolo ri-
siede inevitabilmente sullo scaffale in cui si trova la rivista che lo
comprende.

Per correggere i problemi descritti sopra riscriviamo il diagramma del-


le classi come illustrato nella Figura 3.12. In questo diagramma si usa la
convenzione che la mancanza di indicazioni implica molteplicità uguale
a uno, mentre * indica molteplicità 0..*. Da notare che queste convenzioni
non sono standard, ma sono adottate da alcuni strumenti, tra cui Posei-
don. In UML, la mancanza di indicazioni significa semplicemente che la
molteplicità non è (ancora) stata definita.
Page 108 CAPITOLO 3. ANALISI DEI REQUISITI

Nel diagramma della Figura 3.12 gli articoli non sono più documen-
ti, e quindi non ereditano la relazione con gli scaffali. In conseguenza di
ciò è stato necessario modificare il diagramma per consentire agli artico-
li di continuare a essere associati a tutte quelle informazioni che prima
ereditavano dal documento.
• La classe DatiDocumento raggruppa le informazioni precedentemen-
te in Documento che servono sia alle specializzazioni di Documento
che ad Articolo. DatiDocumento è accessibile con il tipico meccani-
smo della delega: la rappresentazione del titolo e dell’autore è “de-
legata” da Documento a DatiDocumento. Tutte le specializzazioni di
Documento ereditano la relazione con DatiDocumento, quindi posso-
no accedere alle informazioni su titolo e autore. Anche Articolo de-
lega a DatiDocumento la rappresentazione delle stesse informazioni.

• DescrizioneDocumento non può più essere semplicemente associa-


ta a Documento perché l’associazione non sarebbe ereditata da Articolo,
che non è più una specializzazione di Documento. Abbiamo quindi
optato per l’associazione esplicita a tutte le classi interessate.
Il diagramma delle classi della Figura 3.12 introduce anche gli attributi
delle classi. Il significato degli attributi è evidente e non viene commen-
tato.
Notiamo che ci sono alcune sbavature: per esempio le riviste hanno
un autore, mentre il materiale audio ha un attributo Lingua che in mol-
ti casi non avrà senso (si pensi alle registrazioni di musica non canta-
ta). Queste sono comunque manchevolezze marginali che trascuriamo.
Il lettore può esercitarsi a modificare il diagramma per eliminarle (il pro-
blema dell’autore delle riviste è comunque risolto nel diagramma della
Figura 3.20).

3.1.5 Valutazione critica


Poiché il modello diventerà la base per la progettazione e successivamen-
te per l’implementazione del sistema di gestione della biblioteca, è oppor-
tuno sottoporlo a periodiche valutazioni critiche che ne mettano in luce
gli eventuali punti deboli. Da una tale analisi possono quindi emergere le
seguenti osservazioni.
• Potremmo includere le proprietà della classe DescrizioneDocumento
direttamente nella classe Documento, ottenendo cosı̀ un’unica clas-
CAPITOLO 3. ANALISI DEI REQUISITI Page 109

UtenteRegistrato
Azione Prestito
Amministrativa - idUtente: int
- dataConcessione: Date RegistroUtenti
- dataRinnovo: Date + beneficiario - nome: String
- data: Date - codiceFiscale: String
0..2 - dataConclusione: Date * *
- rinnovato: boolean - indirizzo: String
- chiuso: boolean - dataRegistrazione: Date
- abilitato: boolean
+ prestitoCorrente 0..* * + prestitiConclusi
Biblioteca
(from ambiente)
ScaffaleNovità 1..*
Sollecito Multa - numero: int
- ripiano: int
- importo: double * 0..1
Documento
0..1
- editore: String
- codiceDewey: String * 0..1 Scaffale 1..*
- numeroCopia: int
- sala: String *
* Settore
DatiDocumento - numero: int
- ripiano: int - argomento: String
- titolo: String
+ supersettore
- autore: String

0..1
+ sottosettore *

Articolo Rivista Video Audio Libro


- pagInizio: int 1..* - volume: int - sistema: String - tipo: String - ISBN: String
- pagFine: int - numero: int - durata: int - durata: int - numeroPagine: int

* * * * *

DescrizioneDocumento
- genere:String
- argomento:String
- lingua:String
0..1 - parolaChiave: String[]
- riassunto:String

Figura 3.12: Diagramma delle classi con attributi.


Page 110 CAPITOLO 3. ANALISI DEI REQUISITI

se. In effetti l’operazione è possibile: la classe DescrizioneDocumento


non ha uno stato, quindi non esiste il problema di conciliarne il
comportamento con quello della classe Documento, inoltre possia-
mo prevedere che i metodi della classe DescrizioneDocumento (non
ancora esplicitati) possano essere trasferiti alla classe Documento. Re-
sta da valutare se l’operazione sia conveniente. A favore dell’elimi-
nazione c’è una maggior compattezza del modello, contro una mag-
giore flessibilità. Infatti è prevedibile che le funzionalità di ricerca si
basino fortemente sul contenuto della classe descrizione, che quin-
di potrà facilmente essere soggetta a modifiche in dipendenza delle
modalità di ricerca richieste dall’utente, mentre è prevedibile che la
classe documento resti stabile. Qui abbiamo optato per lasciare le
due classi distinte. A livello di analisi non ha molta importanza co-
me rappresentare l’informazione, purché sia fatto correttamente. In
ogni caso sarà poi il progettista a valutare se e quali classi eventual-
mente fondere per ottenere un sistema più efficiente e/o facilmente
gestibile.

• Alcune classi (come Biblioteca e RegistroUtenti) esisteranno pre-


sumibilmente in un’unica istanza. Questo in generale va contro l’i-
dea di classe, che normalmente rappresenta un “tipo” cui si confor-
mano diversi oggetti. Del RegistroUtenti abbiamo già detto: po-
trebbero in futuro essercene diversi, in rappresentanza di diverse
comunità di utenti: studenti, ricercatori, soci sostenitori, ecc. Per
quanto riguarda la classe Biblioteca si potrebbero fare considera-
zioni analoghe: potremmo in futuro dover rappresentare altre bi-
blioteche oltre la nostra, ad esempio in base a una convenzione per
cui i nostri utenti possono richiedere documenti conservati presso
altre biblioteche. Infine, scrivere le specifiche del sistema in assen-
za della classe Biblioteca complicherebbe le cose, rendendo pro-
blematica la leggibilità e intuitività del modello. Abbiamo quindi
optato per mantenere entrambe le classi in questione.

• Un’altra caratteristica che va verificata è la “navigabilità” del model-


lo. In sostanza si richiede che ogni oggetto attivo sia raggiungibile
a partire degli altri o attraverso un identificatore univoco. Il nostro
modello garantisce questa proprietà.
CAPITOLO 3. ANALISI DEI REQUISITI Page 111

3.1.6 Qualcosa di più formale


Ci occupiamo ora di specificare alcune caratteristiche del modello che
non possono essere rappresentate adeguatamente mediante i formalismi
grafici di UML. Ogni vincolo è descritto sia informalmente, sia mediante
OCL, il linguaggio logico fornito da UML per esprimere vincoli sui mo-
delli. L’uso di OCL non è molto diffuso in ambito industriale, tuttavia
consente di scrivere specifiche decisamente più precise di quelle che si
possono ottenere mediante notazioni informali. Lasciamo al lettore con-
frontare le specifiche OCL con le corrispondenti specifiche informali, e di
conseguenza valutare quando siano preferibili le une o le altre.
Relativamente all’uso di OCL, ricordiamo che ogni qual volta occor-
ra riferirsi alle associazioni tra classi è possibile utilizzare il nome del-
l’associazione oppure il nome del ruolo della classe nell’associazione op-
pure —quando tali nomi non siano definiti— il nome della classe stes-
sa. Ad esempio, nel contesto della classe Documento utilizzeremo il ter-
mine Scaffale per indicare l’associazione che collega un documento allo
scaffale che lo ospita.
Nella Tabella 3.7 sono riportati i vincoli cui è soggetta la classe Documento
o, per essere più precisi, le sue istanze.

Un documento deve essere collocato su uno scaffale oppure sullo scaffale


delle novità.

context Documento
inv: self.scaffale->size() + self.scaffaleNovita->size() = 1

Letteralmente il vincolo specifica che il numero di scaffali associati al do-


cumento, più il numero di scaffali novità su cui si trova è esattamente uno.
In altre parole, è sempre vero che esiste una e una sola istanza di associa-
zione tra quelle indicate (poiché evidentemente size non può mai essere
negativo).

Tabella 3.7: Vincoli relativi alla classe Documento.

Nella Tabella 3.8 sono riportati i vincoli cui sono soggette le relazio-
ni che coinvolgono istanze della classe Prestito. Nella Tabella 3.9 sono
riportati i vincoli cui sono soggette le relazioni che coinvolgono istanze
della classe Settore.
Per essere del tutto precisi bisognerebbe specificare un’ulteriore vin-
colo che riguarda i settori: nessuno deve essere sottosettore (o superset-
Page 112 CAPITOLO 3. ANALISI DEI REQUISITI

A un utente non possono mai essere associati più di max prestiti


contemporaneamente.

context UtenteRegistrato
inv: self.prestito->select(chiuso = false)->size() <= max

A un prestito possono essere associate: un sollecito e nessuna mul-


ta, un sollecito e una multa oppure nessuna azione amministrativa.
Nessun’altra combinazione di azioni ammistrative è possibile.

context Prestito
inv: (self.sollecito->isEmpty() and self.multa->isEmpty()) or
(self.sollecito->size() = 1 and self.multa->isEmpty()) or
(self.sollecito->size() = 1 and self.multa->size() = 1)

Tabella 3.8: Vincoli relativi alle relazioni che coinvolgono la classe Prestito.

tore) di se stesso. Questa proprietà deve valere anche per le relazioni in-
dirette: ad esempio non deve capitare che il settore A sia sottosettore di
B che è sottosettore di C che è sottosettore di A. OCL non fornisce alcun
meccanismo che consenta di specificare questo tipo di vincolo in modo
semplice. Sarebbe possibile complicare il modello in modo da poter scri-
vere un vincolo OCL; tuttavia —anche in considerazione dell’ovvietà del
vincolo— si preferisce omettere questa specifica.

3.1.7 Metodi e specifiche funzionali

Finora ci siamo concentrati sulla modellazione delle classi in termini di


attributi e relazioni. Per completare la specifica è necessario indicare qua-
li sono i metodi di cui ciascuna classe dispone.
Ricordiamo che, essendo ancora nella fase di specifica dei requisiti, in-
dicare i metodi associati alle classi ha il significato di esplicitare quali sono
le sollecitazioni a cui ciascuna classe deve rispondere, realizzando il com-
portamento suo proprio (nel caso di una classe appartenente al dominio
del problema) o il comportamento desiderato dall’utente. In altre parole,
i metodi non vengono riportati per indicare come il sistema realizza certe
funzionalità, bensı̀ per dettagliare il comportamento delle classi che co-
CAPITOLO 3. ANALISI DEI REQUISITI Page 113

Un settore appartiene necessariamente alla biblioteca oppure a un altro


settore (di cui è sottosettore).

context Settore
inv: (self.biblioteca->size()=1 and self.supersettore->size()=0) or
(self.biblioteca->size()=0 and self.supersettore->size()=1)

Un settore può contenere sottosettori o (non esclusivamente) scaffali. In


nessun caso può contenere né alcun sottosettore né alcuno scaffale.

context Settore
inv: self.sottosettore->union(self.scaffale)->notEmpty()

Tabella 3.9: Vincoli relativi alle relazioni che coinvolgono la classe Settore.

stituiscono il sistema e quali sono gli eventi che determinano la dinamica


del sistema.
In generale ogni classe fornisce alcuni insiemi di metodi aventi il si-
gnificato seguente.

• Metodi di inizializzazione. Questi metodi (talvolta chiamati “costrut-


tori” nei linguaggi di programmazione orientati agli oggetti) sono
particolarmente rilevanti. Infatti, benché l’importanza di inizializ-
zare i propri dati e variabili sia ben nota e valida per tutti i linguaggi
di programmazione, nel caso degli oggetti la situazione è ancor più
delicata. Non si tratta infatti solo di inizializzare il singolo attributo,
ma anzi di inizializzare diversi attributi, che —tutti insieme— devo-
no costituire un insieme coerente. Definire costruttori scorretti può
portare alla creazione di oggetti che sono internamente incoerenti e
che quindi possono portare il sistema a evolvere in modo imprevisto
e incontrollato.

• Metodi di lettura degli attributi. Per garantire un adeguato livello di


astrazione, gli attributi sono solitamente privati, cioè non accessi-
bili direttamente da altre classi del sistema. I metodi in questione
servono a fornire all’esterno notizie sul contenuto dell’oggetto: na-
turalmente non è detto che i dati coincidano coi valori degli attribu-
ti, ad esempio potrebbero esserne delle opportune funzioni. Que-
sto garantisce la visione astratta dell’oggetto. In particolare alcuni
Page 114 CAPITOLO 3. ANALISI DEI REQUISITI

metodi forniranno notizie sullo stato astratto dell’oggetto, che può


dipendere da un insieme di attributi (al limite anche tutti).

• Metodi per modificare gli attributi privati e/o lo stato della classe.
Per quanto riguarda l’accesso agli attributi della classe vale quan-
to detto al punto precedente. La differenza sta nel fatto che questi
metodi provocano un cambiamento dei valori degli attributi dell’og-
getto, e quindi possibilmente anche del suo stato.

• Metodi per raggiungere gli oggetti associati, cioè metodi per “navi-
gare” lungo i link (istanze di associazioni) dell’oggetto. Le associa-
zioni a un oggetto possono essere private, nel qual caso l’accesso
agli oggetti associati può essere ottenuto solo mediante questo tipo
di metodi, che restituiscono uno o più identificatori di oggetti.

• Metodi per aggiungere o rimuovere link privati. Questi metodi ser-


vono a manipolare l’insieme di link privati posseduti dall’oggetto.

La classe UtenteRegistrato con i suoi metodi è riportata nella Figu-


ra 3.13. Il lettore deve immaginare che queste specifiche confluiscano nel
diagramma della Figura 3.12.

RegistroUtenti

1
+ trovaUtente(id: Integer): utenteRegistrato
+ trovaUtente (CF:String): utenteRegistrato
+ verificaRegistrazione (CF: String): boolean
+ effettuaRegistrazione (n: String, CF: String, ind: String): int
0..*

UtenteRegistrato
- idUtente: int
- nome: String
- codiceFiscale: String
- indirizzo: String
- dataRegistrazione: date
- abilitato: boolean :UtenteRegistrato
<<create>>
+ make (n: String, CF: String, ind: String): void
+ disabilita(): void
+ abilitato (): boolean
+ nome (): String
+ recapito (): String
+ scriviNome (n: String): void
+ scriviIndirizzo (s: String): void

Figura 3.13: Le classi UtenteRegistrato e RegistroUtenti.

Abbiamo un metodo di inizializzazione (make) che ha l’obiettivo di ga-


rantire che tutti i dati necessari (nome, indirizzo, ecc.) siano presenti e che
l’oggetto sia inizialmente nello stato corretto (ad esempio un nuovo uten-
te deve sempre essere abilitato). Osserviamo la notazione utilizzata in
CAPITOLO 3. ANALISI DEI REQUISITI Page 115

UML 2.0 per indicare un metodo costruttore: l’istanza di UtenteRegistrato


è legata da una relazione di dipendenza avente stereotipo create al
metodo make della classe UtenteRegistrato.
Relativamente ai metodi di lettura possiamo osservare come venga
effettivamente realizzata un’interfaccia astratta. Per esempio, il metodo
abilitato() indica lo stato di abilitazione dell’utente senza che il chia-
mante debba sapere come tale informazione sia codificata. Nel nostro
caso esiste un attributo abilitato, ma avremmo potuto rappresentare la
stessa informazione con un link a un oggetto che rappresenta la disabili-
tazione, senza per questo dover definire il metodo abilitato() in modo
diverso.
Occorre ricordare che UML offre anche la possibilità di specificare il si-
gnificato dei metodi, mediante opportune pre- e post-condizioni espresse
in OCL. È questo un modo dichiarativo di specificare i metodi. Infatti non
si entra nel merito dei meccanismi di funzionamento dei metodi, ovvero
di “come siano fatti dentro”. Viceversa si specifica l’effetto del metodo per
differenza tra la situazione immediatamente precedente l’esecuzione del
metodo stesso e la situazione alla conclusione dell’esecuzione. In partico-
lare le precondizioni indicano cosa sia sempre vero prima dell’esecuzione
del metodo, mentre le postcondizioni dicono cosa risulta essere vero do-
po. Notiamo che le precondizioni hanno un duplice ruolo: da una parte
definiscono la situazione precedente, in modo da poterla poi confrontare
con la situazione conclusiva, dall’altra limitano le condizioni di applicabi-
lità del metodo, ponendo dei requisiti su cosa debba essere vero prima di
eseguire il metodo. Si suppone quindi che un metodo venga invocato solo
quando la precondizione è soddisfatta; altrimenti il suo comportamento
non è prevedibile.
Ad esempio, benché il significato di un metodo costruttore sia general-
mente chiaro, esiste comunque sempre la possibilità di specificarlo preci-
samente con OCL. Nel nostro caso può essere interessante definire il cri-
terio di scelta dell’attributo IDUtente, come si vede nella specifica OCL se-
guente (tra gli utenti che compaiono nel registro utenti prima della crea-
zione del nuovo utente non ce n’è alcuno avente un IDUtente uguale a
quello assegnato al nuovo utente).
context UtenteRegistrato::make(n: String, CF: String, ind: String): void
pre: not UtenteRegistrato.allInstances()->
exists(u | u.codiceFiscale = CF)
post: self.nome = n and self.codiceFiscale = CF and
self.indirizzo = ind and self.abilitato = true and
self.dataRegistrazione = "oggi" and
Page 116 CAPITOLO 3. ANALISI DEI REQUISITI

not self.registroUtenti.utenteRegistrato@pre->
exists(u | u.IDUtente = self.IDUtente)

Il significato del metodo disabilita() può essere specificato come


segue (la pre-condizione uguale a True indica che il metodo è invocabile
sempre):

context UtenteRegistrato::disabilita(): void


pre: self.abilitato = true
post: self.abilitato = false

Il significato del metodo abilitato() può essere specificato come se-


gue:

context UtenteRegistrato::abilitato(): boolean


post: result = self.abilitato

I metodi delle altre classi non sono riportati qui per motivi di spazio.
Il lettore può cimentarsi con il completamento della specifica dei metodi
omessi.

3.1.8 Dinamica del sistema


Per modellare la dinamica del sistema UML fornisce diversi strumenti, il
più sintetico ed espressivo dei quali è costituito dai diagrammi degli stati.
Nel caso della biblioteca la classe con la dinamica più ricca e articolata
è sicuramente Prestito. Il diagramma degli stati di un prestito è riportato
nella Figura 3.14.
Il significato del diagramma è piuttosto intuitivo e non richiede parti-
colari spiegazioni. Ci limitiamo a ricordare che il costrutto UML after(t)
indica un evento che si verifica t unità di tempo dopo l’ingresso nello sta-
to corrente. Tipicamente serve a indicare la durata della permanenza in
un dato stato. Nella Figura 3.14 indica che dopo una permanenza di una
settimana nello stato SollecitatoDueVolte il prestito passa nello stato
CausaDiDisabilitazione.
Il diagramma degli stati di un UtenteRegistrato è riportato nella Fi-
gura 3.15.
Non si è previsto di riabilitare l’utente in alcun caso. Benché confor-
me ai requisiti utente, quest’interpretazione delle specifiche è piuttosto
restrittiva, nel senso che qualora un utente sia stato disabilitato per erro-
re il sistema deve permettere di ripristinare la situazione corretta. Si può
CAPITOLO 3. ANALISI DEI REQUISITI Page 117

/dataConcessione = oggi; dataScadenza = oggi + 15

Normale

rinnovo/ Iniziale
dataScadenza +=7
restituzione(d)/
dataChiusura=d

Rinnovato

[oggi>dataScadenza]/sollecito Chiuso

Scaduto

SollecitatoUnaVolta after (7 giorni)/ multa


restituzione(d)/
dataChiusura=d
SollecitatoDueVolte

CausaDiDisabilitazione

after (7 giorni)/
beneficiario.disabilita()

Figura 3.14: Diagramma degli stati della classe Prestito.

Abilitato
effettuaRegistrazione

disabilita/abilitato=false

Disabilitato

Figura 3.15: Diagramma degli stati della classe UtenteRegistrato.


Page 118 CAPITOLO 3. ANALISI DEI REQUISITI

quindi ipotizzare di inserire l’operazione di riabilitazione, anche se il bi-


bliotecario non dovrebbe usarla se non in casi eccezionali (ad esempio
per rimediare a errori, come detto sopra).
Il diagramma degli stati di un documento è riportato nella Figura 3.16.
In tale diagramma si ipotizza che sia opportuno trattare in modo parti-
colare i documenti presi in prestito e mai più restituiti: a questo scopo
è stato introdotto uno stato apposito PermanentementeIndisponibile. Il
documento viene dato per disperso dopo due mesi dal prestito. Si sup-
pone che due mesi sia un tempo significativamente maggiore alla durata
massima consentita, compreso il tempo per rispondere ai solleciti.
Nel diagramma della Figura 3.16 ipotizziamo anche che tutti i docu-
menti siano dati in prestito. Se cosı̀ non fosse, basterebbe mettere un’op-
portuna guardia sulla transizione da Disponibile a InPrestito.

SulBancone
restituito

restituzione
Disponibile
acquisizione
restituzione
InPrestito
prestito

vendita
after (2 mesi)

PermanentementeIndisponibile

Venduto

Figura 3.16: Diagramma degli stati della classe Documento.

Le rimanenti classi non hanno una dinamica degna di nota e non ven-
gono quindi dotate di diagramma degli stati.

3.1.9 Alcune alternative


In questo paragrafo spieghiamo come differenti organizzazioni della bi-
blioteca reale possano facilmente essere rappresentate nel modello UML.
Non è detto che tutte le possibili organizzazioni considerate siano van-
taggiose o anche solo ragionevoli. Lo scopo è mostrare come UML ci
consenta di modellare situazioni diverse.
CAPITOLO 3. ANALISI DEI REQUISITI Page 119

Organizzazione dei settori della biblioteca


Finora abbiamo ipotizzato che uno scaffale appartenga a un unico set-
tore, corrispondente all’argomento condiviso da tutti i libri dello scaffale
stesso. La relazione tra SettoreVirtuale e Scaffale è uno-a-molti. Dato
un documento possiamo individuare univocamente lo scaffale e quindi il
settore di appartenenza. La biblioteca potrebbe però essere organizzata
diversamente.
Consideriamo una prima ipotesi in cui i documenti non sono raggrup-
pati per argomento, ma in base all’ordine di arrivo: ad esempio, uno scaf-
fale accoglie tutti i libri acquisiti in un certo periodo, indipendentemente
dal settore di appartenenza. I settori non esistono più in senso fisico, cioè
non c’è più un insieme di stanze contenente un insieme di scaffali con-
tenenti documenti dello stesso argomento. I settori continuano a esistere
solo in senso logico di argomento: grazie al supporto informatico si pos-
sono infatti creare e mantenere dei settori “virtuali” che raggruppano (ma
solo nella memoria del calcolatore) i documenti di argomento affine.

0..1

SettoreVirtuale
Biblioteca
* - argomento: String
+ sottosettore *

1..*

1..* *

Documento Scaffale
- editore: String * 0..1 - sala: String
- codiceDewey: String - numero: int
- numeroCopia: int - ripiano: int

Figura 3.17: Frammento di diagramma delle classi: settori virtuali.

Il frammento di diagramma delle classi riportato nella Figura 3.17 mo-


della la situazione esposta. Il settore virtuale contiene sottosettori o do-
cumenti. Ogni documento risiede anche su uno scaffale (come prima),
però Scaffale appartiene ora direttamente alla Biblioteca. Non c’è più
legame tra Settori e Scaffale. La relazione tra Settore e Documento è
ora molti-a-molti, poiché ipotizziamo che un documento possa apparte-
nere a diversi settori virtuali (ad esempio un libro di storia della mate-
Page 120 CAPITOLO 3. ANALISI DEI REQUISITI

matica può stare sia nel settore virtuale “Storia” che nel settore virtuale
“Matematica”).
Consideriamo ora una seconda alternativa, intermedia tra quella espo-
sta nella Figura 3.17 e quella originale della Figura 3.12. Supponiamo che
uno scaffale appartenga a un unico settore, che rappresenta l’argomento
principale di tutti i documenti dello scaffale stesso, tuttavia esistono “ar-
gomenti secondari” (che comunque corrispondono a settori fisici) cui un
documento può essere associato. Ad esempio, esiste un settore “Storia”
con degli scaffali pieni di libri di storia; in uno di questi ci sono alcuni li-
bri che sono anche libri di filosofia, ma che evidentemente non possono
essere fisicamente anche nel settore “Filosofia”. Il sistema deve assumersi
il compito di ascrivere “virtualmente” tali libri anche al settore “Filosofia”.

Biblioteca

0..1

1..*

Documento Scaffale Settore


- editore: String - sala: String
1..* 0..1
- codiceDewey: String - numero: int - argomento: String
- numeroCopia: int * 0..1 - ripiano: int

* + argomentoSecondario * *
+ sottosettore

Figura 3.18: Frammento di diagramma delle classi: argomenti secondari.

Il frammento di diagramma delle classi riportato nella Figura 3.18 mo-


della la situazione esposta. In pratica abbiamo aggiunto al diagramma
originale della Figura 3.12 una relazione tra Documento e Settore che de-
scrive l’argomento secondario del documento. La relazione è molti-a-
molti e opzionale, nel senso che un documento non è obbligato ad avere
un argomento secondario e analogamente un settore non è necessaria-
mente argomento secondario di un documento.
Come terza possibile variazione dell’organizzazione della biblioteca
consideriamo che sia anche possibile classificare i documenti in settori
“trasversali”, cioè secondo caratteristiche ortogonali all’argomento cor-
rispondente al settore. Ad esempio, potrebbero essere settori trasversa-
li i libri in inglese, i libri per ragazzi, i tascabili, ecc. I settori trasversali
CAPITOLO 3. ANALISI DEI REQUISITI Page 121

non hanno esistenza fisica perché qualunque documento sarà comun-


que collocato nel settore corrispondente al suo argomento specifico (sto-
ria, romanzi, musica, ecc.). Conviene quindi introdurre una nuova clas-
se SettoreTrasversale per rappresentare i settori trasversali, e rappre-
sentare la relazione tra Documento e SettoreTrasversale esplicitamente,
come mostrato nella Figura 3.19.

SettoreTrasversale Biblioteca
- argomento: String
*

*
0..1

1..* 1..*

Documento Scaffale Settore


- editore: String * 0..1 - sala: String 0..1
1..*
- codiceDewey: String - numero: int - argomento: String
- numeroCopia: int - ripiano: int

* + sottosettore

Figura 3.19: Frammento di diagramma delle classi: settori trasversali.

Da notare che l’introduzione dei settori trasversali non è in contrasto


con alcuna delle organizzazioni alternative descritte. Potremmo pertanto
aggiungere la classe SettoreTrasversale e le sue relazioni ai diagrammi
visti in precedenza.

Analisi dei casi speciali

Nell’analisi di problemi non banali è abbastanza naturale —almeno inizial-


mente— concentrarsi sul funzionamento “normale” del sistema. Prima
o poi occorre però considerare anche i casi speciali, o quelli che sono
solo meno ovvi degli altri. Nel caso della biblioteca —e relativamente
al solo modello statico— possiamo individuare un certo numero di casi
che, pur essendo abbastanza frequenti, non rientrano nel modello finora
proposto.

• Esistono pubblicazioni i cui autori sono istituzioni, fondazioni, ecc.


In questo caso risulta in genere utile riportare informazioni aggiun-
tive su di esse (indirizzo, numero di telefono, persona da contattare,
Page 122 CAPITOLO 3. ANALISI DEI REQUISITI

ecc.). È chiaro quindi come sia insufficiente modellare l’autore di un


documento come un semplice attributo di tipo stringa di caratteri.
• Esistono documenti formati da elementi che hanno esistenza fisica
indipendente (ad esempio i libri composti da più volumi o le opere
registrate su più CD). In casi come questi occorre non solo modella-
re i documenti in modo diverso, ma anche interrogarsi sulla validità
di alcuni ipotesi incorporate nel modello. Ad esempio, è possibile
prestare i diversi volumi di un libro separatamente?
• Esistono documenti —ad esempio le antologie— che comprendo-
no brani di autori diversi, con titoli diversi, ecc. Le informazioni
sui brani dovranno essere rappresentate individualmente (come già
accade per gli articoli delle riviste).

DocBase
- titolo:String Rivista Articolo
- editore:String
- codiceDewey:String - volume: int 1..* - pagInizio: int
- numeroCopia:int - numero: int - pagFine: int
- prestabile:boolean

DocBase
ElementoDoc
- genere:String
- argomento:String - autore:String
- lingua:String - titolo:String
Documento - parolaChiave: String[]
- riassunto:String
- autore: String

Video Audio Libro Antologia Brano


* 1..*
- sistema: String - tipo: String - ISBN: String
- durata: int - durata: int - numeroPagine: int

Posizione
- pagInizio: int
- pagFine: int

Figura 3.20: Diagramma delle classi che descrive i documenti della biblioteca,
comprese le antologie.
CAPITOLO 3. ANALISI DEI REQUISITI Page 123

Procediamo ora a modellare le antologie. Lasciamo al lettore come


esercizio la rappresentazione dei restanti casi speciali. Nella Figura 3.20 è
riportato un frammento di diagramma delle classi della biblioteca, modi-
ficato in modo da prevedere la possibile presenza di antologie.
Possiamo facilmente notare che l’organizzazione del diagramma è sta-
ta ristrutturata piuttosto ampiamente rispetto al diagramma mostrato nel-
la Figura 3.12. In effetti ci siamo serviti dell’esercizio per “sistemare” an-
che qualche altro piccolo problema del modello originale. La spiegazione
del modello della Figura 3.20 è la seguente.

• La classe Documento originale è stata suddivisa in due classi: Documen-


toBase e Documento, per poter distinguere i documenti con un auto-
re da quelli senza.

• Tutti i documenti, o meglio, tutte le istanze della classe DocumentoBase,


sono associate a una descrizione, istanza di DescrizioneDocumento.

• La classe astratta ElementoDoc rappresenta gli elementi che fanno


parte di documenti eterogenei, ottenuti assemblando contributi di
autori diversi. Tutte le istanze di questa classe dispongono di una
descrizione propria, esattamente come le istanze di DocumentoBase.

• La classe Rivista è una specializzazione di DocumentoBase (docu-


mento senza autore), costituita da articoli che sono specializzazioni
di ElementoDoc e quindi hanno un autore e una descrizione indivi-
duale.

• La classe Antologia è una specializzazione di Libro. Ogni istanza


della classe Antologia è composta da istanze della classe Brano, che
sono (come quelle di Articolo) specializzazioni di ElementoDoc e
quindi hanno un autore e una descrizione individuale.

• Vediamo che, benché simili, brani e articoli sono trattati in modo


diverso. Ciò è dovuto al fatto che, mentre un dato articolo compare
normalmente solo su un dato numero di una data rivista, è invece
relativamente frequente che uno stesso brano compaia su diverse
antologie. Quindi un articolo appartiene esclusivamente a una rivi-
sta (relazione di composizione). Al contrario, un brano appartiene a
un’antologia in modo potenzialmente condiviso. Le pagine di inizio
e fine sono trattate in modo diverso nelle riviste e nelle antologie:
mentre la posizione di un articolo nella rivista è fissa, e quindi i nu-
meri di pagina possono essere trattati come attributi dell’articolo, la
Page 124 CAPITOLO 3. ANALISI DEI REQUISITI

posizione di un brano cambierà nelle diverse antologie, quindi l’in-


formazione è correttamente modellata come attributo della classe
di associazione tra Brano e Antologia, Posizione. Sono possibili
eccezioni alla situazione: in particolare lo stesso articolo potrebbe
comparire su due o più riviste. In tal caso il modello proposto fun-
ziona ancora, purché si crei un’istanza di Articolo per ogni istanza
di Rivista su cui compare.

• Abbiamo aggiunto l’attributo Prestabile alla classe documento. In


questo modo ogni singola istanza di documento potrà essere indivi-
dualmente indicata come prestabile o meno.

Il modello proposto per i libri antologici è applicabile anche alla rappre-


sentazione di collezioni di brani audio o video di autori diversi.
Un’ultima osservazione riguarda l’attributo Lingua della classe Descri-
zioneDocumento. Esso indica il fatto che in un’antologia potrebbero com-
parire brani scritti in lingue diverse. Viceversa, non è del tutto corretto as-
sociare tale attributo alle istanze della classe Documento. Infatti per taluni
documenti il concetto di lingua non è applicabile (pensiamo ad esempio
ai libretti per bambini piccoli, dotati di sole figure). Inoltre, talvolta a un
documento sono associate più lingue diverse (ad esempio il materiale au-
dio è spesso accompagnato da note descrittive scritte in diverse lingue). I
libri “con testo a fronte” presentano problematiche simili. Peraltro il caso
più comune è che un libro sia scritto interamente in un’unica lingua.
Lasciamo al lettore il compito di produrre un modello che contempli
correttamente i casi testé accennati.

3.2 Ascensore
In un edificio di diversi piani è presente un ascensore, che si vuole con-
trollare mediante un sistema informatizzato.

3.2.1 Requisiti informali


La situazione che vogliamo descrivere rispecchia il normale funzionamen-
to di un ascensore, cosı̀ come siamo abituati a intenderlo.

• A ogni piano sono presenti due pulsanti per chiamare l’ascensore:


uno per scendere e uno per salire. Fanno naturalmente eccezione
CAPITOLO 3. ANALISI DEI REQUISITI Page 125

il piano terra e l’ultimo, che sono dotati di un unico pulsante, cor-


rispondente all’unica direzione possibile. Ciascun pulsante è col-
legato a una lampadina che si illumina per confermare che è stato
premuto e che quindi la chiamata corrispondente è stata recepita
dal sistema.

• Ai piani sono presenti anche degli apparati puramente informati-


vi: un display che mostra a quale piano si trova l’ascensore e due
frecce luminose che indicano la direzione dell’ascensore in arrivo
in risposta alla chiamata (se spente entrambe indicano che l’ascen-
sore non si sta muovendo per rispondere alla chiamata). Ad esem-
pio se noi siamo al quarto piano e chiamiamo l’ascensore per scen-
dere, ma questo si trova al quinto e sta portando delle persone al-
l’ottavo, le frecce resteranno spente, indicando cosı̀ che l’ascensore
non sta rispondendo alla nostra chiamata. Quando l’ascensore, do-
po aver raggiunto l’ottavo piano, inizierà a scendere per raggiungere
il nostro piano, la freccia della discesa si illuminerà, indicando che
l’ascensore sta rispondendo alla nostra chiamata.

• All’interno dell’ascensore si trovano tanti pulsanti quanti sono i pia-


ni. La pressione di un pulsante equivale alla richiesta di raggiungere
il piano corrispondente. Ogni pulsante è dotato di lampadina: si
illumina una volta premuto e indica una richiesta pendente per il
piano corrispondente. È possibile che vengano premuti più pulsan-
ti in successione: in tal caso si costituisce un insieme di richieste
pendenti.

• L’ascensore è dotato di porta automatica. La porta è a sua volta do-


tata di fotocellula, in modo che la porta non possa chiudersi mentre
qualcuno si trova sulla soglia.

• A ogni piano è collocato un sensore che segnala al controllore del-


l’ascensore il passaggio o la presenza della cabina dell’ascensore.

• Quando non impegnato, l’ascensore è fermo all’ultimo piano che ha


servito, con la porta aperta.

Nel seguito adotteremo un paio di ipotesi “favorevoli” che semplifica-


no le responsabilità del sistema: trascuriamo la possibilità di malfunzio-
namenti, quindi il sistema non deve gestire situazioni eccezionali; inoltre
non richiediamo politiche di gestione che ottimizzino i consumi o i tempi
Page 126 CAPITOLO 3. ANALISI DEI REQUISITI

d’attesa, bensı̀ ci accontentiamo che il sistema garantisca un funziona-


mento “ragionevole”.
Responsabilità del sistema di controllo è fare in modo che l’ascensore
si comporti in modo corrispondente alle attese degli utenti. Poiché sup-
poniamo che il lettore conosca il funzionamento di un normale ascenso-
re, evitiamo di riportarne qui la descrizione.

3.2.2 Casi d’uso e scenari


Prima di affrontare la descrizione dei requisiti osserviamo che quello che
stiamo trattando è un tipico problema di controllo. Cioè abbiamo un
sistema di cui vogliamo vincolare il comportamento, in reazione anche
agli stimoli provenienti dall’ambiente, in modo da soddisfare i requisi-
ti dell’utente. A questo scopo definiamo un controllore che osservi il si-
stema da controllare (l’ascensore), l’ambiente circostante (comprenden-
te anche gli utenti dell’ascensore, oltre agli elementi dati come i pulsanti,
le lampadine, il motore, ecc.) e impartisca i comandi giusti affinché il
comportamento risultante sia conforme alle aspettative.
Ovviamente la natura dei comandi che vengono impartiti dal control-
lore all’oggetto controllato dipende da quanto è “intelligente” quest’ulti-
mo. Consideriamo infatti cosa succede quando l’ascensore deve mettersi
in moto e, per motivi di sicurezza, occorre accertarsi che le porte siano
chiuse prima di avviare il motore. Sono possibili due casi estremi.
• Se l’ascensore è dotato di una capacità elaborativa abbastanza so-
fisticata potrà ricevere dal controllore un comando del tipo “chiu-
di le porte e parti verso l’alto” e gestire l’esecuzione del comando
autonomamente. Questo comporta che l’ascensore invii il coman-
do di chiusura alla porte e quindi riceva e interpreti correttamente i
segnali provenienti dalle porte (in particolare, l’ascensore non atti-
verà il motore finché non si saranno chiuse). Notiamo che in questo
caso non solo possono essere più sofisticati i comandi, ma cambia
anche la natura delle comunicazioni (ad esempio le porte non co-
municano con il controllore, ma solo con l’ascensore). In sostanza
c’è una gerarchia di controllori: il gestore controlla l’ascensore che
a sua volta controlla le porte, il motore, ecc.
• Viceversa, se l’ascensore non è abbastanza “intelligente”, sarà capa-
ce di eseguire solo comandi semplici, e in molti casi si limiterà a
propagare verso il controllore le informazioni che gli vengono dai
suoi componenti. Ad esempio, una porta che si stava chiudendo
CAPITOLO 3. ANALISI DEI REQUISITI Page 127

inizia nuovamente ad aprirsi: l’ascensore —essendo incapace di de-


cidere da solo cosa fare— comunica l’evento al gestore, che reagirà
impartendogli i comandi opportuni.

Nel primo caso è possibile descrivere i requisiti semplicemente in ter-


mini di comandi di alto livello impartiti all’ascensore. Poiché le capacità
dell’ascensore sono molto simili ai compiti che deve assolvere, le speci-
fiche sono spesso banali: ad esempio quando l’ascensore non è impegna-
to e viene chiamato a un piano superiore gli viene impartito il comando
“chiudi le porte e parti verso l’alto”.
In questo caso è comunque possibile —ma non necessario— specifi-
care anche il comportamento “interno” dell’ascensore, cioè ad esempio
come vengono gestiti i segnali provenienti dalle fotocellule.
Nel secondo caso, se cioè l’ascensore non è capace di gestire da solo
i suoi elementi, occorre necessariamente descrivere tutta la gerarchia di
composizione dell’ascensore e le sue relazioni con il controllore.
Comunque in generale occorre considerare che l’utente tende a enun-
ciare dei requisiti in cui vengono citati esplicitamente dei fatti elementari
del dominio del problema, indipendentemente da come questi possano
poi essere gestiti. Ad esempio, l’utente potrebbe indicare che “le porte de-
vono sempre essere chiuse quando l’ascensore è in movimento”. In con-
seguenza di ciò, se si vuole mostrare che il nostro sistema effettivamente
soddisfa tali requisiti, è necessario che la specifica arrivi a descrivere i fat-
ti elementari (ad esempio, “la chiusura delle porte”) anche se disponiamo
di un ascensore intelligente in grado di gestire da solo le porte, e quindi il
controllore da realizzare non dovrà preoccuparsene.
Pertanto, nel seguito daremo una descrizione abbastanza dettagliata
dei requisiti, modellando il funzionamento dell’ascensore (dominio del
problema) al livello di dettaglio imposto dai requisiti utente.
Il diagramma dei casi d’uso del sistema per la gestione dell’ascensore
è riportato nella Figura 3.21.
Il sistema che dobbiamo realizzare è costituito unicamente dal siste-
ma di controllo, mentre l’ascensore, il sensore di piano, i pulsanti, ecc. so-
no elementi dati dell’ambiente (cioè del dominio del problema). Pertanto
occorre modellare l’interazione di questi elementi dati con il sistema di
controllo.
Per non complicare eccessivamente il diagramma riportato nella Fi-
gura 3.21 sono state omesse le interazioni tra il sistema di controllo e i
dispositivi di visualizzazione (display, lampadine, ecc.). Lasciamo al let-
Page 128 CAPITOLO 3. ANALISI DEI REQUISITI

tore la descrizione di queste interazioni, peraltro abbastanza facilmente


individuabili.

Utente Fotocellula

Chiamata
<<include>>

MonitoraggioE
Pulsante Pianificazione
Discesa <<include>>

RichiestaPiano <<include>>

<<include>>
Controllo
Pulsante Pulsante
Salita
Porta

NotificaArrivo
AlPiano

Pulsante Sensore
Ascensore Piano Motore

Figura 3.21: Diagramma dei casi d’uso del sistema di gestione dell’ascensore.

Possiamo interpretare il diagramma della Figura 3.21 come segue.

• Il sistema è dotato di tre servizi di raccolta degli input dall’ambien-


te esterno. Questi input sono: le chiamate (effettuate attraverso i
pulsanti ai piani), le richieste di raggiungere un piano (effettuate
attraverso i pulsanti nella cabina dell’ascensore) e le notifiche del
raggiungimento di un piano (provenienti dai sensori ai piani).

• Le funzionalità che gestiscono gli input invocano il servizio di mo-


nitoraggio e pianificazione. Quest’ultimo si preoccupa di dedurre la
situazione dell’ascensore e determinare l’elenco dei compiti futuri.

• I compiti dell’ascensore vengono comunicati dal servizio di pianifi-


cazione al controllore vero e proprio, che emette i comandi destinati
ai componenti dell’ascensore.

Come sempre, i casi d’uso possono essere dettagliati mediante dia-


grammi di sequenza che descrivono scenari specifici. Ad esempio, il dia-
gramma nella Figura 3.22 specifica questa sequenza di eventi: l’ascensore
CAPITOLO 3. ANALISI DEI REQUISITI Page 129

invia alla porta il comando di chiudersi, questa —dopo aver verificato che
la fotocellula sia chiusa— inizia a chiudersi, ma a un certo punto la foto-
cellula segnala un passaggio, e quindi la porta non completa la chiusura
fino a dopo che il passaggio è completato.

Gestore
Ascensore
Motore Porta Fotocellula

chiudi()

verificaChiusura()

true
La porta inizia
a chiudersi
passaggio()
La porta
si riapre
finePassaggio()
La porta si
richiude

portaChiusa()

azionaSalita()

Figura 3.22: Diagramma di sequenza: l’ascensore attende la chiusura delle porte


per avviare il motore.

L’osservazione della Figura 3.22 ci offre lo spunto per notare che nei
diagrammi di sequenza si rappresentano bene solo gli scenari che sono
scanditi essenzialmente da eventi. Nel nostro caso, non abbiamo modo
di rappresentare i cambi di stato della porta, poiché non abbiamo defini-
to alcun messaggio al quale si possa associare tale significato. Abbiamo
pertanto supplito a tale mancanza introducendo dei commenti che indi-
cano in quale punto della “life-line” della porta avvengono i cambiamenti
di stato. Da notare che —come mostrato più avanti— l’utilizzo dei dia-
grammi di stato consente di specificare esattamente il comportamento
delle porte senza dover ricorrere a commenti o a notazioni non previste
nel linguaggio.
In effetti nel caso dell’ascensore il limite descritto sopra è particolar-
mente rilevante: il comportamento dell’ascensore dipende dallo stato cor-
rente, che è determinato da sequenze di eventi occorsi prima del periodo
in cui si vuole descrivere il comportamento.
In realtà i diagrammi di sequenza hanno dei limiti espressivi piuttosto
evidenti. Se da una parte vanno benissimo per esemplificare situazioni
descrivendo degli scenari, dall’altra sono scarsamente adatti a descrive-
re regole di validità generale. Per esempio, nel nostro caso, che riguar-
Page 130 CAPITOLO 3. ANALISI DEI REQUISITI

da solo il funzionamento in assenza di malfunzionamenti, è ragionevole


aspettarsi che tra i vari requisiti compaiano le seguenti regole:

• le porte sono aperte solo se l’ascensore è fermo a un piano;

• quando l’ascensore si ferma a un piano deve iniziare ad aprire le


porte, che dovranno essere completamente aperte entro un certo
ritardo massimo;

• se un utente ha chiamato l’ascensore dal piano P, prima o poi l’a-


scensore si troverà fermo al piano P con le porte aperte, nell’ipote-
si che nessun utente lo blocchi indefinitamente restando fermo in
mezzo al raggio della fotocellula.

Tutte queste regole perfettamente ragionevoli non sono esprimibili me-


diante diagrammi di sequenza. Consideriamo infatti la prima: possia-
mo facilmente costruire un diagramma di sequenza in cui si vede che il
sensore segnala l’arrivo a un piano, il controllore manda al motore l’or-
dine di fermarsi e quindi alle porte l’ordine di aprirsi, ma questo non
implicherebbe l’assenza di altri casi in cui l’ascensore apre le porte.
Un altro problema dei diagrammi di sequenza è che la combinazio-
ne di diagrammi diversi può risultare problematica. Infatti combinando
scenari diversi si possono ottenere i cosiddetti “scenari implicati”, cioè
combinazioni che non sono state previste da chi ha scritto il modello, ma
sono possibili in base alla semantica del linguaggio. In generale non tutti
gli scenari implicati corrispondono a situazioni desiderabili.
In conclusione, suggeriamo di non forzare l’uso dei diagrammi di se-
quenza oltre il limite imposto dalla loro espressività. Viceversa, è oppor-
tuno valutare l’ipotesi di utilizzare gli altri formalismi offerti da UML. Per
esempio, il requisito relativo all’apertura delle porte è descritto facilmente
da un vincolo OCL riportato più avanti in questo paragrafo.

3.2.3 Modello statico


Gli elementi del dominio del problema e il gestore dell’ascensore sono
descritti dal diagramma delle classi riportato nella Figura 3.23.
L’ascensore è rappresentato da un’opportuna classe e contiene un di-
splay, una porta, un motore e un certo numero di pulsanti, ciascuno con la
sua lampadina associata. In particolare, l’associazione 1-a-1 tra pulsan-
ti dell’ascensore e piani indica che esiste esattamente un pulsante (e la
DisplayPiano
Display
- numPianoAscensore: int
LampadinaDirezione GestoreAscensore
+ arrivoAscensore (): void
1..*
- fermateProgrammate: int[] + setNumPianoAsc (numPianoAsc: int): void
- chiamateSalitaPendenti: int[]
- chiamateDiscesaPendenti: int []
1..2
- pianoCorrente: int
<<actor>>
+ portaAperta(): void Fotocellula
+ portaChiusa(): void
LampadinaPiano
+ richiesta (numPiano:int): void - statoChiusura: boolean
+ chiamataSalita (numPiano: int): void
+ chiamataDiscesa (numPiano: int): void + chiusa (): boolean
DisplayAscensore
Lampadina + alPiano (numPiano: int): void
1..* + nessunaPrenotazione (): boolean
+ aggiungiFermata (numPiano: int): void
1..2 + aggiungiChiamataDiscesa (numPiano: int): void
+ accendi ():void
+ aggiungiChiamataSalita (numPiano: int): void
+ spegni (): void
+ eliminaDestinazione (numPiano: int): void <<actor>>
+ prenotato (numPiano: int): boolean
+ prenotataDiscesa (): boolean
Porta
+ prenotataDiscesa (numPiano: int): boolean - tempoMovimento: Time
+ prenotataSalita (): boolean - tempoApertura: Time
LampadinaAscensore + prenotataSalita (numPiano: int): boolean - tempoInitMov: Time
+ sotto Piano
0..1 + apri (): void
- numero: int 1..* + chiudi (): void
CAPITOLO 3. ANALISI DEI REQUISITI

1..* + passaggio (): void


+ sopra + finePassaggio (): void
0..1 Sensore
- numero: int 1..*
Ascensore
+ arrivoAscensore (): void
+ partenzaAscensore (): void
0..1
+ portaChiusa ():boolean
<<actor>> + portaAperta ():boolean
1..*
PulsanteSalita
<<actor>> <<actor>>
0..1 Pulsante PulsanteAscensore
0..1 0..1
- numPiano: int <<actor>>
<<actor>>
Motore
PulsanteDiscesa + premi(): void

+ azionaSalita (): void


Edificio + azionaDiscesa (): void
+ ferma (): void

Figura 3.23: Diagramma delle classi del sistema di gestione dell’ascensore.


Page 131
Page 132 CAPITOLO 3. ANALISI DEI REQUISITI

relativa lampadina) per ogni piano. Gli elementi dell’ascensore sono op-
portunamente collegati al gestore da cui le lampadine ricevono i comandi
di accendersi e di spegnersi.
L’ascensore è collegato al gestore e ai sensori, anche questi ultimi in
ragione di uno per ogni piano. I piani sono pure modellati mediante una
classe opportuna. Ogni piano appartiene all’edificio e ha un piano sotto
e uno sopra (con la solita eccezione del primo e dell’ultimo: le eccezioni
sono trattate nel seguito mediante OCL). A ciascun piano corrispondono
un display, uno o due pulsanti, ciascuno con relativa lampadina, una o
due lampadine che indicano la direzione dell’ascensore che sta rispon-
dendo alla chiamata, e il sensore che consente di rilevare la presenza o il
passaggio dell’ascensore.
Il piano non è connesso direttamente al gestore dell’ascensore, ma
solo attraverso i pulsanti, le lampadine e il sensore. In un certo senso
il piano serve a raggruppare questi elementi, che sono le vere e proprie
interfacce del sistema, come mostrato nella Figura 3.21, dando loro una
connotazione spaziale. Il gestore dell’ascensore è collegato ai sensori, al-
l’ascensore, ai pulsanti e alle lampadine. È opportuno notare che queste
connessioni servono a trasmettere informazioni in direzioni ben precise:
il gestore riceve informazioni dai sensori e dai pulsanti, e invia comandi
all’ascensore e alle lampadine. In realtà possiamo modellare le relazio-
ni tra gestore da una parte e pulsanti e lampadine dall’altra in due modi
diversi: il gestore può essere connesso direttamente (come mostrato nel-
la Figura 3.23), oppure mediante l’ascensore (ad esempio, il gestore co-
munica all’ascensore che una data lampadina deve accendersi e l’ascen-
sore rinvia il comando alla lampadina in questione). In fase di specifica
dei requisiti non è poi cosı̀ rilevante quale dei due modi venga utilizzato.
L’importante è che le relazioni rilevanti siano correttamente rappresenta-
te. In fase di design stabiliremo quale dei due modelli è più appropriato.
Probabilmente usare l’ascensore come “mediatore” tra gestore, pulsanti
e lampadine comporta dei vantaggi in termini di economia di comunica-
zioni tra elementi remoti: anziché connettere il gestore a ciascun pulsante
e lampadina, lo si connette solo all’ascensore, il quale poi sfrutterà delle
connessioni locali. In questo genere di valutazioni entrano peraltro diver-
se considerazioni: ad esempio, se l’ascensore è unico, è probabile che il
gestore sia montato direttamente nell’ascensore, rendendo praticabile la
connessione diretta a pulsanti e lampadine. Viceversa, se il gestore ha in
carico il controllo di diversi ascensori, allora la connessione del gestore
agli ascensori appare una scelta molto ragionevole.
Notiamo ora che nel diagramma della Figura 3.23 sono riportate tre
CAPITOLO 3. ANALISI DEI REQUISITI Page 133

classi astratte: Display, Pulsante e Lampadina. Queste classi meritano


un commento relativamente alla loro astrattezza: infatti sono dotate di
tutti gli attributi e metodi che potrebbero qualificarle come classi concre-
te. L’unico elemento mancante —che è anche l’unica proprietà che con-
traddistingue le classi derivate— è la relazione con l’oggetto servito. Ad
esempio, la classe Display ha tutte le proprietà utili a definire un display,
ma le manca l’appartenenza all’elemento fisico servito dal display (che
può essere un piano o l’ascensore). Nel diagramma della Figura 3.23 sono
dunque previste classi concrete che specializzano le classi astratte. Nel
caso del Display, le classi DisplayPiano e DisplayAscensore sono con-
nesse rispettivamente alla clase Piano e alla classe Ascensore. Viceversa,
il fatto che le classi astratte Pulsante e Lampadina siano connesse al gesto-
re implica che tutte le loro sottoclassi siano connesse nello stesso modo:
non dovendo specificare esplicitamente la connessione al gestore delle
sottoclassi si ottiene un modello più compatto.

È interessante notare come si possa produrre un modello equivalente


del Display senza utilizzare classi astratte. Un esempio è mostrato nel-
la Figura 3.24, dove appare un’unica classe Display concreta: il fatto che
ogni istanza di Display possa essere connessa esclusivamente a una di
Piano o di Ascensore deriva dalla semantica della relazione di composi-
zione, che prevede che ogni istanza del componente possa esistere solo
come parte dell’oggetto composto e non possa essere condivisa.

Display

- numPianoAscensore: int

+ setNumPianoAsc()

1 1

0..1 0..1

Piano Ascensore

- numero: int

Figura 3.24: Modello alternativo del display.


Page 134 CAPITOLO 3. ANALISI DEI REQUISITI

3.2.4 Qualcosa di più formale


Come accennato in precedenza, il modello fin qui descritto comprende
anche situazioni che non sono possibili nel dominio del problema o non
sono desiderabili (cioè non corrispondono ai requisiti). Vediamo dunque
come specificare alcune restrizioni che vincolano il modello a rappresen-
tare solo situazioni “legali”.
Da notare che in molti casi le associazioni tra classi nel diagramma
della Figura 3.23 sono anonime. Per navigare queste associazioni si usa
direttamente il nome della classe che si intende raggiungere, come previ-
sto nella definizione di OCL.
Nella Tabella 3.10 sono riportati i vincoli cui sono soggette le relazioni
che coinvolgono istanze della classe PulsanteAscensore.

Il numero scritto su ogni pulsante è uguale al numero del piano cor-


rispondente (proprietà banale, ma nessuno vuole rischiare di finire al
diciottesimo piano quando vuole andare al quinto ...).

context PulsanteAscensore
inv: self.numPiano = self.piano.numero

Tabella 3.10: Vincoli relativi alle relazioni che coinvolgono la classe


PulsanteAscensore.

Nella Tabella 3.11 sono riportati i vincoli cui sono soggette le relazioni
che coinvolgono istanze della classe Piano.
L’invariante 1 specifica che, per ogni piano, il piano sovrastante (se esi-
ste) è identificato dal numero intero immediatamente successivo, il sot-
tostante (se esiste) dall’intero immediatamente precedente. Questa frase
vincola i numeri di piano a formare una sequenza ordinata e continua,
ma non indica da quale numero debba iniziare la sequenza. L’invariante
2 assicura che le associazioni tra coppie di piani successivi siano corrette.
L’invariante 3 specifica che il piano che non ha alcun piano sottostante è
il numero 0, vincolando cosı̀ la sequenza a iniziare da 0.
In realtà, i tre invarianti non sono sufficienti a vincolare il modello in
modo da escludere situazioni indesiderabili. Per esempio, una situazione
in cui esistono due piani terra non violerebbe alcuna parte del modello.
Analogamente, potrebbero esistere piani “sospesi nel vuoto” senza alcun
piano sopra o sotto. Benché questo sia escluso dalla conoscenza del do-
minio applicativo che possiamo dare per scontata, è comunque interes-
CAPITOLO 3. ANALISI DEI REQUISITI Page 135

I piani formano una sequenza, numerata mediante interi consecuti-


vi. Il piano terra corrisponde al numero zero. Ogni piano interme-
dio ha un piano sopra, identificato dal numero intero immediatamente
successivo, e uno sotto, identificato dall’intero precedente.

Invariante 1:
context Piano
inv: self.sopra->size()=1 implies self.sopra.numero = self.numero+1
and
self.sotto->size()=1 implies self.sotto.numero = self.numero-1

Invariante 2:
context Piano
inv: self.sopra.sotto = self and self.sotto.sopra = self

Invariante 3:
context Piano
inv: self.sotto->size() = 0 implies self.numero = 0

Invariante 4:
context Edificio
inv: (self.piano->select(sotto->size() = 0)->size() = 1

Il piano terra ha solo il pulsante per la chiamata per salire; analogamen-


te l’ultimo piano ha solo il pulsante per prenotare la discesa. Gli altri
piani hanno entrambi i pulsanti.

context Piano
inv: sotto->size() = pulsanteDiscesa->size() and
sopra->size() = pulsanteSalita->size()

Tabella 3.11: Vincoli relativi alle relazioni che coinvolgono la classe Piano.
Page 136 CAPITOLO 3. ANALISI DEI REQUISITI

sante vedere come si possano eliminare i casi assurdi anche formalmente,


attraverso l’uso di OCL.
L’invariante 4 specifica che in ogni edificio il numero di piani che non
hanno un piano sottostante è esattamente uguale a uno, cioè esiste un
unico piano terra. Gli invarianti da soli non basterebbero a escludere ad
esempio che esistano due o più piani immediatamente sopra a un altro.
Quest’eventualità è esclusa dalle molteplicità delle associazioni tra pia-
ni nel diagramma delle classi (Figura 3.23), che assicura che ogni piano
possa avere al più un piano sovrastante (o sottostante).
Infine, la proprietà del sistema citata in precedenza, cioè che le porte
dell’ascensore possono essere aperte solo a motore fermo e con l’ascen-
sore al piano, si può specificare come segue:
context Porta
inv: self.oclInState(Aperta) implies
self.ascensore.motore.oclInState(Fermo) and
self.ascensore.sensore->select(oclInState(Sollecitato))->
size() = 1

Benché possiamo individuare parecchi altri vincoli che è necessario


specificare per ottenere una descrizione rigorosa del sistema, non li ripor-
tiamo qui per non appesantire troppo la trattazione. Invitiamo il lettore a
cimentarsi con l’individuazione e la formalizzazione di ulteriori vincoli.

3.2.5 Metodi e specifiche funzionali


Riportiamo ora la specifica del significato di alcuni metodi. Anche in que-
sto caso ci limitiamo a un piccolo insieme: il lettore è invitato a cimentarsi
nella specifica del significato dei rimanenti metodi.
Cominciamo da aggiungiFermata(numPiano: int) della classe Gesto-
reAscensore. Il metodo aggiunge il numero di piano passato come ar-
gomento della chiamata all’insieme di fermate programmate (attributo
fermateProgrammate). Il metodo si può specificare come segue (ricordan-
do che fermateProgrammate è un insieme di interi, come pure chiamateSa-
litaPendenti e chiamateDiscesaPendenti):
context GestoreAscensore::aggiungiFermata(numPiano: int): void
post: self.fermateProgrammate =
self.fermateProgrammate@pre->includes(numPiano)

Il metodo prenotato(numPiano: int) della classe GestoreAscensore


indica se il numero di piano passato come argomento appartiene al pro-
gramma di viaggio corrente (fermateProgrammate).
CAPITOLO 3. ANALISI DEI REQUISITI Page 137

context GestoreAscensore::prenotato(numPiano: int): boolean


post: result = (self.fermateProgrammate->includes(numPiano))

Il metodo prenotataSalita() della classe GestoreAscensore indica


se ci sono salite prenotate tra le chiamate pendenti (cioè non inclusa nel
programma di viaggio corrente dell’ascensore).

context GestoreAscensore::prenotataSalita(): boolean


post: result = (self.chiamateSalitaPendenti->size() > 0)

Il metodo prenotataSalita(numPiano: int) della classe GestoreA-


scensore indica se esistono prenotazioni a salire incluse nel programma
di viaggio corrente dell’ascensore, con destinazione a piani superiori a
quello indicato dal parametro.

context GestoreAscensore::prenotataSalita(numPiano: int): boolean


post: result=self.fermateProgrammate->exists(NP: int | NP>numPiano)

3.2.6 Dinamica del sistema


Il comportamento del motore dell’ascensore è descritto dal diagramma
degli stati riportato nella Figura 3.25. Tale comportamento è sufficiente-
mente semplice da non richiedere alcun commento.

azionaSalita Avanti

ferma
Fermo

ferma

Indietro
azionaDiscesa

Figura 3.25: Diagramma degli stati del motore dell’ascensore.

Il comportamento dei pulsanti ai piani per la chiamata a salire è de-


scritto dal diagramma degli stati riportato nella Figura 3.26. Il comporta-
mento dei pulsanti che si trovano ai piani e che servono per chiamare l’a-
scensore per scendere hanno comportamento del tutto analogo, pertanto
il corrispondente diagramma di stato è omesso.
Page 138 CAPITOLO 3. ANALISI DEI REQUISITI

Inattivo

premi/gestoreAscensore.chiamataSalita(piano.numero)

Figura 3.26: Diagramma degli stati del pulsante per la salita (classe
PulsanteSalita).

Inattivo

premi/gestoreAscensore.richiesta(numPiano)

Figura 3.27: Diagramma degli stati dei pulsanti presenti nell’ascensore (classe
PulsanteAscensore).

Il comportamento dei pulsanti situati nella cabina dell’ascensore è


descritto dal diagramma degli stati riportato nella Figura 3.27.
Dalle Figure 3.26 e 3.27 è chiaro che questi diagrammi non servono
tanto a specificare un comportamento complesso (ciascuna classe ha un
unico stato), quanto piuttosto a specificare come gli stimoli provenien-
ti dall’ambiente (la pressione del pulsante da parte dell’utente) venga-
no tradotti in messaggi (chiamataSalita e richiesta) comprensibili da-
gli oggetti del sistema. Il comportamento dei sensori ai piani è descritto
dal diagramma degli stati riportato nella Figura 3.28. Notate che gli eventi
arrivoAscensore e partenzaAscensore sono generati dall’ascensore stes-
so in corrispondenza del passaggio al piano. Lo stato Sollecitato non sa-
rebbe strettamente necessario, ma è stato introdotto per poter esprimere
compiutamente il vincolo OCL relativo all’apertura delle porte.

Inattivo
arrivoAscensore/gestoreAscensore.alPiano(numero)

Sollecitato
partenzaAscensore

Figura 3.28: Diagramma dei sensori (classe Sensore).


CAPITOLO 3. ANALISI DEI REQUISITI Page 139

La dinamica della porta dell’ascensore è descritta dal diagramma degli


stati della Figura 3.29. Esso tiene conto di una serie di fattori.

• La porta ci mette un certo tempo ad aprirsi e a chiudersi. Pertanto


accanto agli ovvi stati Aperta e Chiusa è stato necessario introdurre
degli stati intermedi, come InApertura.

• Mentre la porta si sta chiudendo (stato InChiusuraIniziale) è pos-


sibile che qualcuno entri o esca. In questo caso la fotocellula segnala
il passaggio e la porta si riapre, per agevolare il transito delle perso-
ne. Di nuovo occorre tener conto del fatto che la riapertura non è
istantanea, quindi la porta andrà prima in uno stato InRiapertura,
dove resterà per il tempo necessario, poi resterà aperta (stato Atte-
saChiusura) finché la fotocellula non darà il via libera, dopo di che
ricomincerà a chiudersi (stato InChiusuraIniziale). L’attributo
chiusa della fotocellula indica lo stato del circuito, che è chiuso quan-
do non sta passando nessuno, e aperto quando la luce della fotocel-
lula è interrotta dal passaggio delle persone attraverso la porta.

• Il tempo di apertura della porta è variabile, quando l’apertura è cau-


sata dal passaggio di persone. Infatti si suppone che la porta ci met-
ta ad aprirsi tanto tempo quanto è stata in chiusura (ad esempio,
se quando la fotocellula segnala un passaggio la porta aveva appe-
na cominciato a chiudersi, la porta si è chiusa di poco, e quindi ci
metterà poco a riaprirsi).

• Se qualcuno entra o esce mentre la porta si sta aprendo non c’è


alcun effetto sulla porta, che continuerà regolarmente ad aprirsi.

Il comportamento della fotocellula è assolutamente ovvio e quindi non


lo descriviamo esplicitamente: il circuito risulta chiuso quando non c’è
alcun passaggio; in corrispondenza dei passaggi il circuito è aperto. L’a-
pertura (passaggio) e chiusura (fine passaggio) sono segnalate esplicita-
mente mediante messaggi al gestore dell’ascensore. Prima di descrivere
nel dettaglio il comportamento dell’ascensore, consideriamo il significato
dei seguenti attributi.

• fermateProgrammate rappresenta l’insieme dei piani a cui l’ascen-


sore si fermerà nel suo viaggio corrente.

• chiamateSalitaPendenti rappresenta l’insieme di chiamate a salire


che non possono essere soddisfatte nel viaggio corrente e che quin-
Page 140 CAPITOLO 3. ANALISI DEI REQUISITI

Aperta inChiusuraIniziale
chiudi [fotocellula.chiusa()]
entry/gestoreAscensore.portaAperta() entry/tInitMov=now

chiudi [not fotocellula.chiusa()]

attesaChiusura finePassaggio
after(tempoMovimento)

inApertura after(tempoApertura)

inRiapertura passaggio
entry/tempoApertura=now - tInitMov

apri

chiusa
entry/gestoreAscensore.portaChiusa()
after(tempoMovimento) [fotocellula.chiusa()]

Figura 3.29: Diagramma degli stati della porta dell’ascensore.

di lo saranno in seguito. Ad esempio, se l’ascensore sta scenden-


do e arriva una richiesta di salita, quest’ultima dovrà attendere che
l’ascensore abbia concluso la discesa per poter essere soddisfatta.
• chiamateDiscesaPendenti rappresenta l’insieme di chiamate a scen-
dere che non possono essere soddisfatte nel viaggio corrente e che
quindi verranno soddisfatte in seguito. Ad esempio se l’ascenso-
re è al quarto piano e sta salendo, e arriva una chiamata a salire
dal secondo piano, quest’ultima dovrà attendere che l’ascensore ab-
bia concluso la salita e l’eventuale successivo viaggio in discesa per
poter essere soddisfatta.
Il funzionamento del gestore dell’ascensore è descritto nel diagramma
degli stati riportato nella Figura 3.30. Per semplificare la figura, abbiamo
indicato solo il comportamento dell’ascensore rispetto al movimento in
salita. La parte relativa alla discesa è naturalmente simmetrica.
• Lo stato Inattivo corrisponde all’assenza di richieste e all’ascenso-
re fermo con le porte aperte.
• Quando l’ascensore è inattivo e arriva una chiamata da un piano su-
periore o una richiesta (generata da un pulsante interno all’ascenso-
re) relativa a un piano superiore, il gestore aggiunge il piano in que-
stione all’insieme delle fermate programmate, impartisce l’ordine di
chiusura alla porta e si pone nello stato InPartenzaSalita.
CAPITOLO 3. ANALISI DEI REQUISITI Page 141

• Lo stato InPartenzaSalita e gli altri di cui parleremo tra poco sono


sottostati dello stato AttivoSalita. Questo stato serve essenzial-
mente a far condividere ai suoi sottostati la gestione delle richieste.
Le richieste relative al piano corrente quando l’ascensore è fermo
vengono ignorate. Per il resto le richieste sono gestite secondo la
logica descritta più avanti.
• Nello stato InPartenzaSalita attendiamo la chiusura della porta.
Quando questo evento ha luogo, si avvia il motore e la salita inizia
(stato InSalita).
• Nello stato InSalita si verifica prima o poi il raggiungimento di un
piano, evento segnalato da alPiano(NumPiano). Quando questo even-
to ha luogo, si deve aggiornare il piano corrente. Inoltre, se il piano
appartiene all’insieme di quelli per cui è pendente una prenotazio-
ne, occorre fermarsi.
• La fermata a un piano prenotato avviene attraverso lo stato InFermata,
in cui si attende l’apertura delle porte.
La logica di gestione delle chiamate e delle richieste —ipotizzando che
l’ascensore stia salendo e si trovi tra il piano X (piano corrente) e il piano
X+1— è la seguente.
• Richiesta interna (cioè effettuata mediante un pulsante interno alla
cabina) per un piano ≥ X+1. Il piano X+1 viene inserito nel program-
ma di viaggio, cioè in fermateProgrammate.
• Richiesta interna per un piano ≤ X. Viene inserita tra le richieste a
scendere pendenti.
• Chiamata a salire da piano ≥ X+1. Viene inserita nel programma di
viaggio.
• Chiamata a salire da piano < X. Viene inserita tra le richieste a salire
pendenti.
• Chiamata a salire da piano = X. Dipende dal sottostato. Se l’ascenso-
re è fermo con le porte aperte possiamo ignorare la richiesta, poiché
l’utente può semplicemente entrare e selezionare il piano desidera-
to. Se invece l’ascensore è in salita possiamo solo inserirla tra le ri-
chieste a salire pendenti. Se l’ascensore è fermo in attesa che le por-
te si chiudano si impartisce il comando di apertura porte, in modo
da lasciare entrare l’utente che desidera salire.
Page 142 CAPITOLO 3. ANALISI DEI REQUISITI

• Chiamata a scendere da un piano qualunque. Viene inserita tra le


richieste a scendere pendenti.

In corrispondenza della transizione da InPartenzaSalita allo stesso


stato si invia alla porta il segnale Passaggio. In questo modo si provoca
l’apertura della porta, che si chiuderà poi appena possibile.
Un’attenzione particolare deve essere messa nella gestione dell’inver-
sione del movimento. Quando l’ascensore ha esaurito le fermate preno-
tate a salire deve iniziare a soddisfare le chiamate a scendere che si sono
accumulate. Per far questo deve prima posizionarsi al piano più alto inte-
ressato da una chiamata a scendere. Infatti può accadere che l’ascensore
abbia esaurito le fermate a salire al piano n, ma che ci sia una chiamata
pendente a scendere dal piano n + 1: in tal caso l’ascensore deve prima
recarsi al piano n + 1 e poi cominciare la discesa. Per ottenere questo
comportamento, quando nello stato InFermata non ci sono più fermate
programmate a salire (prenotataSalita(pianoCorrente) è falsa), ma il
piano corrente è minore del massimo tra quelli da cui sono state effettuate
chiamate a scendere (pianoCorrente < max(chiamateDiscesaPendenti))
la transizione avviene verso lo stato FermoSalendo facendo quindi pro-
seguire l’ascensore verso l’alto. La transizione da InSalita a InFermata
tiene conto di condizioni analoghe. Infine, alla transizione da InFermata
ad AttivoDiscesa le chiamate pendenti diventano il nuovo programma
di viaggio.
Nel modello presentato fin qui manca la gestione lampadine: poiché
la loro accensione è compito del gestore dell’ascensore, tali azioni dovran-
no comparire nel diagramma degli stati della Figura 3.30. In particolare,
accensioni e spegnimenti sono sempre associati a eventi (chiamate, rag-
giungimento di piani, ecc.), dunque le emissioni degli opportuni messag-
gi saranno associate a transizioni della macchina a stati. Lasciamo al let-
tore il compito di arricchire il diagramma della Figura 3.30 con le azioni
relative alle lampadine.
Notiamo anche che l’apertura delle porte viene comandata contem-
poraneamente alla fermata del motore. A rigore ciò non è scorretto, però
è poco realistico: probabilmente sarebbe opportuno introdurre un ritar-
do tra la fermata e l’apertura. Addirittura potrebbe essere il caso che l’a-
scensore abbia un dispositivo di “fermata morbida”, che impone di atten-
dere la conclusione della decelerazione. Lasciamo al lettore il compito di
modellare un comportamento più realistico.
chiamataDiscesa(n) [n>pianoCorrente] / aggiungiFermata(n); porta.chiudi()
Inattivo chiamataSalita(n) [n>pianoCorrente] / aggiungiFermata(n); porta.chiudi()
richiesta(n) [n>pianoCorrente] / aggiungiFermata(n); porta.chiudi()

AttivoSalita
portaAperta
[nessunaPrenotazione()] AttivoSalitaFermo

FermaSalendo InPartenzaSalita chiamataSalita(n)


after(DurataFermata)/porta.chiudi()
[n=pianoCorrente] /
porta.passaggio()

portaAperta()
AttivoDiscesa [prenotataSalita(pianoCorrente) or
piano_corrente <
max(ChiamateDiscesaPendenti)]
CAPITOLO 3. ANALISI DEI REQUISITI

portaChiusa() /
alPiano(n) motore.azionaSalita()
[prenotato(n) or pianoCorrente =
max(ChiamateDiscesaPendenti)]/
InFermata eliminaDestinazione(n); InSalita alPiano(n)
pianoCorrente=n; [not prenotato(n)]/
portaAperta() porta.apri(); motore.ferma() pianoCorrente=n
[prenotataDiscesa() and
not prenotataSalita(pianoCorrente)]/
fermateProgrammate =
chiamateDiscesaPendenti; chiamataSalita(n) [n=pianoCorrente] chiamataSalita(n) [n=pianoCorrente] /

Figura 3.30: Diagramma degli stati del gestore dell’ascensore.


chiamateDiscesaPendenti = [] aggiungiChiamataSalita()

richiesta(n) [n>pianoCorrente] / aggiungiFermata(n)


richiesta(n) [n<pianoCorrente] / aggiungiChiamataDiscesa(n)
chiamataDiscesa(n) / aggiungiChiamataDiscesa(n)
chiamataSalita(n) n>pianoCorrente] / aggiungiFermata(n)
chiamataSalita(n) n<pianoCorrente] / aggiungiChiamataSalita(n)
Page 143
Page 144 CAPITOLO 3. ANALISI DEI REQUISITI

3.3 Esercizi
Proponiamo alcuni esercizi che dovrebbero consentire al lettore di ap-
plicare i principı̂ che abbiamo presentato. Per evitare lunghe e tediose
spiegazioni sull’argomento di ciascun esercizio, essi si riferiscono ai te-
mi già trattati nel capitolo, richiedendo approfondimenti, variazioni, o il
trattamento di casi specifici o particolari.

Gestione delle tessere della biblioteca

Con riferimento alla biblioteca descritta nel Paragrafo 3.1, ipotizziamo


che agli utenti registrati venga rilasciata una tessera dove sono riportati
il nome e cognome, insieme con l’identificatore attribuitogli dal sistema.
Quando un utente viene registrato il sistema produce un numero identi-
ficatore che viene usato per rilasciare all’utente la sua tesserina di rico-
noscimento. Se l’utente risulta già registrato, il numero di identificazione
può essere ad esempio usato per rilasciare duplicati della tessera. Que-
st’ultima operazione può essere prevista ma al di fuori delle responsabi-
lità del sistema: in tal caso è responsabilità del bibliotecario compilare il
duplicato. Se fosse responsabilità del sistema, un apposito caso d’uso an-
drebbe previsto. È piuttosto evidente che esistono diverse possibilità che
devono anche tenere conto delle modalità di fruizione del sistema.

• Il sistema non è dotato di capacità di stampa delle tessere: in que-


sto caso il rilascio, sia dell’originale che dei duplicati, è compito del
bibliotecario.

• Il sistema è dotato di capacità di stampa delle tessere e il rilascio, sia


dell’originale che dei duplicati, è effettuato dal sistema. È tuttavia
opportuno che solo il bibliotecario abbia accesso all’operazione, in
modo che vengano garantiti i controlli del caso.

• Il sistema è dotato di capacità di stampa delle tessere e di acces-


so remoto. In questo caso possiamo ipotizzare che la produzione
delle tessere sia totalmente automatica o che il rilascio sia manua-
le e soggetto ai soliti controlli (stavolta a posteriori) da parte del
bibliotecario.

Specificate i tre casi descritti qui sopra, mediante i diagrammi più adat-
ti.
CAPITOLO 3. ANALISI DEI REQUISITI Page 145

Coerenza della composizione della biblioteca

Con riferimento alla biblioteca descritta nel Paragrafo 3.1, specificate at-
traverso un opportuno invariante OCL che ogni settore appartiene neces-
sariamente a un altro settore o direttamente alla biblioteca.

Settori “trasversali” della biblioteca

Arricchite i diagrammi delle classi riportati nelle Figure 3.17 e 3.18 in mo-
do che vengano inclusi anche i settori trasversali (come definiti nel Pa-
ragrafo 3.1.9) e le corrispondenti relazioni. Valutate le implicazioni di
questa estensione.

Gestione delle prenotazioni dei prestiti

Con riferimento alla biblioteca descritta nel Paragrafo 3.1, specificate la


possibilità, da parte dell’utente registrato e abilitato, di prenotare docu-
menti concessi in prestito ma attualmente non disponibili e anche le re-
sponsabilità del sistema nel supportare le attività di prenotazione.

Gestione di più ascensori

Con riferimento ai modelli riportati nel Paragrafo 3.2 consideriamo il caso


in cui l’edificio è dotato di più ascensori, e ogni piano è servito da più
ascensori. Il controllore li deve gestire tutti in modo coordinato.
Il dominio del problema non cambia, cioè gli ascensori, i sensori, i
pulsanti, le lampadine, ecc. si comportano sempre come descritto in pre-
cedenza. L’unica differenza sta nel fatto che le istanze di ascensore (con
annessi e connessi) sono molteplici.
Occorre dunque rivedere il modello del sistema in modo che rappre-
senti la situazione in cui —a parità di tutte le altre condizioni— il control-
lore debba gestire più ascensori.
Ipotizziamo (come ragionevole) che le richieste effettuate ai piani pos-
sano essere gestite da un ascensore qualunque.
Consideriamo anche, come variazione del punto precedente, il caso in
cui ogni ascensore serva un sottoinsieme dei piani presenti nell’edificio.

Suggerimento Nel sistema con un unico ascensore tutte le informazioni


per la gestione (chiamate, fermate programmate, ecc.) erano concentrate
nel GestoreAscensore. In presenza di più ascensori occorre valutare se
Page 146 CAPITOLO 3. ANALISI DEI REQUISITI

esistono informazioni specifiche del singolo ascensore, oltre a eventuali


informazioni di gestione condivise.
È immediato rendersi conto che ogni ascensore dovrà disporre di un
suo programma di viaggio, di cui faranno parte le richieste effettuate in-
ternamente all’ascensore stesso, oltre alle fermate decise dal gestore.
Viceversa, poiché le chiamate effettuate ai piani possano essere gestite
da un ascensore qualunque, queste ultime faranno parte di un insieme di
informazioni di gestione condivise.
In sostanza quindi la classe che rappresenta il gestore dell’ascensore
conserverà tutte le informazioni condivise (ad esempio, gli elenchi delle
chiamate pendenti), mentre le informazioni specifiche di ciascun ascen-
sore potranno essere allocate in una classe apposita, che avrà tante istan-
ze quanti sono gli ascensori. Questa classe sarà associata al gestore con
una relazioni 1 a molti e all’ascensore con una relazione 1 a 1.
In aggiunta a questo cambiamento marginale nel modello statico del
sistema, occorre poi considerare che i compiti di “scheduling” del gesto-
re (cioè la pianificazione dei movimenti degli ascensori) divengono più
complessi, poiché, essendoci più ascensori, esistono più gradi di libertà
nella gestione delle richieste.
Ad esempio, se abbiamo due ascensori entrambi in salita e arriva una
richiesta a scendere da un piano inferiore alla posizione di entrambi gli
ascensori, non è immediato decidere quale dei due dovrà servire la chia-
mata. Infatti, volendo minimizzare il tempo di attesa dell’utente non pos-
siamo limitarci a considerare la posizione degli ascensori (cioè quanta
strada devono fare per raggiungere il piano della richiesta) ma dobbiamo
considerare anche quante fermate hanno già programmato, poiché ogni
fermata richiede un certo tempo.
Occorre dunque modellare queste strategie, modificando la macchina
a stati del gestore dell’ascensore o specificando altrimenti (ad esempio
con OCL) le regole di scheduling.

Gestione dei malfunzionamenti dell’ascensore


Riesaminate il modello del sistema, considerando la possibilità di malfun-
zionamenti e prevedendo che il sistema debba gestire opportunamente le
situazioni eccezionali.
Page 147

Capitolo 4

Progettazione

Obiettivo del capitolo è mostrare come, partendo dalla descrizione dei re-
quisiti del sistema, UML possa essere utilizzato nelle fasi di progettazio-
ne dell’architettura del sistema e di progettazione di dettaglio dei singoli
componenti architetturali. In particolare illustreremo un esempio di pro-
gettazione di una semplice applicazione distribuita e mostreremo come i
diversi strumenti messi a disposizione da UML 2 possono essere utilizzati
per descrivere gli aspetti statici e dinamici del progetto del sistema. L’e-
sempio è semplice dal punto di vista delle funzionalità e della comples-
sità della logica applicativa. Questa scelta è stata effettuata per concen-
trare l’attenzione del lettore sull’utilizzo di UML come strumento di pro-
gettazione senza introdurre inutili complicazioni legate a un particolare
esempio applicativo. Il secondo esempio, più complesso, affronta alcuni
problemi tipici della progettazione architetturale in un contesto web.

4.1 Chat distribuita


Vogliamo progettare una semplice chat, caratterizzata dai requisiti infor-
mali descritti nel seguito, che consente a diversi utenti lo scambio di mes-
saggi in rete.

• Esistono diverse stanze virtuali (ciascuna legata a una tematica spe-


cifica) nelle quali un utente può entrare per scambiare messaggi con
gli altri utenti presenti nella stessa stanza.

• I messaggi inviati da un utente sono trasmessi a tutti gli altri parte-


cipanti presenti nella stanza.
Page 148 CAPITOLO 4. PROGETTAZIONE

• È comunque possibile inviare messaggi “privati” ad uno specifico


partecipante presente nella stessa stanza. In questo caso il destina-
tario selezionato sarà l’unico a ricevere il messaggio.

• Il sistema prevede la presenza di una tipologia di utente particola-


re (il moderatore della chat), con l’autorità di inviare avvisi ad altri
utenti, di espellere utenti e di creare o eliminare stanze della chat.
In ogni istante nella chat può esistere un solo moderatore.

• Un moderatore della chat, dopo aver inviato un messaggio di av-


viso all’utente interessato, può successivamente richiedere la sua
espulsione dall’insieme dei partecipanti alla chat.

• Ogni successivo tentativo di invio di un messaggio da parte di un


utente espulso deve essere rifiutato dal sistema.

Vogliamo che l’interazione dell’utente con il sistema avvenga attraver-


so una semplice interfaccia testuale utilizzata per inviare messaggi agli al-
tri utenti e per visualizzare i messaggi ricevuti, oltre agli avvisi provenienti
dal moderatore della chat.

4.1.1 Requisiti e modello concettuale


Per poter inquadrare correttamente il problema è necessario fornire un
minimo di strutturazione ai requisiti informali descritti nel paragrafo pre-
cedente. Questo obiettivo è ottenuto realizzando un diagramma dei casi
d’uso e definendo un modello concettuale che consenta di catturare tutti
gli aspetti rilevanti del dominio del problema.
La Figura 4.1 illustra i principali casi d’uso del sistema e in particola-
re individua due gruppi distinti di funzionalità, esercitate rispettivamen-
te dal generico partecipante alla chat e dal moderatore. Le funzionalità
del sistema associate ai casi d’uso del primo gruppo sono brevemente
descritte nel seguito.

• IngressoInChat. Il partecipante richiede al sistema l’ingresso in una


specifica stanza della chat (che deve essere stata precedentemente
creata dal moderatore della chat).

• UscitaDallaChat. Un partecipante richiede al sistema l’uscita dal-


la chat. Questo comporta la sua eliminazione dall’insieme dei par-
tecipanti presenti nella stanza della chat cui era precedentemente
associato l’utente.
CAPITOLO 4. PROGETTAZIONE Page 149

IngressoNellaChat

UscitaDallaChat
InvioMessaggioPrivato
EliminazioneStanza

CreazioneStanza EspulsioneUtente InvioMessaggio

InvioAvvisoAUtente

ModeratoreChat UtenteChat

Figura 4.1: Diagramma dei casi d’uso per la chat.

• InvioMessaggio. Un partecipante alla chat invia un messaggio “pub-


blico” che viene inviato dal sistema a tutti i partecipanti che si tro-
vano nella stessa stanza di colui che ha inviato il messaggio.

• InvioMessaggioPrivato. Un partecipante alla chat invia un mes-


saggio “privato” ad uno specifico partecipante.

Come mostrato nella Figura 4.1, il moderatore è una specializzazione


del generico partecipante e può pertanto partecipare a tutti i casi d’uso in
cui questi è coinvolto. In aggiunta alle funzionalità previste per il generico
partecipante, il sistema mette a disposizione del moderatore le seguenti
funzionalità.

• CreazioneStanza. Il moderatore della chat è responsabile della crea-


zione (preventiva) delle stanze che potranno successivamente esse-
re visitate dai partecipanti.

• EliminazioneStanza. In ogni momento un moderatore può elimi-


nare una stanza dalla chat (ad esempio perchè non ci sono parte-
cipanti). Ciò comporta l’invio preventivo di un messaggio a tutti i
partecipanti eventualmente presenti nella stanza, i quali potranno
in seguito richiedere l’ingresso in una nuova stanza della chat.
Page 150 CAPITOLO 4. PROGETTAZIONE

• InvioAvvisoAUtente. Il moderatore può in ogni momento inviare


un messaggio di avviso ad uno specifico partecipante alla chat.
• EspulsioneUtente. Il moderatore può in ogni momento espellere
dalla chat uno specifico partecipante.

Stanza
- nome: String
+ aggiungiPartecipante(p: Partecipante): void
+ rimuoviPartecipante(p: Partecipante): void
+ esistePartecipante(p: IPartecipante): boolean
- partecipanteStanza + stanzaVuota(): boolean
* + getPartecipanti(): Partecipante[*]
Partecipante - partecipanteChat
- nome: String *
* - stanzaChat

Messaggio

- moderatore Chat
ModeratoreChat 1

Figura 4.2: Modello concettuale.

Il diagramma delle classi che rappresenta gli aspetti principali del do-
minio del problema è rappresentato nella Figura 4.2. Il significato degli
elementi rappresentati è il seguente.
• Partecipante rappresenta il generico utente connesso alla chat. È
caratterizzato da un nome e può ricevere messaggi pubblici o privati
inviati da altri partecipanti alla chat. Interagisce con la chat richie-
dendo la registrazione e l’ingresso in una specifica stanza, l’uscita
volontaria dalla chat, l’invio di messaggi pubblici o privati agli altri
utenti presenti nella stessa stanza.
• Messaggio individua il generico messaggio scambiato fra utenti del-
la chat e fra moderatori e utenti. Nel modello concettuale non è ul-
teriormente dettagliata la struttura interna del messaggio (che può
essere qualsiasi). Nel caso più semplice la classe messaggio sarà uti-
lizzata per trasportare delle semplici stringhe di testo. Come eviden-
ziato dalla figura la classe Messaggio è utilizzata dalle classi Chat,
Partecipante e ModeratoreChat.
• ModeratoreChat rappresenta un moderatore connesso alla chat. È
una specializzazione di Partecipante. Pertanto ha un nome e può in-
teragire con gli altri partecipanti in modo del tutto analogo a quanto
CAPITOLO 4. PROGETTAZIONE Page 151

descritto per la classe Partecipante. Inoltre il moderatore interagi-


sce con la chat per creare le stanze, inviare avvisi agli utenti connessi
e infine per espellere utenti dalla chat.

• Stanza è caratterizzata da un nome. Ciascuna istanza della classe in-


dividua una specifica stanza nella chat e mantiene un riferimento
all’insieme di partecipanti che in un certo istante sono presenti nel-
la stanza. La Figura 4.2 evidenzia le principali operazioni fornite dal-
la classe Stanza per la gestione dell’insieme dei partecipanti ad una
specifica stanza della chat.

• Chat rappresenta e incapsula il sistema chat nel suo complesso. Man-


tiene la lista delle stanze e dei partecipanti alla chat. Riceve le ri-
chieste di ingresso nelle stanze da parte dei partecipanti. Svolge il
ruolo di smistatore di messaggi inviati dalle istanze di Partecipante
e riceve le richieste di invio avvisi e di espulsione dal Moderatore.

Osserviamo che il modello concettuale considerato, pur essendo del


tutto ragionevole, incorpora già al suo interno un’importante scelta ar-
chitetturale. Il modello descritto attribuisce infatti alla classe Chat il ruo-
lo di mediatore nello scambio di messaggi fra i partecipanti nonostante
questo tipo di comportamento non sia esplicitamente richiesto dai re-
quisiti del sistema. Il modello concettuale, in generale, non dovrebbe
anticipare scelte che sono di responsabilità della fase di progetto del si-
stema, poiché questo modo di procedere potrebbe pregiudicare la scelta
dell’architettura più idonea per il sistema da realizzare.

4.1.2 Architettura logica


Come discusso nel paragrafo precedente, un possibile modello architet-
turale, implicitamente suggerito dal modello concettuale definito, preve-
de la presenza di un mediatore con il ruolo di smistatore dei messaggi
fra i partecipanti alla chat. Si tratta quindi di un classico modello di tipo
client/server nel quale esiste un componente, denominato ChatManager
che riceve tutte le richieste di servizio inviate da partecipanti e modera-
tore, le analizza e decide come procedere. Ricordiamo che questo tipo
di architettura deriva da una particolare scelta progettuale e rappresenta
soltanto uno tra i diversi modelli architetturali possibili.
Un primo passo nella descrizione dell’architettura logica del sistema
consiste nella definizione della struttura logico-funzionale complessiva,
nell’individuazione degli elementi principali coinvolti e delle modalità di
Page 152 CAPITOLO 4. PROGETTAZIONE

interconnessione e interazione tra essi. Queste informazioni sono cattu-


rate attraverso la costruzione della vista logico-funzionale.
Per la costruzione di questa vista architetturale si possono utilizzare
diagrammi di struttura composita e diagrammi dei componenti per de-
scrivere gli aspetti statici e strutturali, mentre i diagrammi di sequenza, di
comunicazione) ed i diagrammi di attività possono essere realizzati per
descrivere le interazioni rilevanti fra gli elementi architetturali del siste-
ma. La vista logico-funzionale non evidenzia la distribuzione fisica degli
elementi dell’architettura.

Chat

moderatore: ModeratoreChat[1] : ChatManager[1]


ModeratoreChat

partecipanteChat: Partecipante[*] stanzaChat: Stanza[*]

UtenteChat

Figura 4.3: Vista logico-funzionale del sistema.

In aggiunta alla vista logico-funzionale, durante il progetto dovremo


costruire altre tre viste architetturali, tra loro complementari, per fornire
una descrizione sufficientemente esaustiva del sistema [KRU95].

• Vista sull’organizzazione del codice (Module View). È una vista le-


gata al progetto di dettaglio del sistema e in particolare illustra quali
sono le unità di codice sorgente (ad esempio classi Java) che costi-
tuiscono il sistema e come queste entità interagiscono tra loro per
realizzare l’architettura descritta nelle viste logico-funzionale e di
distribuzione.

• Vista sulla distribuzione del software (Deployment View). Si tratta di


una vista sull’architettura fisica del sistema. Descrive le modalità di
dispiegamento dei componenti architetturali sui nodi nodi, dispo-
sitivi (device) e ambienti di esecuzione (execution environment) del
sistema.

• Vista sulla struttura di esecuzione (Runtime View). Fornisce una de-


scrizione degli aspetti rilevanti del sistema in esecuzione in termini
di processi e thread attivi sui nodi. Si tratta di una vista piuttosto cri-
tica (almeno per alcune categorie di applicazioni) che spesso viene
CAPITOLO 4. PROGETTAZIONE Page 153

trascurata dai progettisti anche a causa della carenza di strumenti


opportuni per la sua descrizione.

Il diagramma di struttura composita nella Figura 4.3 introduce la vista


logico-funzionale del sistema nel suo complesso. Il diagramma mostra, in
accordo a quanto definito da UML 2, la scomposizione del sistema nelle
sue parti principali, evidenzia la molteplicità delle parti costituenti e i col-
legamenti (communication link) esistenti tra esse. Data la semplicità del
sistema considerato il diagramma evidenzia una sostanziale corrispon-
denza uno-a-uno fra le parti del sistema e le entità del modello concettua-
le precedentemente introdotto. La differenza rilevante è che gli elementi
(parti) sono da considerare non più entità del modello concettuale, ma
elementi dell’architettura logica del sistema.
La Figura 4.3 evidenzia attraverso relazioni di dipendenza quali sono
le parti del sistema direttamente coinvolte nell’interazione con gli atto-
ri UtenteChat e ModeratoreChat. Ciascuna istanza di Partecipante e di
ModeratoreChat dovrà pertanto fornire un’opportuna interfaccia utente
per consentire alle due diverse tipologie di attori l’accesso alle funziona-
lità individuate dal diagramma dei casi d’uso della Figura 4.1.
Le informazioni rappresentate nella Figura 4.3 possono a questo pun-
to essere dettagliate e raffinate esplicitando i componenti UML che rap-
presentano i sottosistemi in cui è scomposta l’architettura logica del siste-
ma Chat. In UML 2 un sottosistema individua una particolare tipologia di
componente che genericamente rappresenta “un’unità di scomposizio-
ne gerarchica del sistema”. In un diagramma dei componenti un sotto-
sistema viene rappresentato con un componente a cui viene associato lo
stereotipo subsystem. Il diagramma di struttura composita della Figu-
ra 4.4 introduce i sottosistemi Partecipante, Moderatore e ChatManager
ed evidenzia:

• le mutue relazioni di dipendenza tra i componenti ChatManager e


Partecipante e tra ChatManager e Moderatore;
• le relazioni di dipendenza tra i componenti e le parti costituenti illu-
strate nel diagramma precedente, oltre alle relazioni di dipendenza
verso gli elementi del modello concettuale ritenuti rilevanti, a loro
volta rappresentati come parti nel diagramma. Un esempio è da-
to dalla relazione di dipendenza esistente fra ciascuno dei sottosi-
stemi individuati e la parte Messaggio. Il motivo di questa dipen-
denza è abbastanza intuitivo: un Messaggio rappresenta l’unità di
informazione che viene scambiata tra i sottosistemi Partecipante,
Page 154 CAPITOLO 4. PROGETTAZIONE

<<subsystem>>
Chat
<<use>>
<<subsystem>>
<<use>> ChatManager
<<use>>

<<use>>
<<use>>

<<subsystem>> <<subsystem>>
Partecipante Moderatore

<<use>> <<use>>
<<use>>

: Messaggio

: Partecipante : ChatManager : Stanza

: ModeratoreChat

Figura 4.4: Sottosistemi nella vista logico-funzionale.


CAPITOLO 4. PROGETTAZIONE Page 155

Moderatore e ChatManager. Un’ulteriore relazione di dipendenza è


quella esistente fra il componente ChatManager e la parte Stanza;

• le relazioni di realizzazione tra i componenti e le parti individua-


te nel diagramma precedente: il sottosistema Partecipante è una
realizzazione della parte Partecipante precedentemente descritta.
Analogamente i sottosistemi Moderatore e ChatManager costituisco-
no rispettivamente una realizzazione delle parti ModeratoreChat e
ChatManager;

• le relazioni di specializzazione esistenti fra i componenti del siste-


ma. Un esempio è il componente Moderatore, rappresentato nel
diagramma come una specializzazione di Partecipante.

Fino a ora abbiamo utilizzato il termine componente in modo abba-


stanza intuitivo. È opportuno a questo punto cercare di chiarire meglio
cosa intendiamo. Secondo la specifica UML 2, un componente è “un’unità
modulare con interfacce ben definite che può essere sostituita senza im-
patti sull’ambiente circostante”. L’interpretazione suggerita dalla specifi-
ca considera quindi un componente come un’unità autonoma di riuso in
un sistema o sottosistema, il cui comportamento “ai morsetti” è comple-
tamente individuato attraverso le sue interfacce, che costituiscono l’uni-
co punto di contatto del componente con il mondo esterno. Notiamo che
quest’interpretazione è diversa da quella considerata valida nelle versioni
precedenti della specifica UML, nelle quali un componente era definito
come un’unità fisica di deployment, ovvero un’entità che poteva essere
installata sui nodi del sistema (ad esempio una libreria di classi). Que-
sta differenza di interpretazione non è marginale e va tenuta ben presen-
te per evitare pericolose ambiguità di interpretazione del progetto del si-
stema. Nel seguito del libro il termine componente verrà utilizzato con il
significato attribuito dalla versione 2 della specifica UML.
Una volta identificati i sottosistemi (componenti) principali del siste-
ma, il passo successivo consiste nell’individuare ed esplicitare le interfac-
ce esposte dai singoli sottosistemi. Queste individuano l’unico punto di
contatto fra i componenti e l’esterno, nel senso che tutte le interazioni
che coinvolgono ciascun componente potranno avvenire esclusivamente
attraverso le interfacce individuate. L’esplicitazione delle interfacce di un
componente consente (almeno in questa fase di progettazione architettu-
rale) di trattare il componente stesso come una “scatola nera” (black-box)
senza la necessità di doversi preoccupare dei dettagli sulla sua struttura
interna (che potrebbe essere complicatissima).
Page 156 CAPITOLO 4. PROGETTAZIONE

Questo approccio presenta per il progettista l’indubbio vantaggio di


poter descrivere l’architettura logica del sistema (sia dal punto di vista sta-
tico, che dal punto di vista dinamico) esclusivamente in termini di com-
ponenti e di interfacce. Il sistema complessivo viene modellato per “as-
semblaggio” dei componenti individuati attraverso le interfacce fornite e
richieste dai singoli componenti, che definiscono le API (Application Pro-
gramming Interface) pubbliche che sono fornite o richieste dai compo-
nenti. Nelle fasi di progetto di dettaglio e di implementazione (quando
cioè la scatola nera di ciascun componente deve essere aperta) il proget-
tista o lo sviluppatore potranno concentrarsi esclusivamente sull’imple-
mentazione di queste interfacce senza preoccuparsi, almeno in linea di
principio, delle interazioni tra componenti diversi (già descritte nella fase
di progettazione architetturale). Il vantaggio evidente di questo modo di
procedere è che consente in ogni momento di sostituire, a parità di inter-
facce, l’implementazione di un componente con un’altra con la garanzia
di avere un impatto ragionevolmente ridotto sulle altre parti del sistema
(in teoria nullo). In ogni momento sarà quindi possibile scegliere la tecno-
logia, l’insieme di librerie o gli standard più adatti per l’implementazione
di un componente specifico senza ripercussioni sul progetto generale del
sistema.

<<subsystem>>
Moderatore

Comandi Messaggi

IModeratore
IChatAdmin
Comandi Messaggi

<<subsystem>> <<subsystem>>
Partecipante ChatManager

Comandi Comandi
IChat
Messaggi Messaggi
IPartecipante

Figura 4.5: Definizione delle interfacce dei componenti.


CAPITOLO 4. PROGETTAZIONE Page 157

Il diagramma dei componenti1 della Figura 4.5 introduce fra i sotto-


sistemi precedentemente individuati le principali interfacce e porte che
definiscono il contratto esterno dei componenti.

• IPartecipante definisce la modalità di invio di un messaggio a un


partecipante alla chat. È fornita dal sottosistema Partecipante. L’in-
terfaccia è anche fornita indirettamente dal sottosistema Moderatore
in quanto specializzazione di Partecipante. L’interfaccia viene ri-
chiesta (utilizzata) dal sottosistema ChatManager per comunicare con
il sottosistema Partecipante attraverso le porte Messaggi esposte
dai due sottosistemi.

• IModeratore definisce le modalità di comunicazione del resto del


sistema con un moderatore della chat. È fornita dal sottosistema
Moderatore ed è utilizzata dal sottosistema ChatManager attraverso
la porta Messaggi.

• IChat incapsula i servizi forniti dal sottosistema ChatManager e ri-


chiesti da Partecipante attraverso la porta Comandi per richiedere
l’ingresso o l’uscita dalla chat e per l’invio di messaggi ad altri par-
tecipanti. Il sottosistema ChatManager fornisce l’interfaccia IChat
attraverso la porta Comandi.

• IChatAdmin individua i servizi forniti dal sottosistema ChatManager


e richiesti da Moderatore per l’ingresso in chat, per l’invio di mes-
saggi di avviso ad altri partecipanti o per comunicare a ChatManager
la richiesta di espulsione di un partecipante dalla chat. L’interfaccia
è fornita da ChatManager ed è utilizzata da Moderatore attraverso la
porta Comandi.

Il dettaglio delle operazioni fornite dalle singole interfacce è illustrato


nel diagramma della Figura 4.6 e descritto nel seguito.

• Interfaccia IPartecipante

– msg() individua una richiesta di invio di un messaggio (con


struttura interna definita nella classe Messaggio) da parte di un
partecipante della chat. Il tipo del parametro che rappresenta
il mittente è l’interfaccia IPartecipante. Questo significa che
1
Le porte del componente ChatManager sono ripetute per una migliore disposizione
degli elementi.
Page 158 CAPITOLO 4. PROGETTAZIONE

il mittente potrà essere istanza di un qualunque elemento che


realizza l’interfaccia IPartecipante (in particolare questo va-
le per i componenti Partecipante e Moderatore). ChatManager
utilizza questa interfaccia per distribuire una richiesta di invio
di un messaggio “pubblico” a tutti i partecipanti presenti nella
stessa stanza del mittente.

• Interfaccia IModeratore. È una interfaccia vuota, utilizzata come


“segnaposto” nelle operazioni per identificare una generica entità
di tipo moderatore nei parametri delle operazioni dell’interfaccia
IChatAdmin.

• Interfaccia IChat

– registraPartecipante()
individua una richiesta di ingresso in una specifica stanza nella
chat da parte di un partecipante. Il nome della stanza è speci-
ficato come secondo parametro dell’operazione. A seguito del-
l’esecuzione dell’operazione il richiedente viene aggiunto al-
l’elenco dei partecipanti presenti nella stanza specificata come
secondo parametro.
– abbandonaChat() individua una richiesta di uscita dalla chat da
parte di un partecipante. L’operazione comporta la sua elimi-
nazione dall’insieme dei partecipanti alla chat.
– msg() viene richiesta da un Partecipante per inviare il mes-
saggio a ChatManager ed è implementata da ChatManager per
distribuire il messaggio a tutti i partecipanti della stanza del
mittente.
– msgPrivato() individua la richiesta di invio di un messaggio
privato a uno specifico partecipante alla chat.
– getNomiStanze() restituisce l’insieme dei nomi delle stanze pre-
senti nella chat.
– getPartecipantiChat() restituisce l’insieme dei partecipanti
alla chat.
– getPartecipantiStanza() restituisce l’insieme dei partecipan-
ti alla stanza della chat specificata come parametro, oppure un
insieme vuoto se la stanza non esiste.

• Interfaccia IChatAdmin
CAPITOLO 4. PROGETTAZIONE Page 159

<<subsystem>>
Partecipante

<<interface>>
IChat
+ registraPartecipante(p: IPartecipante, nomeStanza: String):void
+ abbandonaChat(p: IPartecipante):void <<interface>>
+ msg(mitt: IPartecipante, msg: Messaggio):void IPartecipante
+ msgPrivato(mitt: IPartecipante, msg: Messaggio, dest: IPartecipante):void
+ getNomiStanze(): String [*] + msg(mitt: IPartecipante, msg: Messaggio):void
+ getPartecipantiChat(): IPartecipante [*]
+ getPartecipantiStanza(nomeStanza: String ): IPartecipante [*]

<<subsystem>> <<interface>> <<subsystem>>


Moderatore IModeratore ChatManager

<<interface>>
IChatAdmin
+ registraModeratore(moderatore: IModeratore):void
+ avvisaPartecipante(m: IModeratore, p:IPartecipante, msgAvviso: Messaggio):void
+ espelliPartecipante(m: IModeratore, p: IPartecipante):void
+ creaStanza(nome: String, m: IModeratore):void
+ eliminaStanza(nome: String, m: IModeratore):void

Figura 4.6: Dettaglio delle interfacce esposte dai componenti dell’architettura.

– registraModeratore() individua una richiesta di ingresso nel-


la chat da parte del moderatore.
– avvisaPartecipante() viene utilizzata da un moderatore per
inviare un messaggio di avviso (definito dal secondo parametro
dell’operazione) al partecipante specificato come primo para-
metro.
– espelliPartecipante() viene utilizzata da un moderatore per
richiedere l’espulsione dalla chat del Partecipante specificato
come primo parametro.
– creaStanza() viene utilizzata da un moderatore per richiedere
la creazione di una particolare stanza della chat (individuata
dal nome specificato come parametro).
– eliminaStanza() viene utilizzata da un moderatore per richie-
dere l’eliminazione di una specifica stanza della chat (indivi-
duata dal nome specificato come parametro). Tutti i partecipan-
ti presenti nella stanza ricevono un messaggio di notifica della
chiusura della stanza. Successivamente la stanza viene rimossa
dall’insieme delle stanze della chat.
Page 160 CAPITOLO 4. PROGETTAZIONE

A questo punto possiamo considerare ragionevolmente definita l’ar-


chitettura logica del sistema dal punto di vista statico poiché sono stati
individuati i principali componenti architetturali e le interfacce richieste
e fornite da ciascuno di essi. Partendo da quanto definito finora passiamo
ora alla descrizione degli scenari dinamici di interazione fra i componenti
dell’architettura.

Inizializzazione della chat

La fase di inizializzazione della chat prevede la registrazione del modera-


tore e la successiva creazione dell’insieme iniziale delle stanze della chat.
Per descrivere le interazioni rilevanti utilizzeremo dei diagrammi di se-
quenza e in particolare mostreremo come l’utilizzo dei textitframe di in-
terazione introdotti in UML 2 consenta di ottenere una descrizione mo-
dulare e molto espressiva delle interazioni di interesse.

<<subsystem>> <<subsystem>>
ModeratoreChat m: Moderatore chat: ChatManager

richiesta registrazione
registraModeratore(m)

alt
setModeratore(m)
[non esiste già un moderatore registrato ]

segnala registrazione avvenuta

[else ]

errore: Moderatore già esistente

segnala impossibilità di registrazione

Figura 4.7: Diagramma di sequenza RegistraModeratore.

La Figura 4.7 descrive lo scenario di ingresso di un moderatore nella


chat.

Scenario: registrazione del moderatore nella chat.

Attori: utente-moderatore (ModeratoreChat), sottosistemi Moderatore e


ChatManager.
CAPITOLO 4. PROGETTAZIONE Page 161

Precondizioni: nessuna.

Svolgimento:

• L’interazione inizia con una richiesta esplicita di ingresso nella


chat inviata dall’utente-moderatore al sottosistema Moderatore
attraverso l’interfaccia utente fornita dal componente.
• Il sottosistema Moderatore interpreta la richiesta e, utilizzando
la porta Comandi (non mostrata nella figura), propaga la richie-
sta al sottosistema ChatManager invocando l’operazione regi-
straModeratore() esposta dall’interfaccia IChatAdmin. Il para-
metro passato nell’invocazione dell’operazione è un riferimen-
to al sottosistema Moderatore richiedente (poichè Moderatore
implementa l’interfaccia IModeratore il tipo del parametro pas-
sato è consistente con quanto richiesto dalla interfaccia).
• Il sottosistema ChatManager verifica se esiste un moderatore
registrato per la chat.

Svolgimento alternativo 1:

• Se non esiste un moderatore registrato, ChatManager registra il


riferimento al Moderatore (attraverso l’invocazione dell’opera-
zione interna setModeratore()) e termina con successo l’inte-
razione.

Svolgimento alternativo 2:

• Se esiste un moderatore registrato, ChatManager segnala un er-


rore al chiamante (e all’utente).

Postcondizioni: esiste un moderatore registrato nel sistema.

Per evidenziare in modo visivo i due frammenti di interazione alterna-


tivi nel diagramma di sequenza è stato utilizzato un frame di interazione
di tipo alt. Questa tipologia di frammento consente di specificare percor-
si di esecuzione alternativi nello scenario ed è sostanzialmente parago-
nabile allo statement case utilizzato nei linguaggi di programmazione. La
scelta di uno tra i percorsi alternativi definiti viene effettuata valutando le
condizioni (guard condition) specificate tra parentesi quadre in ciascuno
dei rami alternativi del frame: viene eseguito il frammento di interazione
per il quale la corrispondente condizione di guardia è vera. Nel caso in
Page 162 CAPITOLO 4. PROGETTAZIONE

<<subsystem>> <<subsystem>>
: ModeratoreChat m: Moderatore chat: ChatManager
richiesta creazione stanza ns
creaStanza(ns, m) isModeratore(m)

esisteStanza(ns)

alt
<<create>>(ns)
[isModeratore(m) and not(esisteStanza(ns))] : Stanza
ok
ok

[isModeratore(m) and esisteStanza(ns)]

errore: stanza già presente


errore: stanza già presente

[not isModeratore(m)] errore: moderatore non abilitato


alla richiesta
errore: moderatore non abilitato
alla richiesta

Figura 4.8: Diagramma di sequenza CreaStanza.


CAPITOLO 4. PROGETTAZIONE Page 163

cui nessuna delle condizioni risulti verificata viene eseguito, se presente,


il frammento associato alla condizione else.
Il secondo scenario che consideriamo con riferimento alla fase di ini-
zializzazione della chat riguarda la creazione dell’insieme iniziale di stan-
ze da parte del moderatore ed è mostrato nella Figura 4.8.

Scenario: creazione di una stanza della chat da parte del moderatore.

Attori: utente-moderatore, sottosistemi Moderatore e ChatManager.

Precondizioni: Il moderatore si è precedentemente registrato nel siste-


ma.
Page 164 CAPITOLO 4. PROGETTAZIONE

Svolgimento:

• L’utente-moderatore attraverso l’interfaccia utente richiede al


sottosistema Moderatore la creazione di una stanza con nome
ns.
• Il sottosistema Moderatore interpreta la richiesta e, utilizzando
la porta Comandi (non mostrata nella figura), propaga la richie-
sta al sottosistema ChatManager invocando l’operazione cre-
aStanza() esposta dall’interfaccia IChatAdmin. Nell’invocazio-
ne dell’operazione sono passati come parametri il nome ns del-
la stanza da creare e un riferimento al sottosistema Moderatore
richiedente.
• Il sottosistema ChatManager verifica se il sottosistema Modera-
tore richiedente è il moderatore registrato per la chat (attraver-
so l’invocazione dell’operazione interna isModeratore()) e se
esiste già nella chat una stanza con il nome passato come para-
metro nella richiesta (attraverso l’invocazione dell’operazione
interna esisteStanza()).

Svolgimento normale:

• se il componente richiedente è il moderatore registrato per la


chat e non esiste nella chat una stanza di nome ns, ChatManager
crea la nuova stanza e termina con successo l’interazione.

Svolgimento alternativo 1:

• se il componente richiedente è il moderatore registrato per la


chat, ma esiste già nella chat una stanza di nome ns l’intera-
zione termina con la segnalazione dell’errore al chiamante (e
all’utente).

Svolgimento alternativo 2:

• se il richiedente non è il moderatore registrato per la chat l’in-


terazione termina con la segnalazione dell’errore al chiamante
(e all’utente).

Postcondizioni: nel caso di svolgimento normale e di svolgimento alter-


nativo 1 esiste una stanza di nome ns nella chat.
CAPITOLO 4. PROGETTAZIONE Page 165

<<subsystem>> <<subsystem>>
: ModeratoreChat moderatore: Moderatore chat: ChatManager

ref
RegistraModeratore(moderatore)

opt
[registrazione moderatore OK ]

definizione insieme iniziale di nomi di stanze da creare

loop
[per ogni nome stanza ns nell'insieme ]

ref
CreaStanza(ns,moderatore)

Figura 4.9: Diagramma di sequenza per la inizializzazione della chat.

La Figura 4.9 descrive lo scenario complessivo di inizializzazione del-


la chat ed è ottenuto come composizione dei due scenari di base prece-
dentemente illustrati. La composizione viene realizzata utilizzando frame
di interazione di tipo ref, attraverso i quali è possibile referenziare fram-
menti specificati in dettaglio in altri diagrammi di sequenza. I frammenti
referenziati con l’operatore ref possono essere inseriti in altri frame di in-
terazione esattamente come accade nel caso di interazioni singole, con la
possibilità di specificare alcuni parametri che rappresentano i valori at-
tuali da sostituire al momento dell’esecuzione ai parametri formali defi-
niti nel frammento incluso. Questa modalità di inclusione di frammenti
all’interno di un diagramma di sequenza è del tutto assimilabile all’in-
vocazione di una procedura in un linguaggio di programmazione. Il dia-
gramma di sequenza nella Figura 4.9 utilizza anche nuove tipologie di fra-
me di interazione finora non utilizzate: il frame di tipo loop e il frame di
tipo opt. Il primo consente di specificare la ripetizione delle interazio-
ni contenute nel frammento fino a quando una condizione di guardia è
verificata. Il secondo individua un insieme di interazioni che vengono
eseguite soltanto se la corrispondente condizione di guardia è verificata.
Il frame di tipo opt può essere considerato una variante “semplificata” di
quello di interazione alt, con un solo ramo di esecuzione condizionata
Page 166 CAPITOLO 4. PROGETTAZIONE

(associato alla condizione di guardia del frammento opt) e con un ramo


else vuoto. Il dettaglio dei passi dello scenario è descritto nel seguito.

Scenario: inizializzazione della chat.

Attori: utente-moderatore (ModeratoreChat), sottosistemi Moderatore e


ChatManager.

Precondizioni: nessuna.

Svolgimento:

• L’utente-moderatore richiede la registrazione nella chat secon-


do le modalità specificate nel diagramma di sequenza referen-
ziato dal frame di interazione RegistraModeratore. L’intera-
zione viene invocata passando come parametro il riferimento
al sottosistema Moderatore che richiede la registrazione.

Svolgimento opzionale: eseguito solo nel caso in cui la registrazione del


moderatore sia andata a buon fine.

• L’utente-moderatore definisce l’insieme dei nomi delle stanze


che devono essere create.
• L’utente-moderatore richiede al sistema la creazione di tutte
le stanze dell’insieme definito al passo precedente secondo le
modalità specificate nel diagramma di sequenza referenziato
dal frame di interazione CreaStanza. Ad ogni iterazione, Cre-
aStanza viene referenziata passando come parametro il nome
corrente della stanza da creare.

Postcondizioni: esiste un moderatore registrato nella chat. L’insieme


iniziale di stanze della chat è stato creato.

In questo paragrafo abbiamo mostrato, con riferimento alla fase di ini-


zializzazione della chat, come l’utilizzo dei frame di interazione nei dia-
grammi di sequenza sia uno strumento molto potente che consente di
realizzare una specifica estremamente modulare e dettagliata senza in-
correre nel problema di “esplosione” delle dimensioni che si verificava
con UML 1.x anche con diagrammi non particolarmente complessi, con
il risultato di rendere difficoltosa la lettura e l’interpretazione della speci-
fica. Dobbiamo tuttavia osservare che l’uso di questo strumento di mo-
dularizzazione da parte del progettista non deve diventare un abuso, nel
CAPITOLO 4. PROGETTAZIONE Page 167

senso che un’eccessiva parcellizzazione delle interazioni rende difficolto-


sa la comprensione della specifica allo stesso modo di un diagramma di
sequenza “gigante”.

Interazioni di un partecipante alla chat

Esamineremo ora le principali interazioni di interesse per un partecipan-


te alla chat, ovvero l’ingresso e l’abbandono della chat e l’invio di un mes-
saggio a un altro partecipante. Per dettagliare le interazioni seguiremo
lo stesso approccio del paragrafo precedente, descrivendo inizialmente le
interazioni di base che saranno successivamente referenziate nelle inte-
razioni più complesse.

<<subsystem>> <<subsystem>>
s: Stanza : Stanza
: UtenteChat p: Partecipante chat: ChatManager
richiesta ingresso in chat
nella stanza ns
registraPartecipante(p, ns)
esisteStanza(ns)

alt s= getStanza(ns)
[esisteStanza(ns) ]

aggiungiPartecipante(p)

loop
[per ogni altra stanza diversa da s]

rimuoviPartecipante(p)

ok
ok

[else ]
errore: stanza non esistente
errore: stanza non esistente

Figura 4.10: Diagramma di sequenza RegistraPartecipante.

Il primo scenario di interesse per un partecipante alla chat, descritto


nella Figura 4.10 è quello relativo alla registrazione e all’ingresso in una
stanza specifica della chat.
Page 168 CAPITOLO 4. PROGETTAZIONE

Scenario: ingresso di un partecipante nella chat.


Attori: utente-partecipante (UtenteChat), sottosistemi Partecipante e
ChatManager.
Precondizioni: nessuna.
Svolgimento:
• L’utente-partecipante attraverso l’interfaccia utente richiede al
sottosistema Partecipante l’ingresso nella stanza della chat con
nome ns.
• Il sottosistema Partecipante riceve la richiesta e, utilizzando la
porta Comandi (non mostrata nella figura), propaga la richiesta
al sottosistema ChatManager invocando l’operazione registra-
Partecipante() esposta dall’interfaccia IChat. Nell’invocazio-
ne dell’operazione sono passati come parametri il nome ns del-
la stanza di interesse per il partecipante e un riferimento al
Partecipante richiedente.
• Il sottosistema ChatManager verifica se esiste nella chat una stan-
za con il nome ns (passato come parametro nella richiesta).
Svolgimento normale: attivato se esiste nella chat una stanza di nome
ns.
• ChatManager individua la stanza di nome ns nell’insieme di stan-
ze della chat.
• ChatManager aggiunge il partecipante all’insieme dei parteci-
panti della stanza invocando sull’oggetto Stanza l’operazione
aggiungiPartecipante() e specificando come parametro il ri-
ferimento al Partecipante richiedente.
• ChatManager richiede la rimozione del partecipante da tutte le
altre stanze della chat iterando sull’insieme delle stanze e in-
vocando a ogni iterazione sulla stanza corrente l’operazione
rimuoviPartecipante().
• ChatManager termina con successo l’interazione.
Svolgimento alternativo: attivato se non esiste nella chat una stanza di
nome ns.
• L’interazione termina con la segnalazione dell’errore al sottosi-
stema chiamante (e all’utente).
CAPITOLO 4. PROGETTAZIONE Page 169

Postcondizioni: nel caso di svolgimento normale il partecipante è regi-


strato ed è presente nell’insieme dei partecipanti della stanza speci-
ficata.

RegistraPartecipante

<<subsystem>>
chat: ChatManager[1]

<<subsystem>>
partecipanteChat: Partecipante[1]

stanzeChat: Stanza[*]

Figura 4.11: Collaborazione per la registrazione di un partecipante alla chat.

Un altro utile strumento di specifica introdotto da UML 2 è quello del-


le collaborazioni. Una collaborazione individua un insieme di oggetti del
sistema, ciascuno con uno specifico ruolo, che interagiscono per realiz-
zare insieme una specifica funzionalità del sistema. Una collaborazione
non specifica i dettagli delle interazioni fra gli oggetti coinvolti. L’obietti-
vo è catturare solo gli aspetti essenziali mostrando gli oggetti coinvolti, le
molteplicità con cui i diversi oggetti partecipano alla collaborazione ed i
collegamenti (communication link) esistenti fra gli oggetti. I nomi delle
istanze presenti identificano il ruolo giocato dagli oggetti nella collabora-
zione. La collaborazione RegistraPartecipante nella Figura 4.11 illustra
ad esempio i principali oggetti coinvolti nella registrazione di un parteci-
pante alla chat. Gli elementi del sistema che partecipano alla collabora-
zione sono i componenti (sottosistemi) Partecipante (un’istanza, con il
ruolo partecipanteChat) e ChatManager (una istanza, con il ruolo chat),
oltre a una collezione di oggetti di classe Stanza. Nella collaborazione non
compaiono gli attori, in quanto entità esterne al sistema. I collegamenti
fra Partecipante e ChatManager e fra ChatManager e Stanza evidenziano
l’esistenza di un’interazione diretta fra questi oggetti. Gli aspetti dinami-
ci della collaborazione sono quelli precedentemente descritti in dettaglio
con il diagramma di sequenza della Figura 4.10.
Uno degli aspetti rilevanti delle collaborazioni è che possono essere
contestualizzate, cioè possono essere applicate a un contesto specifico.
Page 170 CAPITOLO 4. PROGETTAZIONE

RegistrazionePartecipanti

: RegistraPartecipante

partecipanteChat partecipanteChat
<<subsystem>> <<subsystem>>
p2: Partecipante p3: Partecipante
stanzeChat
partecipanteChat
: Stanza[*]
<<subsystem>>
p1: Partecipante
chat
<<subsystem>>
: ChatManager

Figura 4.12: Collaborazione per la registrazione di più partecipanti alla chat.

Un collaboration use rappresenta l’applicazione dell’interazione cattura-


ta dalla collaborazione a una situazione specifica in cui le particolari clas-
si e istanze collaborano tra loro con i ruoli e secondo le modalità stabili-
te dalla collaborazione stessa. Il diagramma della Figura 4.12 mostra un
esempio di applicazione della collaborazione RegistraPartecipante alla
registrazione di tre distinti partecipanti alla chat. Nel diagramma è mo-
strata un’unica istanza ChatManager che interagisce con i tre partecipanti,
i quali a loro volta partecipano tutti al collaboration use con il ruolo di
partecipanteChat.
Osserviamo che in base a quanto detto finora è naturale considerare
una collaborazione lo strumento ideale per descrivere pattern (architettu-
rali o di design). Nel capitolo successivo mostreremo in particolare alcuni
esempi di descrizione di pattern attraverso le collaborazioni UML.
Tornando alla descrizione degli scenari di interesse per il partecipante
alla chat, consideriamo ora l’invio di un messaggio “pubblico” alla chat da
parte di un partecipante. Lo scenario è mostrato nella Figura 4.13.
CAPITOLO 4. PROGETTAZIONE Page 171

<<subsystem>> <<subsystem>> <<subsystem>>


: UtenteChat mittente: Partecipante chat: ChatManager : Partecipante

richiesta invio messaggio

<<create>> m: Messaggio

msg(mittente, m)

loop
[per tutti i partecipanti nella stanza del mittente]

msg(mittente, m)

Figura 4.13: Diagramma di sequenza InvioMessaggio.

Scenario: invio di un messaggio da parte di un partecipante alla chat.

Attori: utente-partecipante (UtenteChat), sottosistemi Partecipante e


ChatManager.

Precondizioni: il mittente si è precedentemente registrato e appartiene


all’insieme degli utenti di una delle stanze della chat.

Svolgimento:

• L’utente-partecipante attraverso l’interfaccia utente richiede al


sottosistema Partecipante l’invio di un messaggio “pubblico”
alla chat.
• Il sottosistema Partecipante riceve la richiesta e crea una istan-
za di Messaggio contenente il messaggio dell’utente-partecipante.
• Il sottosistema Partecipante, utilizzando la porta Comandi (non
mostrata nella figura), propaga la richiesta al sottosistema Chat-
Manager invocando l’operazione msg() esposta dall’interfaccia
IChat. Nell’invocazione dell’operazione sono passati come pa-
rametri il riferimento al Partecipante richiedente e l’istanza di
messaggio creata al passo precedente.

Postcondizioni: il messaggio inviato dal mittente è stato distribuito a


tutti gli altri partecipanti presenti nella stessa stanza del mittente.
Page 172 CAPITOLO 4. PROGETTAZIONE

Il diagramma della Figura 4.14 mostra l’abbandono della chat da parte


di un partecipante registrato.

<<subsystem>> <<subsystem>> <<subsystem>>


stanza: Stanza
p: Partecipante chat: ChatManager : Partecipante
rimuoviPartecipante(p)

loop
[per ogni stanza s ]
esistePartecipante(p)

opt
[esiste partecipante p in stanza s ]

rimuoviPartecipante(p)

loop
[per ogni altro partecipante alla stanza s ]

msg(p, "Uscita dalla chat")

Figura 4.14: Diagramma di sequenza AbbandonaChat.

Scenario: uscita di un partecipante nella chat.

Attori: utente-partecipante (UtenteChat), sottosistemi Partecipante e


ChatManager.

Precondizioni: il partecipante richiedente è presente in una delle stanze


della chat.

Svolgimento:

• L’utente-partecipante attraverso l’interfaccia utente richiede al


sottosistema Partecipante l’uscita dalla chat.
• Il sottosistema Partecipante riceve la richiesta e, utilizzando la
porta Comandi (non mostrata nella figura), propaga la richiesta
al sottosistema ChatManager invocando l’operazione rimuovi-
Partecipante() esposta dall’interfaccia IChat. Nell’invocazio-
ne dell’operazione è passato come parametro il riferimento al
Partecipante richiedente.
CAPITOLO 4. PROGETTAZIONE Page 173

• Il sottosistema ChatManager itera sull’insieme delle stanze del-


la chat e per ogni stanza della chat verifica se il partecipante
richiedente appartiene all’elenco dei partecipanti della stanza
corrente.

Svolgimento opzionale: attivato se il partecipante richiedente appartie-


ne all’elenco dei partecipanti della stanza corrente.

• ChatManager rimuove il partecipante richiedente dalla stanza


corrente;
• ChatManager itera sull’insieme degli altri partecipanti alla stan-
za corrente e, utilizzando la porta Messaggi invia a ciascuno de-
gli altri partecipanti alla stanza una notifica dell’uscita del par-
tecipante richiedente attraverso l’invocazione dell’operazione
msg(). L’operazione viene invocata passando come parame-
tri il riferimento al partecipante richiedente e un messaggio
convenzionale che segnala l’uscita dello stesso dalla chat.

Postcondizioni: il partecipante è eliminato dalla lista dei partecipanti


alla chat.

L’ultimo diagramma di sequenza che mostriamo in questo paragrafo è


un esempio di interazione che coinvolge tre partecipanti della chat, dalla
fase di registrazione all’uscita dalla chat (Figura 4.15). Analogamente a
quanto fatto nel paragrafo precedente utilizziamo i frame di interazione
di tipo ref per referenziare le interazioni di base descritte.

4.1.3 Progetto di dettaglio


I concetti trattati ci hanno fornito una sufficiente conoscenza di dettaglio
dell’architettura logica del sistema sia dal punto di vista statico (principali
componenti, porte e interfacce), sia dal punto di vista dinamico (descri-
zione dettagliata delle principali interazioni fra i componenti dell’archi-
tettura). Ricordiamo che l’obiettivo dell’attività di progettazione archi-
tetturale è descrivere l’interazione dei componenti dell’architettura con il
mondo esterno senza entrare nel merito della struttura interna dei com-
ponenti stessi. Adesso è il momento di aprire le “scatole nere” rappresen-
tate dei componenti logici allo scopo di definire la struttura interna. In
altri termini a questo punto occorre dedicarsi al progetto di dettaglio del
sistema.
Page 174 CAPITOLO 4. PROGETTAZIONE

<<subsystem>> <<subsystem>> <<subsystem>> <<subsystem>>


p1: Partecipante p2: Partecipante p3: Partecipante : ChatManager stanza1: Stanza

consider

ref
RegistraPartecipante(p1,ns)

ref
RegistraPartecipante(p2,ns)

ref
RegistraPartecipante(p3,ns)

ref
InvioMessaggio(p1,messaggio1)

ref
InvioMessaggio(p2,messaggio2)

ref
AbbandonaChat(p3)

ref
InvioMessaggio(p1,messaggio3)

ref
AbbandonaChat(p2)

ref
AbbandonaChat(p1)

Figura 4.15: Diagramma di sequenza per l’interazione di più partecipanti alla


chat.
CAPITOLO 4. PROGETTAZIONE Page 175

In particolare in questa fase, oltre a scegliere il linguaggio da utiliz-


zare per l’implementazione dei componenti dell’architettura, dobbiamo
considerare le problematiche di distribuzione dei componenti della piat-
taforma sui nodi del sistema (trascurate nella progettazione logica dell’ar-
chitettura) e in particolare effettuare la scelta della piattaforma di midd-
leware da utilizzare per la comunicazione fra i componenti distribuiti.
Dando per scontata la scelta del linguaggio Java per l’implementazio-
ne, osserviamo che ci sono ancora dei gradi di libertà nella scelta dell’ar-
chitettura del sistema. Ricordando che dal punto di vista logico il sistema
è stato modellato con un’architettura client-server, la scelta più ragione-
vole e immediata consiste nel mappare l’architettura logica definita nei
paragrafi precedenti su un’architettura fisica sempre di tipo client-server,
utilizzando ad esempio Java RMI (Remote Method Invocation). Questa
però non è l’unica scelta possibile a disposizione del progettista. Ad esem-
pio alcune possibili scelte alternative (più o meno ragionevoli) sono le
seguenti.

• Progetto di dettaglio con architettura client-server, ma con inter-


faccia web. Questo approccio consentirebbe l’accesso alla chat con
semplice browser e nel caso si decida di realizzare l’interfaccia uten-
te del partecipante come Applet Java sarebbe possibile riusare qua-
si senza modifiche il progetto di dettaglio con architettura client-
server basato su Java RMI.

• Progetto di dettaglio con architettura fisica di tipo diverso dall’ar-


chitettura client-server (ad esempio basato su un’architettura peer-
to-peer o su un’infrastruttura di tipo publish/subscribe)

4.1.4 Progetto di dettaglio con Java RMI


La Figura 4.16 mostra una prima possibile descrizione della struttura in-
terna dei sottosistemi Partecipante, Moderatore e ChatManager: tutti i
sottosistemi sono implementati da una singola classe avente come no-
me quello del sottosistema seguito dal suffisso Impl. I connettori di dele-
ga mostrati nella figura connettono le porte dei componenti con le classi
XXXImpl ed evidenziano come tali classi siano gli effettivi fornitori e ri-
chiedenti delle interfacce offerte e richieste dai componenti dell’architet-
tura logica del sistema. In particolare le classi PartecipanteImpl e Mode-
ratoreImpl sono anche responsabili dell’interazione con gli attori, ovvero
con gli utenti esterni del sistema (utente-moderatore e utenti-partecipanti
Page 176 CAPITOLO 4. PROGETTAZIONE

<<subsystem>>
Moderatore <<delegate>> Comandi

IChatAdmin IChatAdmin

ModeratoreImpl

IModeratore Messaggi

<<delegate>>
IModeratore
Messaggi Comandi

<<subsystem>>
ChatManager
<<subsystem>> <<delegate>>
Partecipante <<delegate>>

<<delegate>> Messaggi
<<delegate>>
IChatAdmin
IChat Messaggi IChat IChat
PartecipanteImpl
ChatManagerImpl

<<delegate>> Comandi <<delegate>>

IPartecipante Comandi IPartecipante IPartecipante

Figura 4.16: Dettaglio componenti, porte e interfacce interne.

alla chat, non mostrati nella figura). Nel seguito verrà fornita una descri-
zione dei meccanismi di base di funzionamento di Java RMI e si mostrerà
l’impatto del suo utilizzo nel progetto di dettaglio del sistema chat.

Java RMI

L’invocazione remota di metodi è la più diffusa e immediata tra le tecni-


che per lo sviluppo di sistemi distribuiti messe a disposizione dall’am-
biente Java. Il più importante principio ispiratore di RMI è di rendere
quanto più possible trasparente al programmatore il fatto che gli ogget-
ti che costituiscono l’applicazione distribuita siano posizionati su diver-
se Java Virtual Machine (JVM), tipicamente residenti su macchine anche
molto distanti tra loro.
Il meccanismo fondamentale di RMI è infatti la chiamata di metodo,
che avviene —dal punto di vista del programmatore— in un unico modo,
indipendentemente dal fatto che i due oggetti coinvolti (quello che in-
via la richiesta di esecuzione del metodo e quello che la riceve) risiedano
sulla stessa JVM o su JVM distinte. Ciò che rende possibile la trasparen-
za rispetto alla distribuzione degli oggetti è la disponibilità di riferimenti
a oggetti remoti sintatticamente indistinguibili dai riferimenti agli ogget-
ti locali. Anche la semantica dei due riferimenti è estremamente simile,
poiché il middleware che supporta RMI si preoccupa di gestire gli aspetti
CAPITOLO 4. PROGETTAZIONE Page 177

relativi alla distribuzione, sollevando il programmatore da questa preoc-


cupazione. Dunque, in un’applicazione distribuita che utilizza Java RMI,
il codice o.m(p) può avere due significati distinti.
Se o si riferisce a un oggetto locale, si tratta di una normalissima chia-
mata del metodo m di o. Se invece o è un riferimento a un oggetto remoto,
si tratta di un’invocazione remota. Nel secondo caso, la chiamata di meto-
do effettuata dal client viene realizzata da RMI mediante un processo non
banale (che il programmatore può però largamente ignorare) e descritto
nel seguito.

1. La chiamata viene indirizzata a un oggetto locale (chiamato stub)


“facente funzione” di o.

2. Lo stub prepara un messaggio, in cui vengono inserite le informa-


zioni salienti della chiamata: in prima approssimazione l’identifica-
tore dell’oggetto, l’identificatore del metodo da chiamare e i para-
metri). Lo stub invia il messaggio alla macchina server.

3. Presso la macchina server si estraggono dal messaggio ricevuto le


informazioni necessarie per effettuare la chiamata di o.m(p). Il me-
todo o.m viene quindi eseguito.

4. Se o.m(p) restituisce un risultato, questo viene confezionato in un


apposito messaggio che viene ritrasmesso allo stub, che estrarrà il
risultato e lo comunicherà al chiamante, restituendogli il controllo.
L’esecuzione riprende quindi dall’istruzione successiva alla chiama-
ta o.m(p).

Naturalmente, affinché tutto ciò funzioni occorre che il client possie-


da un riferimento all’oggetto remoto o. Tale riferimento può essere pas-
sato come parametro di una chiamata di metodo, in stile perfettamente
object-oriented. Tuttavia qui consideriamo un’altra possibilità, che sfrut-
ta il servizio di naming offerto da RMI, utilizzabile mediante la seguente
procedura.

1. Il server crea un oggetto capace di offrire alcuni servizi che si vuole


rendere disponibili a client remoti.

2. Il server effettua una registrazione (o binding) presso il servizio di


naming RMIRegistry. L’effetto della registrazione è che all’oggetto
viene assegnato un nome simbolico.
Page 178 CAPITOLO 4. PROGETTAZIONE

3. Il client si rivolge a RMIregistry passandogli il nome simbolico di


un servizio e ottenendone il riferimento remoto all’oggetto remoto
associato a quel nome.
4. Il client può finalmente usare il riferimento all’oggetto remoto sen-
za più preoccuparsi della locazione dell’oggetto. Cioè, da questo
punto in avanti il programma non contiene più istruzioni finalizzate
esplicitamente alla gestione della distribuzione.
Per la verità RMI non persegue la trasparenza a ogni costo. Ne con-
segue che esiste un certo numero di casi in cui il programmatore non
può ignorare totalmente il fatto che il sistema sia distribuito. Inoltre, RMI
supporta molteplici meccanismi di sviluppo di applicazioni distribuite,
come ad esempio la possibilità di avere codice mobile. In questa sede
non ci addentriamo nella trattazione dei dettagli e degli argomenti che
non sono funzionali allo sviluppo della nostra chat. Rimandiamo il letto-
re desideroso di approfondimenti ai numerosissimi testi che illustrano il
funzionamento e le modalità di programmazione di RMI.

<<component>>
<<component>> RMIServer
RMIRegistry
<<delegate>>
ServerImpl
IPublish IPublish

ServerSkeleton
IService
ILookup
IService

<<delegate>>
<<component>>
RMIClient
Client
<<delegate>>
IService
IService

ServerStub
IService

Figura 4.17: Componenti architetturali coinvolti nella comunicazione basata su


Java RMI.

L’architettura concettuale di un’applicazione distribuita minimale fa-


cente uso di RMI è descritta nella Figura 4.17. Potete vedere i tre com-
ponenti di cui abbiamo parlato: Server, Client e RMIRegistry. È chiara-
mente possibile realizzare applicazioni più complesse, ad esempio con
CAPITOLO 4. PROGETTAZIONE Page 179

più client o con più server, o con un componente che si comporta sia
da server che da client, ecc. Tuttavia, la Figura 4.17 benché semplice
mostra chiaramente le connessioni e i ruoli svolti dai vari componenti e
soprattutto come sono organizzati internamente il client e il server.

• Il componente RMIRegistry mette a disposizione due interfacce:


IPublish, utilizzata dal server per rendere noti e accessibili i servizi
offerti, e ILookup utilizzata dal client per procurarsi un riferimento
all’oggetto remoto che implementa un dato servizio.

• All’interno del componente RMIClient il Client utilizza l’interfaccia


messa a disposizione da ServerStub. La comunicazione tra Client
e ServerStub avviene localmente, mediante semplici chiamate di
metodo.

• Analogamente, nel componente RMIServer il servizio offerto da


ServerImpl viene invocato localmente da ServerSkeleton.

• L’interfaccia IService tra i componenti sottolinea che l’invocazio-


ne del server da parte del client viene trasformata in una chiamata
inter-componenti, gestita da ServerStub e da ServerSkeleton, sen-
za che il Client e il Server si debbano preoccupare della gestione
delle difficoltà dovute alla loro collocazione su macchine diverse.

RMIRegistry Client ServerStub ServerSkeleton ServerImpl

lookup(nomeServizio)

riferimento a oggetto remoto (stub)


chiamata(metodo, parametri) invioRichiesta(IdOggetto,
IdMessaggio,
parametriMarshalled)
chiamata(metodo, parametri)

risultato "marshalled" risultato

risultato

Figura 4.18: La sequenza di operazioni che caratterizzano una tipica interazione


tra componenti RMI.
Page 180 CAPITOLO 4. PROGETTAZIONE

Una tipica sequenza di operazioni che utilizza il registry e realizza una


chiamata remota di metodo in ambiente Java RMI è illustrata nella Figu-
ra 4.18.
Inizialmente il client si rivolge al servizio di naming (RMIregistry)
passandogli il nome di un servizio e chiedendogli il riferimento a un og-
getto remoto che lo implementi. In conseguenza di questa richiesta
RMIregistry fornisce il riferimento, sotto forma di uno stub cui il client
può accedere normalmente (attraverso normali chiamate di metodo).

RMI

client: RMIClient [*] server: RMIServer [1]

registry: RMIRegistry [1]

Figura 4.19: Collaborazione RMI.

Le chiamate inviate allo stub non sono soddisfatte localmente, ma


danno luogo all’invio di richieste al server remoto, previo “marshalling”
dei parametri (cioè i parametri sono convertiti in un formato adatto alla
trasmissione in rete). Il server riceve le richieste sotto forma di messaggi,
ricostruisce le chiamate originali (“unmarshalling”) e le passa all’oggetto
locale indicato, ricevendone risultati di ritorno. Questi sono a loro volta
convertiti in un formato adatto alla rete e spediti allo stub, che ricostrui-
sce il valore di ritorno e lo passa al client, che per tutto il tempo resta in
attesa di tale risultato.
Affinché la sequenza della Figura 4.18 funzioni, occorre che il server
abbia preventivamente registrato, attraverso la primitiva rebind, il nome
del servizio e l’oggetto responsabile della sua esecuzione presso RMIRegistry.
Queste operazioni non sono mostrate nel diagramma di sequenza della
Figura 4.18.

RMI nel progetto di dettaglio della chat

A questo punto resta da chiarire come si può utilizzare RMI nel caso spe-
cifico della chat, cioè qual è il ruolo giocato dai diversi componenti della
CAPITOLO 4. PROGETTAZIONE Page 181

chat nell’architettura RMI. Come sappiamo, gli strumenti più idonei per
mostrare in modo visuale e sintetico questi aspetti sono le collaborazioni
e i diagrammi di uso di collaborazione (collaboration use). In particolare,
nella Figura 4.19 è riportato il diagramma che rappresenta i ruoli svolti dai
componenti in una generica architettura RMI.

RMI

: RMI server
client
: ModeratoreImpl client
: ChatManagerImpl
server registry
client
: RMIRegistry
: PartecipanteImpl

Figura 4.20: RMI Collaboration e contestualizzazione all’applicazione chat.

Nella Figura 4.20 sono invece indicati i componenti della chat e il ruo-
lo che ciascuno di essi ricopre nell’organizzazione architetturale impo-
sta da RMI. La figura evidenzia in particolare che il moderatore assume
esclusivamente il ruolo di client, mentre il sistema di gestione della chat
e il partecipante si comportano sia da client sia da server (cioè mettono a
disposizione e utilizzano servizi remoti).
Nella Figura 4.21 sono illustrate le relazioni esistenti tra gli elementi
che implementano la nostra chat e i costrutti forniti da RMI. Le classi che
implementano il partecipante, il moderatore e il sistema di gestione della
chat sono infatti specializzazioni di UnicastRemoteObject, mentre le in-
terfacce che permettono a queste classi di interagire sono specializzazioni
dell’interfaccia Remote.
Quanto riportato nella Figura 4.21 deve essere coerente con quanto
riportato nelle Figure 4.17 e 4.20.

Vista di deployment del sistema chat

La Figura 4.22 individua i nodi del sistema e mostra il dispiegamento sui


nodi degli artifact (o elaborati) che manifestano i componenti del sistema
descritti in questo paragrafo. In particolare i nodi coinvolti sono PCParte-
cipante, PCModeratore e ChatServer.
Page 182 CAPITOLO 4. PROGETTAZIONE

<<interface>>
Remote
(JDK 5.0 Classes.java.rmi)

IModeratore

ModeratoreImpl
IChatAdmin

PartecipanteImpl ChatManagerImpl
IChat

IPartecipante

UnicastRemoteObject
(JDK 5.0 Classes.java.rmi.server)

Figura 4.21: Dettaglio delle interfacce.

<<subsystem>> <<subsystem>> <<component>> <<subsystem>>


Partecipante ChatManager RMIRegistry Moderatore

<<manifest>> <<manifest>> <<manifest>> <<manifest>>

<<artifact>> <<artifact>> <<artifact>> <<artifact>>


Partecipante ChatManager RMIRegistry Moderatore

<<deploy>> <<deploy>> <<deploy>> <<deploy>>

PCPartecipante ChatServer PCModeratore


RMI RMI
<<protocol>> <<protocol>>
JavaVM JavaVM JavaVM

Figura 4.22: Artifact e nodi.


CAPITOLO 4. PROGETTAZIONE Page 183

• ChatServer ospita gli artifact associati ai componenti ChatManager e


RMIRegistry. Il dispiegamento di RMIRegistry sullo stesso nodo di
ChatManager è necessario per consentire a ChatManager di effettua-
re la pubblicazione del proprio riferimento sul registry (la pubbli-
cazione è infatti possibile soltanto se il registry si trova sulla stessa
macchina che ospita l’oggetto remoto che richiede la registrazione).

• PCModeratore ospita gli artifact associati ai componenti responsa-


bili della gestione dell’interazione con il moderatore della chat.

• PCPartecipante ospita gli artifact associati ai componenti respon-


sabili della gestione dell’interazione con il generico partecipante al-
la chat. Comunica con il nodo ChatServer attraverso il protocollo
RMI per l’invio di messaggi agli altri partecipanti e per le procedure
di ingresso e uscita dalla chat.

4.2 Controllo degli accessi in un’applicazione web


Obiettivo dell’esempio è mostrare l’utilizzo di UML in un progetto di in-
tegrazione di componenti in un’applicazione preesistente in un contesto
web. Elementi caratterizzanti del progetto di integrazione sono la traspa-
renza e non invasività rispetto alla struttura interna dell’applicazione e
dell’ambiente di esecuzione preesistenti.
Esiste un’applicazione web (portale) responsabile dell’erogazione di
servizi (ad esempio servizi di e-government) a utenti finali. Quest’appli-
cazione, della quale non si conoscono i dettagli interni di funzionamento,
deve essere arricchita con funzionalità di autenticazione e autorizzazione
degli utenti al fine di discriminare l’accesso ai singoli servizi applicativi
erogati. L’integrazione delle nuove funzionalità richieste deve essere ef-
fettuata nel modo più trasparente possibile e non invasivo rispetto ai ser-
vizi preesistenti e all’ambiente di esecuzione: l’infrastruttura deve inter-
cettare le richieste di servizio effettuate dall’utente durante la navigazio-
ne sul portale, autorizzare o meno l’utente all’accesso e successivamente
trasferire il controllo al servizio applicativo.

Requisiti informali

• Il portale potrà erogare i propri servizi applicativi soltanto a utenti


registrati.
Page 184 CAPITOLO 4. PROGETTAZIONE

• La registrazione degli utenti è delegata a un’entità esterna fidata, de-


nominata Gestore dell’Identità, responsabile del mantenimento del
profilo di registrazione degli utenti e dell’erogazione di un apposito
servizio web di registrazione. Il Gestore dell’identità è anche respon-
sabile dell’autenticazione degli utenti che richiedono l’erogazione
di un servizio del portale.
• Un utente registrato è caratterizzato da un identificativo (userNa-
me) che lo individua univocamente nel sistema, da un insieme di
informazioni anagrafiche (nome, cognome, codice fiscale, indiriz-
zo di residenza, email, ecc.) e da credenziali utilizzabili dall’utente
registrato durante la fase di autenticazione.
• Per poter accedere a un servizio un utente registrato deve auten-
ticarsi presentando credenziali di tipo opportuno, a seconda cioè
dello specifico servizio richiesto. In particolare si distingue fra cre-
denziali “deboli” e “forti” a seconda della garanzia di identificazione
del soggetto associata alla specifica credenziale utilizzata. In parti-
colare per le credenziali di tipo “forte” è importante prevedere un
processo di consegna riservato e sicuro all’utente.
• Il Gestore dell’identità, contattato dal portale erogatore al momento
della richiesta di accesso a un servizio da parte di un utente, è re-
sponsabile di richiedere le credenziali all’utente, verificarle e di co-
municare al portale erogatore dei servizi l’esito dell’autenticazione.
Le credenziali utente non devono mai venire a conoscenza del por-
tale erogatore del servizio e rimangono quindi un segreto condiviso
tra utente e Gestore dell’Identità.
• Gli utenti sono caratterizzati da un insieme di qualifiche (ad esem-
pio professionali o in generale ruoli noti al portale erogatore del ser-
vizio) che vengono utilizzate nella successiva fase di autorizzazione
per stabilire se l’utente autenticato ha titolo o meno per accedere al
servizio richiesto.
• L’insieme delle qualifiche possedute da uno specifico utente (carat-
terizzato dal suo userName) è situato nel dominio del portale ero-
gatore dei servizi (e non fa quindi parte del profilo di registrazione
mantenuto dal Gestore dell’identità). L’attribuzione delle qualifiche
a un utente del sistema viene effettuato attraverso un opportuno
servizio amministrativo di accreditamento disponibile sul portale
erogatore dei servizi finali.
CAPITOLO 4. PROGETTAZIONE Page 185

• Ciascun servizio erogato dal portale è caratterizzato da un insie-


me di qualifiche autorizzate all’accesso. Una volta completata con
successo l’autenticazione il portale erogatore dei servizi deve con-
sentire all’utente richiedente l’accesso al servizio richiesto soltan-
to se l’utente stesso è in possesso di almeno una tra le qualifiche
autorizzate.

Accreditamento

Registrazione
Utente ServiceProvider

AccessoAiServizi

<<include>> <<include>>

Autenticazione Autorizzazione

Figura 4.23: Diagramma dei casi d’uso per la registrazione e l’accesso ai servizi.

4.2.1 Casi d’uso e modello concettuale

Il sistema da realizzare è responsabile della gestione delle funzionalità


di registrazione, accreditamento e autenticazione degli utenti del siste-
ma. Il diagramma dei casi d’uso della Figura 4.23 evidenzia le macro-
funzionalità fornite dall’infrastruttura e gli attori coinvolti.

Attori

• ServiceProvider rappresenta nella sua globalità il portale erogatore


dei servizi finali agli utenti registrati.

• Utente. Rappresenta la persona fisica che richiede via web l’accesso


ai servizi finali erogati dal Service Provider.
Page 186 CAPITOLO 4. PROGETTAZIONE

Casi d’uso

• Registrazione individua la procedura adottata dal portale eroga-


tore dei servizi per identificare con certezza l’utente e incaricare il
proprio Gestore dell’identità di creare le sue credenziali e di conse-
gnarle all’interessato in modo riservato e sicuro.

• Accreditamento individua la procedura attuata dal portale erogato-


re dei servizi finali che consente di associare a un utente registra-
to le qualifiche necessarie per l’accesso ai servizi finali erogati dal
portale.

• Autenticazione individua la procedura di certificazione e verifica


delle credenziali presentate da un utente registrato che sta richie-
dendo la fruizione di un servizio.

• Autorizzazione individua la procedura, attuata dal portale eroga-


tore dei servizi finali, che stabilisce se un utente autenticato che sta
richiedendo la fruizione di uno specifico servizio ha diritto o me-
no alla fruizione. L’autorizzazione viene attribuita verificando l’e-
sistenza di un accreditamento dell’utente che sia compatibile con i
requisiti del servizio, ovvero in base all’esistenza di una qualifica, as-
sociata all’utente, appartenente all’insieme delle qualifiche abilitate
all’accesso al servizio.

Modello concettuale di riferimento

Tutti gli utenti del portale erogatore dei servizi (denominato d’ora in poi
Service Provider, in accordo con la definizione data in precedenza) pos-
siedono almeno una qualifica, quella di “utente registrato”, acquisita au-
tomaticamente al momento della registrazione. Il diagramma delle classi
che rappresenta le entità rilevanti è rappresentato nella Figura 4.24.

• Utente rappresenta la persona fisica che richiede via web l’accesso


ai servizi finali erogati dal Service Provider.

• Qualifica è la proprietà associata a un Utente che viene utilizza-


ta per discriminare l’accesso ai servizi erogati dal Service Provider.
Tutti gli utenti hanno almeno una qualifica, quella di “utente regi-
strato”, acquisita automaticamente al momento della registrazione.
Ciascun utente potrà poi avere qualifiche aggiuntive che attestano la
CAPITOLO 4. PROGETTAZIONE Page 187

Utente

0..*
Accreditamento

* - qualificaPosseduta
Qualifica <<enumeration>>
+ tipoQualifica
tQualifica
1

<<enumeration>>
tQualificaUtente
+ tipoQualifica
UtenteRegistrato - utenteRegistrato

+ tipoQualifica <<enumeration>>
Professionista tQualificaProfessionista
- ingegnere
- architetto
- geometra

1 + tipoQualifica <<enumeration>>
AmministratoreSistema tQualificaAdmin
- amministratoreSistema
- operatoreSistema

Figura 4.24: Utenti e qualifiche.

sua appartenenza a specifiche categorie (ad esempio categorie pro-


fessionali) o il possesso di un particolare ruolo (ad esempio ammini-
stratore del sistema). Un utente può richiedere l’attribuzione di una
qualifica addizionale accedendo esplicitamente a un servizio web di
accreditamento messo a disposizione dal Service Provider.

• tQualifica caratterizza insieme alle sue sottoclassi le possibili tipo-


logie di Qualifica considerate valide nel sistema. In particolare nel
diagramma sono evidenziate come sottoclassi tQualificaUtente,
tQualificaProfessionista,
tQualificaAdmin. Ciascuna sottoclasse definisce tutti e soli i valori
possibili per ciascuna tipologia di Qualifica (ad esempio tQualifica-
Professionista ammette come valori possibili soltanto ingegnere,
architetto e geometra).

• Accreditamento caratterizza l’associazione tra uno specifico utente


e una particolare qualifica.

Il diagramma delle classi in Figura 4.25 illustra le entità rilevanti per la


definizione del Profilo di servizio.
Page 188 CAPITOLO 4. PROGETTAZIONE

• ProfiloServizio individua i requisiti di uno specifico servizio ero-


gato dal Service Provider per l’autenticazione e l’autorizzazione al-
l’accesso. Comprende un ProfiloAutenticazione e un ProfiloAu-
torizzazione.

• ProfiloAutenticazione individua la tipologia di credenziali che do-


vranno essere presentate dall’utente al Gestore dell’identità durante
l’autenticazione.

• ProfiloAutorizzazione individua l’insieme di qualifiche autorizza-


te alla fruizione del servizio. A uno specifico utente verrà consentito
l’accesso al servizio soltanto se, al momento della richiesta di acces-
so, risulterà in possesso di almeno una fra le qualifiche abilitate per
quello specifico servizio.

• Credenziale viene associata a un Utente al momento della regi-


strazione e utilizzata dal Gestore dell’identità per riconoscere l’u-
tente durante la fase di autenticazione. Si distingue fra credenziali
“deboli” e “forti” a seconda della garanzia di identificazione certa
del soggetto che è associata alla specifica credenziale utilizzata. In
particolare nel diagramma sono considerate le seguenti credenziali.

– UserNamePassword. Coppia di stringhe definita dallo userNa-


me dell’utente e dalla sua password assegnata in fase di regi-
strazione. La password può essere successivamente modificata
dall’utente utilizzando il servizio di registrazione.
– PIN. Stringa numerica da utilizzarsi insieme alla userName e
password.
– CertificatoX509. Certificato software con struttura conforme a
quanto definito nello standard X509 con il campo CN (Com-
mon Name) associato al codice fiscale dell’utente.

Il diagramma delle classi nella Figura 4.26 mostra infine il modello


concettuale di riferimento complessivo.
Per comprendere meglio il funzionamento generale dei meccanismi
di accesso ai servizi erogati dal service provider, con autenticazione e au-
torizzazione degli utenti, introduciamo uno scenario che descrive sinteti-
camente i passi che caratterizzano l’interazione dell’utente con il sistema.
Per limitare le dimensioni dell’esempio nel seguito non verranno detta-
gliate le modalità di gestione della fase di registrazione degli utenti e di
CAPITOLO 4. PROGETTAZIONE Page 189

1
Qualifica + qualificaAbilitata 0..* Servizio ProfiloServizio
0..*

<<enumeration>> 1..*
tQualifica ProfiloAutorizzazione ProfiloAutenticazione
+ tipoQualifica
1
<<enumeration>>
Credenziale - tipoCredenziale
tCredenziale

CredenzialeForte - tipoCredenziale <<enumeration>>


tCredenzialeForte
- PIN
CredenzialeDebole - certificatoX509
PIN
- PIN CertificatoX509

1
UserNamePassword
- userName : String
- password : String
- tipoCredenziale <<enumeration>>
tCredenzialeDebole
- usernamePassword

Figura 4.25: Servizi e qualifiche abilitate.

popolamento dell’insieme di qualifiche associate al singolo utente. La Fi-


gura 4.27 mostra sinteticamente uno scenario di accesso ai servizi, nel
caso in cui l’autenticazione e la successiva autorizzazione vadano a buon
fine. Nella figura le interazioni non fanno riferimento a “semplici” chia-
mate di metodo, bensı̀ descrivono sinteticamente il significato dei singo-
li passi. I dettagli emergeranno nella trattazione successiva, dedicata al
progetto architetturale.

Scenario: accesso ad un servizio da parte di un utente registrato (svolgi-


mento normale).

Attori: Utente e ServiceProvider.

Precondizioni: l’utente è registrato. Il gestore delle identità detiene un


profilo di registrazione associato all’utente. Il service provider detie-
ne un accreditamento associato all’utente compatibile con i requi-
siti del profilo di autorizzazione del servizio richiesto dall’utente.

Svolgimento:
Page 190 CAPITOLO 4. PROGETTAZIONE

GestoreIdentita + certificatoreCredenziali ServiceProvider


1
1 1 + erogatoreServizio
1

Registrazione * Credenziale

CredenzialeDebole CredenzialeForte

1
UserNamePassword
ProfiloRegistrazione 1 PIN
- userName: String
- nome - password: String - PIN
- cognome
- codiceFiscale CertificatoX509
- ...

Accreditamento *

*
Utente 0..* - qualificaPosseduta Qualifica

0..* + fruitore 0..* + qualificaAbilitata 0..


+ servizioErogato
0..* *
Servizio
+ servizioAcceduto
0..*

Figura 4.26: Modello di riferimento complessivo.

2: estrai ProfiloAutenticazione e ProfiloAutorizzazione da


ProfiloServizio associato a servizio richiesto da utente

5: verifica autorizzazione accesso a servizio(ProfiloAutorizzazione,


profiloUtenteAutenticato:ProfiloRegistrazione)

1: richiesta accesso a servizio


ServiceProvider
Utente
6: pagina del servizio

3: richiestaAutenticazione(tCredenziale)

4: comunica esito autenticazione e


profiloRegistrazione utente

3.3: verifica credenziali utente

3.4: estrai profilo registrazione utente

3.5: prepara esito autenticazione da


comunicare a service provider

3.1: richiesta credenziali


GestoreIdentita
3.2: credenziali utente

Figura 4.27: Scenario di accesso a un servizio da parte di un utente registrato.


CAPITOLO 4. PROGETTAZIONE Page 191

• L’utente —attraverso il web browser— richiede l’accesso a una


specifica pagina di un servizio erogato dal ServiceProvider.
• Il ServiceProvider, sulla base dell’URL specificato nella richie-
sta di accesso inviata dall’utente, individua il servizio richiesto
e ne recupera il profilo di servizio.
• Il ServiceProvider estrae dal profilo di servizio il profilo di au-
tenticazione contenente i requisiti di autenticazione del servi-
zio stesso, verifica se l’utente richiedente risulta già autenti-
cato con modalità compatibile con quella specificata nel pro-
filo di autenticazione. In questo caso si ipotizza che l’utente
non sia già autenticato (in caso contrario la procedura sarebbe
chiaramente semplificata).
• Il ServiceProvider ridirige l’utente sul GestoreIdentita con
una richiesta di autenticazione in cui è specificata la tipologia
di credenziali richieste per l’autenticazione.
• Il GestoreIdentita presenta all’utente una pagina di login che
richiede all’utente stesso l’immissione delle credenziali. L’u-
tente inserisce le proprie credenziali.
• Il GestoreIdentita accede al ProfiloRegistrazione associato
allo userName dell’utente e verifica la correttezza delle creden-
ziali inserite.
• Il GestoreIdentita prepara un ResponsoAutenticazione con-
tenente l’esito del processo di autenticazione, il tipo di creden-
ziale utilizzata dall’utente e il ProfiloRegistrazione associato
all’utente.
• Il GestoreIdentita redirige l’utente sul service provider con
una richiesta contenente il ResponsoAutenticazione prepara-
to al passo precedente.
• Il ServiceProvider analizza il ResponsoAutenticazione rice-
vuto e verifica che l’autenticazione abbia avuto successo.
• Il ServiceProvider estrae dal ProfiloRegistrazione contenu-
to nel ResponsoAutenticazione lo userName dell’utente e veri-
fica se esiste almeno un Accreditamento associato a tale userName
compatibile con le tipologie di qualifica specificate nel Profi-
loAutorizzazione associato al servizio.
• Il ServiceProvider —completate con successo tutte le verifiche—
restituisce all’utente la pagina del servizio richiesta.
Page 192 CAPITOLO 4. PROGETTAZIONE

Postcondizioni: per il ServiceProvider l’utente è autenticato e auto-


rizzato ad accedere a tutte le pagine associate al servizio richiesto
inizialmente.

4.2.2 Progetto
In questo paragrafo descriviamo brevemente le modalità di interazione
dell’utente con un’applicazione web per accedere alle pagine web asso-
ciate ai servizi erogati, senza considerare per ora l’introduzione delle fun-
zionalità specifiche di controllo degli accessi che sono oggetto del proget-
to.
La Figura 4.28 descrive gli elementi caratteristici di una tipica architet-
tura web.
• WebBrowser è il ben noto strumento utilizzato dall’utente per inte-
ragire con il sottosistema WebPresentationTier al fine di richiedere
l’accesso a risorse (pagine) associate ai servizi erogati.
• WebPresentationTier rappresenta il portale responsabile della ri-
cezione delle richieste provenienti dal WebBrowser, di elaborarle e di
costruire le risposte. Contiene una parte di logica applicativa asso-
ciata ai singoli servizi erogati e in particolare la parte di logica appli-
cativa responsabile della costruzione dinamica delle pagine web da
restituire al WebBrowser per la visualizzazione. Tale logica applica-
tiva di presentazione è implementata attraverso opportune Pagine-
Server. Il WebPresentationTier dal punto di vista del nostro pro-
getto costituisce l’erogatore delle pagine web dei servizi agli utenti.
Il WebPresentationTier interagisce con il sottosistema Business-
LogicTier, a cui delega l’implementazione della logica applicativa
più complessa associata ai servizi. Il sottosistema WebPresentationTier
è dotato in particolare di due porte: una “di ingresso” (RichiestaHTTP)
attraverso la quale vengono ricevute le richieste provenienti dai bro-
wser degli utenti, e una “di uscita” (RispostaHTTP) attraverso la qua-
le sono inviate le risposte HTTP costruite a seguito dell’elaborazione
delle richieste.
• Risorsa rappresenta una generica pagina web associata a uno dei
servizi erogati dal portale. La risorsa può essere una PaginaHTML sta-
tica oppure può essere costruita dinamicamente da una PaginaServer.
• PaginaServer contiene parte della logica applicativa che concorre a
realizzare un servizio. Esistono diverse tipologie di pagine server che
<<subsystem>>
WebPresentationTier

IBusinessTier

<<component>> <<component>>
WebBrowser RichiestaHTTP <<delegate>> RichiestaHTTP WebServer
<<subsystem>>
RichiestaHTTP HTTPRequest HTTPRequest
BusinessLogicTier
RispostaHTTP <<delegate>> RispostaHTTP

RispostaHTTP HTTPResponse HTTPResponse


CAPITOLO 4. PROGETTAZIONE

IIntegrationTier

: RisorsaWeb
<<forward>> <<subsystem>>
IntegrationTier
Utente
<<client page>> <<server page>>
<<builds>>
: PaginaHTML : PaginaServer
<<delegate>>

Figura 4.28: Principali protagonisti di un’interazione web.


<<component>>
<<subsystem>>
DataSource
ServiceProvider

<<server page>> <<server page>> <<server page>>


: Servlet : JSP : PaginaASP
Page 193
Page 194 CAPITOLO 4. PROGETTAZIONE

si differenziano per il linguaggio utilizzabile per la costruzione dina-


mica delle pagine web. Ad esempio con la piattaforma J2EE (Java 2
Enterprise Edition si possono utilizzare Servlet e pagine JSP (Java
Server Pages), mentre nel caso di tecnologia .NET si utilizzeranno
pagine ASP (Active Server Pages).

• ServiceProvider rappresenta il portale erogatore dei servizi finali


agli utenti registrati su cui si deve intervenire per introdurre le fun-
zionalità di autenticazione e autorizzazione richieste. È rappresen-
tato nella figura come una specializzazione del generico sottosiste-
ma WebPresentationTier (e ne eredita pertanto tutte le caratteristi-
che).

• BusinessLogicTier è il sottosistema responsabile dell’implementa-


zione della logica applicativa più complessa associata ai servizi ero-
gati agli utenti e dell’attuazione delle regole di business. Interagisce
con IntegrationTier attraverso l’interfaccia IIntegrationTier (la
specifica interfaccia varia in base alla specifica tecnologia — J2EE,
ASP, ecc.— utilizzata).

• IntegrationTier. È il sottosistema responsabile dell’accesso ai da-


ti. Attraverso l’interfaccia IIntegrationTier incapsula l’accesso ai
sistemi informativi. Tale interfaccia dipende dalla specifica tecnolo-
gia utilizzata (ad esempio, nel caso di utilizzo della piattaforma J2EE
l’interazione con il sottosistema di accesso ai dati avviene utilizzan-
do il protocollo JDBC (Java Database Connectivity), che rappresenta
uno standard nel mondo Java).

Un esempio di interazione (molto semplificato) tra le entità descritte


sopra è illustrato mediante il diagramma di sequenza della Figura 4.29.

Scenario: scenario semplificato di interazione web.

Attori: Utente e ServiceProvider.

Precondizioni: nessuna (valgono le condizioni normali che consentono


l’accesso a una generica risorsa web).

Svolgimento

• L’utente —attraverso il web browser— richiede l’accesso a una


specifica risorsa di un servizio erogato dal service provider.
CAPITOLO 4. PROGETTAZIONE Page 195

• Il service provider, sulla base dell’URL specificato nella richie-


sta di accesso inviata dall’utente, individua la PaginaServer e
le passa la richiesta.
• La PaginaServer elabora la richiesta ricevuta e interagisce con
il BusinessLogicTier per l’esecuzione di logica di business spe-
cifica.
• Il BusinessLogicTier interagisce con IntegrationTier per l’ac-
cesso a basi di dati.
• La PaginaServer costruisce la PaginaHTML da inviare in risposta
all’utente.
• La PaginaHTML prodotta dalla pagina server viene restituita al
service provider, che a sua volta la invia al web browser dell’u-
tente.

Postcondizioni: il web browser dell’utente visualizza la pagina richiesta


e ricevuta.

Partendo dalla struttura generale introdotta, nel seguito introdurremo


i componenti architetturali in grado di erogare le funzionalità di autenti-
cazione e autorizzazione richieste.

Intercettazione e trattamento delle richieste di servizio

Per rispettare il requisito di trasparenza e non invasività rispetto ai servizi


finali erogati dal service provider non è ovviamente possibile operare di-
rettamente sui servizi applicativi, cioè sulla logica applicativa contenuta
nelle pagine server. L’unica possibilità consiste nell’operare direttamente
sull’ambiente esistente di esecuzione dei servizi, utilizzando componenti
specifici in grado di intercettare e manipolare le richieste prima che que-
ste vengano elaborate dai componenti che implementano i servizi appli-
cativi. Naturalmente la manipolazione in questione ha lo scopo di verifi-
care che l’utente abbia le carte in regola per accedere ai servizi. Dal canto
loro i servizi (o meglio i componenti che li implementano) non si rendono
conto dell’esistenza di un’infrastruttura che filtra le richieste in ingresso.
La Figura 4.30 descrive (in modo semplificato) parte dell’organizza-
zione interna del ServiceProvider, con particolare riferimento ai com-
ponenti che intervengono nella gestione delle richieste di servizio. Una
richiesta ricevuta dal ServiceProvider attraverso la porta RichiestaHTTP
Page 196

: Utente <<subsystem>> <<server page>> <<subsystem>> <<subsystem>>


: Web Browser serviceProvider: WebPresentationTier risorsa web: PaginaServer : BusinessLogicTier : IntegrationTier

interazione con
pagina HTML visualizzata
richiestaHTTP(risorsa web)
analizza richiesta
e individua risorsa

forward

richiesta esecuzione logica di business specifica


accesso a basi dati

risultato esecuzione logica di business

<<create>> <<client page>>


Pagina HTML : PaginaHTML

Figura 4.29: Un caso semplice di accesso a risorsa web.


rispostaHTTP(Pagina HTML)

visualizzazione pagina HTML


CAPITOLO 4. PROGETTAZIONE
<<subsystem>>
ServiceProvider

<<delegate>>
RichiestaHTTP
HTTPRequest

RichiestaHTTP

<<component>>
<<delegate>> Web Server

HTTPRequest

<<component>>
<<delegate>> GestoreRichiesta
CAPITOLO 4. PROGETTAZIONE

*
CatenaFiltri
GestoreRichiesta

<<delegate>> {ordered}

IFiltro

<<delegate>>
<<component>> <<component>> <<component>>
GestoreProfiloServizio GestoreAutenticazione GestoreAutorizzazione

RispostaHTTP
: RisorsaWeb <<use>>

Figura 4.30: Componenti per il filtraggio delle richieste di servizio.


<<delegate>>
RispostaHTTP HTTPResponse

<<client page>> <<server page>>


: PaginaHTML : PaginaServer
Page 197
Page 198 CAPITOLO 4. PROGETTAZIONE

viene trasmessa al WebServer, il quale delega l’elaborazione della richie-


sta al componente interno GestoreRichiesta che analizza la richiesta ri-
cevuta e la mappa su una RisorsaWeb, cioè su una PaginaHTML statica o su
una PaginaServer. Nel primo caso viene preparata immediatamente una
risposta contenente la PaginaHTML individuata, mentre nel secondo ca-
so il GestoreRichiesta cede il controllo al componente PaginaServer re-
sponsabile della costruzione della risposta. Nel caso in cui non sia possi-
bile mappare la richiesta su una RisorsaWeb il GestoreRichiesta prepara
una risposta contenente un messaggio di errore.
Per introdurre le funzionalità di autenticazione e autorizzazione ri-
chieste interveniamo a livello del componente GestoreRichiesta in mo-
do che esegua una sequenza di elaborazioni prima di cedere il controllo
alla PaginaServer applicativa. Ciascuna elaborazione è affidata a un sin-
golo componente specifico che si comporta da filtro (alla Unix) ignorando
completamente le attività svolte dai componenti che lo precedono e/o lo
seguono nella catena. La maggior parte delle piattaforme e tecnologie
oggi disponibili per la realizzazione di applicazioni web prevede nativa-
mente la possibilità di gestire una catena di operazioni di filtraggio e tra-
sformazione delle richieste HTTP che vengono attuate prima di cedere il
controllo alla RisorsaWeb applicativa. In particolare la specifica J2EE in-
troduce per questo scopo alcuni componenti denominati ServletFilter,
mentre in ambiente .NET si parla invece di HTTPHandler. Il meccanismo
di funzionamento generale è semplice.

• Il GestoreRichiesta costruisce la CatenaFiltri contenente l’insie-


me ordinato di filtri che dovranno essere attivati in sequenza per
elaborare la specifica RichiestaHTTP ricevuta. Un filtro è un com-
ponente che implementa l’interfaccia IFiltro illustrata nella Figu-
ra 4.30, le cui caratteristiche specifiche dipendono dalla specifica
tecnologia utilizzata.

• Ciascun filtro viene attivato dal GestoreRichiesta e svolge le pro-


prie attività in modo indipendente dagli altri che lo precedono e lo
seguono nella catena. Le attività svolte da un filtro possono essere di
vario tipo, quali ad esempio la scrittura di un file di log, l’inserimen-
to di informazioni specifiche nella Sessione HTTP associata all’u-
tente e gestita dal WebServer, la manipolazione della RichiestaHTTP
ricevuta in modo da forzare il mapping verso una RisorsaWeb di-
versa da quella che sarebbe selezionata dal GestoreRichiesta se
operasse sulla RichiestaHTTP originaria.
CAPITOLO 4. PROGETTAZIONE Page 199

• Quando l’ultimo filtro della catena ha terminato la propria esecuzio-


ne il GestoreRichiesta analizza la RichiestaHTTP finale e la map-
pa sulla RisorsaWeb opportuna, in accordo con il meccanismo di
funzionamento base precedentemente descritto.

Per introdurre le funzionalità di autenticazione e autorizzazione co-


struiamo una catena di filtri che comprende i seguenti tre componenti.

• GestoreProfiloServizioè il primo filtro della catena ed è respon-


sabile di recuperare il ProfiloServizio associato allo specifico ser-
vizio applicativo richiesto dall’utente. Il profilo è individuato in base
alle informazioni contenute nella RichiestaHTTP ricevuta dal servi-
ce provider. Una volta recuperato il ProfiloServizio estrae da que-
sto il ProfiloAutenticazione e il ProfiloAutorizzazione e li rende
accessibili ai componenti attivati successivamente.

• GestoreAutenticazione analizza il ProfiloAutenticazione e veri-


fica se l’utente corrente risulta già autenticato con una modalità com-
patibile con quella specificata nel ProfiloAutenticazione. In caso
positivo non svolge altra attività e termina la propria esecuzione, ce-
dendo il controllo al componente successivo della catena. Viceversa
viene attivata la procedura di autenticazione descritta in dettaglio
nei paragrafi successivi.

• GestoreAutorizzazione analizza il ProfiloAutorizzazione e veri-


fica se esiste per l’utente richiedente almeno un Accreditamento
compatibile con le tipologie di qualifica specificate nel ProfiloAu-
torizzazione. In caso positivo l’esecuzione termina con successo
e all’utente viene restituita la PaginaHTML associata alla originaria
richiesta di servizio inviata al ServiceProvider. In caso negativo
il filtro manipola la richiesta in modo da redirigere l’elaborazione
successiva verso una pagina di errore.

Accesso al profilo di autenticazione e autorizzazione di un servizio

Il primo passo dell’attività di controllo dell’accesso ai servizi è l’individua-


zione della tipologia di credenziali richieste per l’autenticazione e dell’in-
sieme di qualifiche abilitate all’accesso. Questa attività viene svolta dal
componente GestoreProfiloServizio, che è il primo componente della
catena di filtri a essere attivato dal GestoreRichiesta. Le entità in gioco
sono mostrate nel diagramma di struttura composita della Figura 4.31.
Page 200 CAPITOLO 4. PROGETTAZIONE

<<component>>
GestoreProfiloServizio

: ProfiloServizio
IFiltro

: ProfiloAutenticazione : ProfiloAutorizzazione

<<interface>>
IServiceProfileLoader
+ loadServiceProfile(serviceName: String ): ProfiloServizio

<<component>>
Archivio Profili di servizio

: ProfiloServizio

Figura 4.31: Entità coinvolte nell’accesso al profilo di un servizio.

sessioneUtente: HTTPSession : GestoreProfiloServizio : ArchivioProfiliDiServizio

loadServiceProfile(servizio) recupera profilo associato


al servizio richiesto

<<create>> profiloServizio: ProfiloServizio


profiloServizio

estrae ProfiloAutenticazione
da profiloServizio

salvaProfiloAutenticazione
estrae ProfiloAutorizzazione
da profiloServizio

salvaProfiloAutorizzazione

Figura 4.32: Estrazione del profilo di un servizio: comportamento dinamico.


CAPITOLO 4. PROGETTAZIONE Page 201

Per il livello di presentazione un Servizio è semplicemente una col-


lezione di risorse web univocamente individuate sulla base della struttura
dell’URL associato alla richiesta inviata dall’utente. Compito del Gestore-
ProfiloServizio è analizzare l’URL della richiesta, determinare il servi-
zio associato ed interagire con il componente ArchivioProfiliServizio
attraverso l’interfaccia IServiceProfileLoader ottenendo in risposta il
ProfiloServizio di interesse. Il GestoreProfiloServizio estrae dal Pro-
filoServizio ottenuto il ProfiloAutenticazione e il ProfiloAutorizza-
zione e li memorizza nella sessione utente in modo che componenti suc-
cessivi della catena possano accedervi. Il diagramma di sequenza della
Figura 4.32 illustra il comportamento dinamico descritto.

<<subsystem>>
ServiceProvider

<<component>>
IFiltro GestoreAutenticazione

RichiestaHTTP

<<use>> <<redirect>>
<<subsystem>>
<<redirect>> WebPresentationTier

RichiestaHTTP

ResponsoAutenticazione <<component>>
<<create>>
- autenticazioneOK: boolean GestoreIdentita
- tipoCredenziale: tCredenziale
<<component>>
<<create>>
WebBrowser
ProfiloRegistrazione
- nome <<client page>>
- cognome
PaginaLogin
- codiceFiscale
- ...
IGestoreProfiloRegistrazione

<<use>>
<<form>> Utente
<<component>>
FormLogin
ArchivioProfiliRegistrazione

Figura 4.33: Componenti coinvolti nel processo di autenticazione.

Il processo di autenticazione
Il componente GestoreAutenticazione è il secondo elemento della cate-
na di filtri che implementa il meccanismo di controllo degli accessi ai ser-
Page 202 CAPITOLO 4. PROGETTAZIONE

vizi. Tale componente recupera dalla sessione utente il ProfiloAutenti-


cazione precedentemente memorizzato dal GestoreProfiloServizio e
• verifica l’esistenza di una eventuale autenticazione preesistente per
l’utente e, in caso affermativo, verifica se l’autenticazione pregres-
sa è stata effettuata con una modalità compatibile con quella spe-
cificata nel ProfiloAutenticazione associato al servizio richiesto
dall’utente.
Le entità coinvolte sono mostrate nel diagramma dei componenti del-
la Figura 4.33. Per stabilire se l’utente risulta già autenticato il GestoreAu-
tenticazione verifica se nella sessione utente è presente un ResponsoAu-
tenticazione (Figura 4.33) che certifica il completamento con successo
da parte dell’utente di una precedente autenticazione. Nel caso in cui
risulti già autenticato, il ResponsoAutenticazione viene analizzato veri-
ficando se la tipologia di credenziali utilizzate per l’autenticazione pre-
cedente è compatibile o meno con i requisiti del servizio richiesto dal-
l’utente. Un’autenticazione pregressa viene considerata compatibile se la
tipologia di credenziali utilizzate ha una forza uguale o maggiore di quella
richiesta nel ProfiloAutenticazione associato al servizio. In altri termi-
ni, se il servizio richiede un’autenticazione con credenziali deboli verrà
considerata compatibile una qualsiasi autenticazione pregressa (comple-
tata presentando credenziali sia deboli, sia forti). Viceversa, se il servizio
richiede credenziali forti un’autenticazione preesistente verrà considera-
ta compatibile soltanto nel caso in cui sia stata completata dall’utente
presentando credenziali forti.
Nel caso in cui la verifica di compatibilità di un’autenticazione pre-
gressa vada a buon fine, il GestoreAutenticazione non svolge altra at-
tività e termina la propria esecuzione, cedendo il controllo al compo-
nente successivo della catena di filtri. Viceversa se la verifica non va a
buon fine oppure se non esiste un’autenticazione pregressa viene attiva-
ta la procedura di autenticazione che coinvolge l’utente e l’entità esterna
GestoreIdentita.
Il diagramma di sequenza della Figura 4.34 illustra il comportamento
dinamico appena descritto, mentre quello della Figura 4.35 illustra l’inte-
razione referenziata nel frame di interazione denominato autenticazione
nella Figura 4.34. Il dettaglio dei passi dello scenario “Autenticazione” è
descritto nel seguito.
Scenario: autenticazione.
Attori: ServiceProvider, GestoreIdentita.
CAPITOLO 4. PROGETTAZIONE Page 203

sessioneUtente: HTTPSession : GestoreAutenticazione : GestoreIdentita : GestoreRichiesta

attivazione filtro

recupera ProfiloAutenticazione associato


a richiesta di servizio

recupera ResponsoAutenticazione associato


ad autenticazione pregressa

opt

[esiste ResponsoAutenticazione associato ad autenticazione pregressa ]

verifica compatibilita
(responsoAutenticazione,
profiloAutenticazione)

alt
[non esiste ResponsoAutenticazione precedente oppure esiste ma non è compatibile con il
ProfiloAutenticazione del servizio]
ref
Autenticazione

[esiste ResponsoAutenticazione precedente compatibile con il ProfiloAutenticazione del servizio ]

termina con successo l'esecuzione del filtro

Figura 4.34: Scenario generale di autenticazione.


Page 204

sessioneUtente: HTTPSession GRA: RisorsaWeb serviceProvider: GestoreRichiesta : GestoreAutenticazione : WebBrowser : GestoreIdentita

redirect su servizio di Login del


GestoreIdentita(tipoCredenziali, URL di ritorno
del servizio GestioneResponsoAutenticazione)
redirect

di autenticazione.
ref
Login

redirect su servizio di
GestioneResponsoAutenticazione del
Service Provider

redirect

forward

verifica correttezza formale del


ResponsoAutenticazioneRicevuto

memorizza il ResponsoAutenticazione
nella sessione utente

Figura 4.35: Interazione fra ServiceProvider e GestoreIdentita nel processo


CAPITOLO 4. PROGETTAZIONE
CAPITOLO 4. PROGETTAZIONE Page 205

Precondizioni: il ServiceProvider ha ricevuto una richiesta di accesso


a un servizio da parte dell’utente e non esiste un’autenticazione pre-
gressa compatibile con i requisiti indicati nel ProfiloAutenticazione
associato al servizio richiesto.

Svolgimento:

• Il componente GestoreAutenticazione in esecuzione sul


ServiceProvider forza una redirezione verso il servizio di Lo-
gin erogato dal GestoreIdentita. L’URL specificato nella redi-
rect è ottenuto componendo l’URL per l’accesso al servizio di
Login, comprensivo di eventuali parametri, con un parametro
TARGET che individua l’URL del servizio di Gestione Respon-
so di Autenticazione (GRA) erogato dal ServiceProvider a cui
dovrà essere inviato il ResponsoAutenticazione. L’URL del ser-
vizio GRA contiene a sua volta un ulteriore parametro targe-
tservice che specifica l’URL completo della pagina del servizio
applicativo originariamente richiesta dall’utente. Il contenuto
del parametro TARGET sarà quindi del tipo:

TARGET=http://spHost:port/GRA?targetservice=http://sp
Host:port/servicePage?serviceParameters...

Ipotizzando che il servizio di Login con credenziali deboli sia


individuato dall’URL http://gestoreIdentitaHost:port/
login?authType=weak, l’URL completo utilizzato nella redirect
generata dal GestoreAutenticazione sarà quindi del tipo

http://gestoreIdentitaHost:port/login?authType=weak&
TARGET=...

• La richiesta di redirezione viene inviata come risposta al Browser


dell’utente, con il risultato di creare una nuova richiesta HTTP
verso il servizio di login del GestoreIdentita.
• Il GestoreIdentita richiede le credenziali all’utente, le verifi-
ca e costruisce un ResponsoAutenticazione con il risultato del-
la verifica. Queste operazioni sono dettagliate nello scenario
login referenziato nella Figura 4.35 attraverso l’utilizzo del fra-
me di interazione con lo stesso nome.
Page 206 CAPITOLO 4. PROGETTAZIONE

• Il GestoreIdentita forza una redirezione verso il servizio di Ge-


stione Responso di Autenticazione (GRA) erogato dal Service-
Provider utilizzando come URL per la redirezione il contenu-
to del parametro TARGET presente nella query della richiesta ri-
cevuta dal servizio di Login. Dato che si richiede di trasferire
il ResponsoAutenticazione al ServiceProvider, in questo ca-
so non si può operare come descritto nel primo passo dello
scenario, poiché la modalità di trasferimento di informazioni
nella query string inclusa nell’URL di redirezione non è adatta
al trasferimento di informazioni complesse e strutturate come
il ResponsoAutenticazione (peraltro esiste anche un limite al
numero massimo di caratteri che possono essere inclusi nella
query string). Il problema è comunque facilmente risolvibile e
proponiamo al lettore come esercizio la definizione di un mec-
canismo alternativo per il trasferimento di informazioni fra ap-
plicazioni web che appartengono a contesti diversi.
• La richiesta di redirezione viene inviata come risposta al Browser
dell’utente, con il risultato di creare una nuova richiesta HTTP
verso il servizio GRA del ServiceProvider (la richiesta viene
ricevuta dal GestoreRichiesta del ServiceProvider il quale
provvede ad attivare la RisorsaWeb associata al servizio GRA).
• Il servizio GRA verifica la correttezza formale del ResponsoAu-
tenticazione ricevuto e lo memorizza nella sessione utente
corrente del ServiceProvider.

Postcondizioni: la sessione utente mantenuta dal ServiceProvider con-


tiene il ResponsoAutenticazione trasferito dal GestoreIdentita.

L’ultimo scenario da dettagliare per completare la descrizione del pro-


cesso di autenticazione è lo scenario login referenziato nel frame di in-
terazione nella Figura 4.36. Il dettaglio dei passi dello scenario, limitata-
mente al caso di login con userName e password o con userName/password/PIN,
è descritto nel seguito.

Scenario: login.

Attori: Utente e GestoreIdentita.

Precondizioni:
il GestoreIdentita ha ricevuto una richiesta di accesso al servizio
: Utente : WebBrowser : GestoreIdentita servizioDiLogin: RisorsaWeb : ArchivioProfiliRegistrazione

forward con indicazione tipo credenziali


richieste
<<create>> <<client page>>
: PaginaLogin

pagina Login contenente LoginForm

inserimento credenziali

submit LoginForm

submit
forward
CAPITOLO 4. PROGETTAZIONE

check Credenziali(username, credenziali utente)

esito verifica

<<create>> : ResponsoAutenticazione

setAutenticazioneOK(esito verifica)

setTipoCredenziali(tipo credenziali)

preparazione del ResponsoAutenticazione.


opt
[esito verifica credenziali positivo ]
getProfiloRegistrazione(userName)

profilo registrazione utente

addProfiloRegistrazione(profilo registrazione utente)

Figura 4.36: Interazione tra Utente e GestoreIdentita durante la fase di login e


Page 207
Page 208 CAPITOLO 4. PROGETTAZIONE

di login associato a una delle tipologie di credenziali supportate dal


sistema.

Svolgimento:

• Il componente GestoreRichiesta del GestoreIdentita trasfe-


risce il controllo dell’elaborazione alla RisorsaWeb associata al
servizio di login richiesto.
• Il servizio di Login costruisce una PaginaLogin contenente una
form che verrà popolata dall’utente con le credenziali.
• Il servizio di Login invia al WebBrowser la PaginaLogin.
• L’utente inserisce le credenziali richieste e invia la LoginForm al
servizio di Login del GestoreIdentita.
• Il servizio di Login interagisce con l’ArchivioProfiliRegistra-
zione invocando il metodo checkCredenziali() dell’interfac-
cia IGestoreProfiloRegistrazione passando come parame-
tro lo userName dell’utente e le credenziali inserite nella LoginForm.
L’ArchivioProfiliRegistrazione effettua il controllo delle cre-
denziali associate allo userName e restituisce al servizio di Login
un valore booleano che rappresenta l’esito della verifica.
• Il servizio di Login crea un ResponsoAutenticazione contenen-
te l’esito della verifica delle credenziali e l’indicazione del tipo
di credenziali utilizzate per l’autenticazione.

Svolgimento opzionale: eseguito solo nel caso di esito positivo della


verifica delle credenziali.

• Il servizio di Login richiede all’ArchivioProfiliRegistrazione


il ProfiloRegistrazione dell’utente autenticato invocando il
metodo getProfiloRegistrazione() definito nell’interfaccia
IGestoreProfiloRegistrazione.
• Il servizio di Login inserisce nel ResponsoAutenticazione il pro-
filo di registrazione dell’utente autenticato.

Postcondizioni: il GestoreIdentita ha preparato il ResponsoAutentica-


zione contenente il risultato della procedura di login e, solo in caso
di verifica positiva delle credenziali, il ProfiloRegistrazione del-
l’utente autenticato.
CAPITOLO 4. PROGETTAZIONE Page 209

<<component>>
GestoreAutorizzazione

IFiltro : Accreditamento

: ProfiloRegistrazione : ProfiloAutorizzazione

<<interface>>
IAccreditamento
+ getAccreditamenti(userName: String ): Accreditamento"[]"

<<component>>
ArchivioAccreditamenti

: Accreditamento

Figura 4.37: Componenti coinvolti nel processo di autorizzazione.

Autorizzazione all’accesso a un servizio

L’ultimo passo dell’attività di controllo dell’accesso ai servizi è il processo


di autorizzazione, cioè la verifica del possesso da parte dell’utente auten-
ticato di almeno una Qualifica tra quelle abilitate all’accesso ed elenca-
te nel ProfiloAutorizzazione associato al servizio richiesto dall’utente.
Questa attività viene svolta dal componente GestoreAutorizzazione, che
è l’ultimo componente della catena di filtri a essere attivato dal GestoreRi-
chieste. Le entità coinvolte sono mostrate nel diagramma di struttura
composita della Figura 4.37.
Responsabilità del GestoreAutorizzazione è recuperare dalla sessio-
ne utente il ProfiloAutorizzazione associato al servizio richiesto e ri-
chiedere al componente ArchivioAccreditamenti, attraverso l’interfac-
cia IAccreditamento, l’elenco degli accreditamenti dell’utente autentica-
to (identificato univocamente attraverso il suo userName, il cui Profilo-
Registrazione è stato memorizzato nella sessione utente dal preceden-
te filtro della catena). Una volta ottenuto l’insieme degli accreditamenti
dell’utente autenticato il componente è in grado di verificare l’esistenza
o meno di una qualifica compatibile con l’insieme di qualifiche abilita-
te all’accesso al servizio. In caso positivo l’esecuzione termina con suc-
cesso e all’utente viene consentito l’accesso alla risorsa associata alla ori-
ginaria richiesta di servizio inviata al ServiceProvider. In caso negati-
vo il GestoreAutorizzazione manipola la richiesta in modo da redirige-
Page 210

Sessione utente: HTTPSession : GestoreAutorizzazione : GestoreRichiesta : ProfiloAutorizzazione : Archivio Accreditamenti

recupera ProfiloAutorizzazione
associato al servizio richiesto

recupera ProfiloRegistrazione associato


a utente autenticato
recupera userName utente
autenticato da ProfiloRegistrazione

getAccreditamenti(userName)

Accreditamento[]

estrai da profilo autorizzazione l'elenco delle


qualifiche abilitate all'accesso

alt
[esiste un accreditamento compatibile con
l'elenco qualifiche abilitate all'accesso ]
consenti l'accesso alla risorsa richiesta dall'utente

[non esiste un accreditamento compatibile ]

richiedi presentazione pagina di errore

Figura 4.38: Autorizzazione all’accesso: comportamento dinamico.


CAPITOLO 4. PROGETTAZIONE
CAPITOLO 4. PROGETTAZIONE Page 211

re l’elaborazione successiva, effettuata dal GestoreRichiesta, verso una


pagina di errore. Il diagramma di sequenza della Figura 4.38 illustra il
comportamento dinamico descritto.
Nella trattazione svolta finora abbiamo affrontato molti problemi che
si incontrano nella progettazione di applicazioni web e a questo punto il
progetto può considerarsi ragionevolmente concluso. Nel seguito elen-
chiamo alcuni degli aspetti che devono ancora essere affrontati e risolti
prima di passare all’implementazione dei componenti del sistema (e che
per esigenze di spazio non abbiamo la possibilità di approfondire).

• La prima scelta da effettuare è relativa alla tecnologia di riferimento


(J2EE, .NET, PHP, ecc.) per l’implementazione dei diversi compo-
nenti del sistema. Finora infatti la trattazione svolta è sostanzial-
mente indipendente dalla specifica tecnologia di riferimento. I re-
quisiti non vincolano all’utilizzo della stessa tecnologia per la rea-
lizzazione di tutti i componenti del sistema. Sarà quindi possibile,
ad esempio, realizzare il ServiceProvider con tecnologia J2EE e il
GestoreIdentita in ambiente .NET.

• Una seconda scelta da effettuare è relativa alla definizione del for-


mato “fisico” per il ProfiloServizio (a cui si accede dal filtro Gesto-
reProfiloServizio) e per il ResponsoAutenticazione inviato dal
GestoreIdentita al ServiceProvider. A questo proposito è possi-
bile ricorrere al linguaggio XML (Extensible Markup Language), defi-
nendo un opportuno DTD (Document Type Definition) o uno Sche-
ma XML per i due profili. A titolo di esempio riportiamo una possi-
bile rappresentazione in XML per il profilo di un servizio:

<?xml version="1.0" encoding="UTF-8"?>


<profilo-servizio>
<servizio nome="servizi.servizio1">
<profilo-autenticazione>
<!-- Tipo di credenziali richieste dal servizio
per l’autenticazione:
valori possibili=deboli,forti -->
<tipoCredenziali>deboli</tipoCredenziali>
</profilo-autenticazione>
<profilo-autorizzazione>
<!-- Il caso della qualifica utenteRegistrato
viene trattato separatamente dalle altre qualifiche -->
<accessoUtenteRegistrato enabled="true"/>
<!-- Elenco delle qualifiche abilitate all’accesso -->
<qualificheAbilitate all="false">
Page 212 CAPITOLO 4. PROGETTAZIONE

<qualificaAbilitata>amministratoreSistema
</qualificaAbilitata>
<qualificaAbilitata>operatoreSistema
</qualificaAbilitata>
</qualificheAbilitate>
</profilo-autorizzazione>
</servizio>
</profilo-servizio>

• Occorre anche definire la modalità di realizzazione e di dispiega-


mento per i componenti ArchivioProfiliRegistrazione e Archi-
vioAccreditamenti. Una scelta naturale per la memorizzazione per-
sistente dei profili di registrazione e degli accreditamenti degli uten-
ti consiste nell’utilizzare una base dati SQL. I componenti Archivio-
ProfiliRegistrazione e ArchivioAccreditamenti dovranno in que-
sto caso gestire l’interfacciamento con la base dati utilizzando un
protocollo opportuno (ad esempio JDBC nel caso di scelta della tec-
nologia Java).

• Sempre con riferimento all’interazione con i componenti Archivio


ProfiliRegistrazione e ArchivioAccreditamenti è necessario ope-
rare una scelta relativamente alla distribuzione sui nodi del sistema
complessivo. Il caso più semplice è quello in cui tali componenti
sono accessibili localmente da parte dei rispettivi client: i metodi
invocati sulle interfacce esposte dai componenti si tradurranno ba-
nalmente in chiamate locali. Un caso più interessante è quello che
prevede la possibilità di distribuzione di questi componenti su nodi
remoti rispetto ai componenti client: occorre scegliere il protocollo
di comunicazione che dovrà essere utilizzato per l’interazione con
le interfacce esposte dai componenti. Una scelta possibile, valida
però soltanto su piattaforma Java, consiste nell’utilizzo del proto-
collo Java RMI. Una scelta alternativa può essere quella di espor-
re le interfacce dei componenti ArchivioProfiliRegistrazione e
ArchivioAccreditamenti come Web Service, cui i client accedono
utilizzando il protocollo di comunicazione SOAP (Simple Object Ac-
cess Protocol), ottenendo con questa scelta l’indubbio vantaggio di
poter disaccoppiare la tecnologia realizzativa lato client e lato ser-
ver.

Tenendo presenti queste considerazioni, per concludere l’esempio mo-


striamo nella Figura 4.39 una possibile vista di deployment del sistema,
CAPITOLO 4. PROGETTAZIONE Page 213

valida nell’ipotesi di utilizzo di tecnologia Java in tutti i nodi e di esposi-


zione delle interfacce dei componenti ArchivioProfiliRegistrazione e
ArchivioAccreditamenti come web service.
I nodi e gli artifact presentati nel diagramma di deployment sono de-
scritti nel seguito.

• PCUtente ospita il WebBrowser attraverso il quale l’utente interagisce


con gli altri nodi del sistema. Comunica con i nodi ServiceProvider
e GestoreIdentita attraverso il protocollo HTTP.

• ServiceProvider ospita gli artifact associati ai servizi applicativi de-


stinati agli utenti finali (intesi come collezione di pagine server e
di pagine HTML statiche) e gli artifact che rappresentano i profili
di servizio corrispondenti (se si utilizza una rappresentazione XML
su file system analoga a quella sopra descritta ci sarà un file XML
per ogni servizio applicativo). Il nodo ServiceProvider ospita an-
che gli artifact che realizzano i filtri descritti nelle sezioni preceden-
ti. Il nodo ServiceProvider comunica direttamente con il nodo
GestoreAccreditamenti utilizzando il protocollo SOAP durante la
fase di autorizzazione all’accesso ad un servizio.

• GestoreIdentita ospita gli artifact associati al servizio di registra-


zione e al servizio di login cui ha avuto accesso l’utente durante il
processo di autenticazione. Comunica con il nodo GestoreProfili-
Registrazione utilizzando il protocollo SOAP durante la fase di ve-
rifica delle credenziali dell’utente.

• GestoreAccreditamenti ospita il web service che espone le funzio-


nalità di accesso agli accreditamenti degli utenti. Il web service
GestioneAccreditamenti comunica con la base dati DBAccreditamenti
attraverso il protocollo JDBC.

• GestoreProfiliRegistrazione ospita il web service che espone le


funzionalità di accesso ai profili di registrazione degli utenti (oltre
alle eventuali funzionalità aggiuntive di inserimento e modifica dei
profili di registrazione). Il web service GestioneRegistrazione co-
munica con la base dati DBProfiliRegistrazione attraverso il pro-
tocollo JDBC.
: GestoreProfiliRegistrazione
Page 214

: GestoreIdentita
<<execution environment>>
: J2EEApplicationServer
<<execution environment>>
: J2EEApplicationServer
: WebServiceGestioneProfiliRegistrazione
SOAP
login: ServizioWeb

registrazione: ServizioWeb <<manifest>> JDBC

HTTP <<component>> : DBServer


ArchivioProfiliRegistrazione

: PCUtente : DBProfiliRegistrazione
<<artifact>>
ServizioWeb <<component>>

Figura 4.39: Vista di deployment.


ArchivioAccreditamenti
: WebBrowser
<<manifest>>

HTTP : GestoreAccreditamenti
: ServiceProvider
<<execution environment>>
<<execution environment>> : J2EEApplicationServer
: J2EEApplicationServer
: WebServiceGestioneAccreditamenti
ps1: ProfiloServizio
SOAP
: FiltroGestioneProfiliServizio.class
s1: ServizioWeb
JDBC
Utente : FiltroAutenticazione.class
psn: ProfiloServizio
: FiltroAutorizzazione.class <<device>>
sn: ServizioWeb
: DBServer

<<deploy>>
: DBAccreditamenti
<<artifact>> <<deploy>>
ProfiloServizio
<<manifest>> ProfiloServizio
1
<<artifact>>
Servizio ServizioWeb
<<manifest>>

<<artifact>> <<artifact>>
PaginaServer PaginaHTML
CAPITOLO 4. PROGETTAZIONE
CAPITOLO 4. PROGETTAZIONE Page 215

4.3 Esercizi
Esercizio 1
Il modello concettuale proposto all’inizio del capitolo per il sistema chat
incorpora al suo interno scelte architetturali che dovrebbero essere evita-
te nella modellazione concettuale. Definite per il sistema chat un model-
lo concettuale “neutro”, che non contenga al suo interno elementi del-
la soluzione del problema (ovvero che non anticipi scelte che sono di
responsabilità della fase di progetto del sistema).

Esercizio 2
In RMI le classi che implementano oggetti remoti possono generare ecce-
zioni di tipo RemoteException. Esplicitate l’esistenza di RemoteException
ed evidenziate le relazioni tra le classi e l’eccezione nel diagramma ripor-
tato nella Figura 4.21.

Esercizio 3
Descrivete in dettaglio utilizzando i diagrammi UML ritenuti più oppor-
tuni (communication, activity, deployment) il meccanismo di download
dinamico delle classi stub dal registry RMI ai client del sistema chat.

Esercizio 4
Estendete il progetto architetturale del sistema di controllo degli accessi
per applicazioni web in modo da supportare la possibilità di utilizzo di
diverse tipologie di contenitori “fisici” (basi dati SQL, directory LDAP —
Lightweight Directory Access Protocol) per i diversi archivi definiti nel pro-
getto (ArchivioAccreditamenti, ArchivioProfiliServizio, ArchivioPro-
filiRegistrazione). Suggerimento: utilizzate il design pattern Adapter [GH-
JV95] nel progetto di dettaglio dei componenti che implementano le in-
terfacce esposte dai diversi archivi.

Esercizio 5
L’utilizzo di parametri passati nella query string associata a un URL è un
meccanismo comunemente utilizzato per trasferire informazioni a un’ap-
plicazione web, ma presenta limitazioni sul tipo dei parametri che posso-
no essere passati (soltanto stringhe) e sulla quantità complessiva di infor-
Page 216 CAPITOLO 4. PROGETTAZIONE

mazioni che possono essere inserite nella query string. Descrivete in det-
taglio con i diagrammi UML ritenuti più idonei un meccanismo alternati-
vo per il trasferimento di informazioni fra applicazioni web che apparten-
gono a contesti diversi adatto al trasferimento di informazioni complesse
e strutturate.

Esercizio 6
Estendete il progetto architetturale del sistema di controllo degli acces-
si per applicazioni web in modo da prevedere la possibilità di interazio-
ne con diversi Gestori dell’identità, ciascuno dei quali mantiene il profilo
di una parte degli utenti del sistema. Modellate in particolare un servi-
zio WAYF (Where are you from?), erogato dal Service Provider. Tale servi-
zio è responsabile di richiedere all’utente qual’è il Gestore di identità che
mantiene il suo profilo di registrazione (identificato da un nome univoco)
e successivamente di redirigere l’utente stesso verso il servizio Login da
questo erogato per completare la procedura di autenticazione.
Page 217

Capitolo 5

Battaglia navale in rete

In questo capitolo esemplifichiamo lo sviluppo di un’applicazione di di-


mensioni e complessità non banali, ancorché sufficientemente limitate
da consentire una trattazione soddisfacente nello spazio di poche pagine.
Affrontiamo tutte le fasi dello sviluppo e descriviamo i risultati prodotti da
ciascuna fase, con particolare attenzione all’analisi e alla progettazione.
Il capitolo è organizzato in quattro parti: nella prima diamo le speci-
fiche informali del sistema da realizzare; nella seconda rappresentiamo
i requisiti; nella terza parte progettiamo l’applicazione e nella quarta ne
illustriamo l’implementazione in Java.

5.1 Regole del gioco


L’applicazione deve consentire ai giocatori di cimentarsi nella classica bat-
taglia navale. La novità sostanziale rispetto al gioco su carta consiste nel
fatto che i giocatori interagiscono attraverso dei calcolatori collegati alla
rete.
Il gioco si svolge su una scacchiera quadrata, di dimensioni variabili,
che dovranno essere stabilite all’inizio di ciascuna partita. La dimensione
minima è 8 × 8, quella massima 20 × 20. Il quadrante di gioco è una
matrice quadrata, dove ciascuna casella è individuata dal numero di riga
e di colonna. Se non specificato altrimenti, il quadrante di gioco consta di
dieci righe e altrettante colonne.
Al gioco partecipano due giocatori. È anche possibile il gioco da parte
di un unico giocatore che si cimenta contro il calcolatore. Ogni giocato-
re dispone di una flotta la cui composizione può essere stabilita all’inizio
della partita. Ogni nave è caratterizzata dal numero di caselle che occu-
Page 218 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

pa sul quadrante. Per comodità è possibile riferirsi alle navi con un no-
me significativo, nel modo seguente: (a) i sottomarini occupano una sola
casella; (b) i cacciatorpedinieri occupano due caselle; (c) gli incrociato-
ri occupano tre caselle; (d) le corazzate occupano quattro caselle; (e) le
portaerei occupano cinque caselle.
Se non specificato esplicitamente, ogni giocatore dispone di una por-
taerei, una corazzata, due incrociatori, tre cacciatorpedinieri e quattro
sommergibili.
Ogni nave occupa un numero di caselle adiacenti pari alla dimensione
della nave (due caselle sono adiacenti quando hanno in comune un lato).
Le caselle occupate devono trovarsi sulla stessa riga o sulla stessa colon-
na. La stessa posizione del quadrante non può essere occupata da più
navi. Ogni giocatore dispone di un proprio quadrante, distinto da quello
dell’altro giocatore. All’inizio della partita ciascun giocatore dispone sul
proprio quadrante le proprie navi.
Scopo è l’affondamento della flotta avversaria. Il gioco procede attra-
verso una sequenza di turni. A ogni turno i giocatori indicano una casella
del quadrante avversario su cui sparare, e vengono informati di quale ca-
sella del proprio quadrante sia stata colpita. Il sistema indica se ciascun
colpo è andato a vuoto o se ha centrato una nave.
L’interazione col sistema avviene attraverso la rappresentazione gra-
fica dei quadranti. Le modalità precise di rappresentazione sono lasciate
allo sviluppatore, con l’indicazione generale che la rappresentazione de-
ve essere intuitiva e sostanzialmente simile a quella impiegata nel gioco
su carta.
Quando la casella su cui si è sparato è occupata da una nave, que-
sta viene danneggiata. Una nave affonda quando tutte la caselle da essa
occupate sono state colpite.
Alla fine di un turno un giocatore risulta vincitore se ha ancora almeno
una nave non affondata, mentre le navi dell’altro giocatore sono state tut-
te affondate. Può anche succedere che i giocatori finiscano di affondare
le rispettive flotte nello stesso turno. In tal caso la partita è pari.
Affinché una partita possa iniziare, i giocatori, che potenzialmente
non si conoscono, si devono “trovare” e accettare come partner. A que-
sto scopo, quando un utente attiva la sua applicazione, diventa visibile
agli altri utenti come disponibile. Allo stesso tempo, vede l’elenco degli
altri utenti disponibili. Ciascun utente disponibile può inviare o riceve-
re richieste di iniziare una partita. Quando una richiesta viene accettata,
i due utenti coinvolti cessano di essere disponibili e iniziano le fasi pre-
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 219

liminari della partita (scelta del numero di navi e delle dimensioni della
flotta).

5.2 Requisiti
Iniziamo dai diagrammi dei casi d’uso che illustrano le modalità di in-
terazione degli utenti col sistema. Per comodità, produciamo due dia-
grammi dei casi d’uso. Il primo è relativo alla fase iniziale —largamente
indipendente dal tipo di gioco— in cui l’utente cerca un partner sulla re-
te, lo trova e prende accordi per la partita. Il secondo riguarda le fasi di
gioco vere proprie. Il diagramma dei casi d’uso che illustra le fasi iniziali
è rappresentato nella Figura 5.1.

Connessione
VisualizzaDisponibili

<<include>> RicezioneRichiesta
Definizione
Partner
Giocatore
<<include>>
InvioRichiesta
<<include>>

ConcordaPartita
RispostaARichiesta

Figura 5.1: Diagramma dei casi d’uso relativo alla fase iniziale.

Il giocatore ha a disposizione un certo numero di funzionalità che gli


consentono di iniziare una partita. Con Connessione, egli si connette alla
rete degli altri giocatori. In questo modo diventa visibile agli altri e vice-
versa gli altri giocatori connessi diventano a loro volta visibili. Visualizza-
Disponibili permette al giocatore di visualizzare la lista degli altri gioca-
tori che sono connessi e disponibili a iniziare una partita. DefinizionePartner
è il caso d’uso più articolato dei quattro. Infatti, per scegliere un partner il
giocatore può agire in diversi modi: può limitarsi ad accettare una richie-
sta proveniente da un altro giocatore oppure può lui stesso mandare una
richiesta e attendere una risposta positiva. In caso di risposte negative
Page 220 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

queste azioni possono essere ripetute più volte, finché un’offerta non vie-
ne accettata. ConcordaPartita racchiude il dialogo tra giocatori che porta
a decidere le dimensioni del quadrante di gioco e delle navi da utilizzare.
Da notare che, benché i giocatori coinvolti possano essere molti, il
caso d’uso riporta la presenza di un solo attore. Questo corrisponde al-
l’interpretazione dell’attore come ruolo, che può essere ricoperto da più
istanze. Lo stesso discorso si applica alla Figura 5.2, che rappresenta il dia-
gramma dei casi d’uso che illustra le fasi di gioco, dove i giocatori coinvolti
saranno generalmente due.

VisualizzaProprioQuadrante

<<include>>

PosizionaNave

Spara
Giocatore

VisualizzaQuadranteAvversario

Figura 5.2: Diagramma dei casi d’uso relativo alle fasi di gioco.

All’inizio della partita PosizionaNave consente a ciascun giocatore di


posizionare le proprie navi sul proprio quadrante. Il posizionamento del-
le navi richiede che sia visibile il quadrante su cui le navi vengono disloca-
te, pertanto il caso d’uso PosizionaNave include il caso d’uso Visualizza-
ProprioQuadrante. Una volta iniziata la fase di combattimento, il posi-
zionamento non sarà più disponibile. Con VisualizzaProprioQuadran-
te, ciascun giocatore in ogni istante può visualizzare il proprio quadran-
te, su cui compaiono le proprie navi con le indicazioni dei danni subi-
ti. In realtà il quadrante dovrebbe essere mostrato con continuità, e ag-
giornato automaticamente. VisualizzaQuadranteAvversario visualizza
il quadrante dell’avversario, su cui compaiono le posizioni dei colpi spa-
rati, ciascuna con l’indicazione se il colpo è andato a segno o no. An-
che questo quadrante dovrebbe essere mostrato con continuità, e aggior-
nato automaticamente. Spara consente al giocatore di sparare un colpo
nel quadrante avversario, specificando le coordinate che intende colpi-
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 221

re. Questa opzione è disponibile solo quando il sistema ha reso effettivi


—visualizzandoli sui rispettivi quadranti— uno stesso numero di colpi da
parte di entrambi i giocatori. In altre parole, se un giocatore ha appe-
na sparato l’i-esimo colpo, dovrà aspettare che tale colpo sia stato preso
in considerazione, insieme con l’i-esimo colpo dell’avversario, prima di
poter sparare l’i+1-esimo.

5.2.1 Dominio del problema


Il diagramma delle classi che rappresenta il dominio del problema è rap-
presentato nella Figura 5.3. Notiamo che le classi del dominio del proble-
ma sono racchiuse nel package campoBattaglia.

campoBattaglia

Quadrante Nave
1 + flotta
- numRighe: int - dimensioni: int
- numColonne: int - colpiSubiti: int
+ quadrante *
+ affondata(): boolean
+ reset(): void
+ aggiungiNave(dim: Integer, iniz: Coordinata, fine: Cooordinata): void
+ sparo(c: Coordinata): boolean
+ spostaNave(n: Nave, iniz: Coordinata, fine: Coordinata): void
+ flottaAffondata(): boolean
+ completo(): boolean
Posizione
+ colpito(c: Coordinata): boolean
- inizio: Coordinata
- fine: Coordinata
*
+ conflitto(p: Posizione): boolean
+ interessa(c: Coordinata): boolean
Coordinata + interessa(riga: int, col: int): boolean
Colpo + conflitto(iniz: Coordinata, fine: Coordinata): boolean
- riga: int
- casella: Coordinata - colonna: int
+ make(c: Coordinata): void

Figura 5.3: Diagramma delle classi della battaglia navale.

La classe Quadrante rappresenta il campo di gioco. Non è necessario


distinguere tra i due quadranti di cui ciascun giocatore dispone. Questa
classe rappresenta sia il quadrante del giocatore che quello dell’avversa-
rio.
Gli attributi numRighe e numColonne indicano le dimensioni del qua-
drante. Il vincolo che il quadrante debba essere quadrato può essere espres-
so a parte. Infatti rappresentare separatamente il numero di righe e di
colonne ci permetterà facilmente —se fosse il caso— di rappresentare
campi di gioco rettangolari, semplicemente eliminando il vincolo.
I metodi di cui è dotata la classe hanno un significato abbastanza ov-
vio. reset() “svuota” il quadrante da navi e colpi subiti, riportandolo alle
Page 222 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

condizioni iniziali. aggiungiNave() crea una nave delle dimensioni indi-


cate e la posiziona alle coordinate indicate. spostaNave() cambia la po-
sizione occupata da una nave esistente, portandola nella posizione indi-
cata. sparo() crea un colpo nella posizione indicata. flottaAffondata()
indica se la flotta dislocata nel quadrante è stata completamente affonda-
ta. completo() indica se sul quadrante sono state dislocate esattamente
le navi previste per la partita. colpito indica se sul quadrante è dislocata
una nave alla coordinata indicata.
La classe Coordinata indica una posizione in un quadrante, in termini
di numero di riga e di colonna. È una classe “di servizio”, che permette ad
altre classi di avere attributi e argomenti di metodi di questo tipo.
La classe Nave rappresenta le navi di cui ciascun giocatore dispone sul
campo di gioco. Ciascuna nave è caratterizzata dalle proprie dimensio-
ni (specificate dall’attributo dimensioni). L’attributo colpiSubiti indica
naturalmente il numero di colpi che hanno centrato la nave, ovvero il nu-
mero di colpi sparati in caselle del quadrante occupate dalla nave. Tale at-
tributo è derivato, essendo determinabile dal confronto della Posizione
della nave con le coordinate delle istanze di Colpo sparate nel Quadrante
occupato dalla nave stessa. Il metodo affondata() indica se il numero di
colpi subiti è uguale alla dimensione della nave.
L’associazione Nave-Quadrante rappresenta la collocazione di una na-
ve sul quadrante. La relazione è uno a molti: ogni nave è collocata esat-
tamente su un quadrante, che viceversa può ospitare più navi. La clas-
se di associazione Posizione indica dove ciascuna nave si trova nel qua-
drante. La posizione è indicata mediante le coordinate della prua e della
poppa della nave (attributi inizio e fine, ripettivamente). Per facilitare
la specifica dei requisiti, imponiamo che fine abbia numero di riga e/o
colonna maggiore di inizio, cosa che non implica alcuna restrizione al
posizionamento delle navi nel quadrante.
La classe Colpo rappresenta i colpi subiti da un quadrante. Ogni colpo
è caratterizzato dalla casella colpita (attributo casella). Non ci possono
essere più colpi che interessano la medesima casella.
Notiamo che —come generalmente accade— sarebbe stato possibile
rappresentare il dominio del problema mediante una scelta di classi e as-
sociazioni diverse da quella illustrata nella Figura 5.3. Ad esempio, un’or-
ganizzazione alternativa potrebbe descrivere il quadrante come una com-
posizione di caselle, con la conseguenza che navi e colpi sarebbero col-
legabili direttamente alla caselle interessate. Coppie di caselle adiacenti
potrebbero essere collegate da un’opportuna associazione.
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 223

Tale organizzazione del modello del dominio del problema potrebbe


semplificare la specifica di alcuni aspetti e complicare la rappresentazio-
ne di altri. Lasciamo al lettore interessato l’esercizio di provare a descri-
vere compiutamente il sistema con questo tipo di organizzazione, e di va-
lutare se la specifica risulti più o meno complessa rispetto a quella qui
illustrata.

5.2.2 Responsabilità del sistema


Per completare la descrizione dei requisiti utente occorre definire le re-
sponsabilità del sistema (cioè in sostanza cosa il sistema debba fare nel
dominio del problema). È abbastanza evidente che il sistema Battaglia
Navale è composto da tre parti fondamentali.
• Il campo di battaglia è già descritto mediante il package campoBattaglia.
• L’interfaccia utente fornisce ai giocatori la visione corretta dei rispet-
tivi quadranti, accetta i comandi e li applica al campo di battaglia.
Occorre descrivere l’interfaccia utente, perché nel caso della batta-
glia navale essa è molto rilevante, essendo il gioco stesso basato sul-
la differenza tra ciò che i giocatori conoscono del proprio quadrante
di gioco rispetto a ciò che sanno del quadrante avversario.
• Il controllore del gioco deve assicurare che le regole del gioco siano
rispettate. I suoi compiti sono: verificare l’applicabilità dei coman-
di dati attraverso l’interfaccia utente e gestire i turni, impedendo a
un giocatore di effettuare lo sparo n+1-esimo prima che l’avversario
abbia effettuato l’n-esimo.
Queste nozioni sono modellate nella Figura 5.4, dove compare il nuovo
package interfaccia, che racchiude appunto gli elementi dell’interfaccia
utente, e la classe Partita, che svolge il ruolo di controllore del gioco.
La classe InterfacciaUtente riceve comandi dal giocatore e direttive
dalla classe Partita. Queste ultime riguardano l’abilitazione e la disa-
bilitazione di comandi, pertanto al giocatore verrà messo a disposizione
un insieme di comandi variabile, la cui composizione dipende dalla si-
tuazione. Quando la classe InterfacciaUtente riceve dal giocatore un
comando abilitato deve innanzitutto verificare che esso sia legale. Ipo-
tizziamo che l’applicabilità dei comandi possa essere garantita semplice-
mente attraverso il rispetto delle precondizioni associate ai metodi che
eseguono i comandi stessi. La classe InterfacciaUtente si rivolge al-
la classe Partita, che contiene metodi corrispondenti alle precondizioni
Page 224

Giocatore

Partita interfaccia
- maxNave: int[]
+ maxNavi(dim: int): int + IU 2
InterfacciaUtente VistaQuadProprio
+ sparo(c: Coordinata): void {ordered}
+ preSparo(c: Coordinata): void - sparoAbilitato: boolean
- aggiuntaAbilitata: boolean + visualizza(): void
- movimentoAbilitato: boolean + reset(): void
{ordered} 2 + quad + abilitaSparo(): void
+ disabilitaSparo(): void
Quadrante + quadAvvers + IUAvvers + abilitaSpostamenti(): void Casella
(from campoBattaglia) *
+ disabilitaSpostamenti(): void - coord: Coordinata
- numRighe: int + abilitaAggiunta(): void - colpita: boolean
- numColonne: int + disabilitaAggiunta(): void CasellaProprio
- occupata: boolean
+ reset(): void + proprioQuad + propriaIU + sparo(c: Coordinata): void
+ aggiungiNave(dim: Integer, iniz: Coordinata, fine: Coordinata): void + visualizzaQuadProprio(): void
+ sparo(c: Coordinata): boolean + visualizzaQuadAvvers(): void
+ spostaNave(n: Nave, iniz: Coordinata, fine: Coordinata): void + messaggio(t: String): void
+ flottaAffondata(): boolean
+ completo(): boolean
+ colpito(c: Coordinata): boolean VistaQuadAvversario CasellaAvversario

+ visualizza(): void *
+ reset(): void

Figura 5.4: Relazioni tra InterfacciaUtente, Partita e Quadrante.


CAPITOLO 5. BATTAGLIA NAVALE IN RETE
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 225

dei comandi (nella Figura 5.4 sono riportati solo il comando sparo() e la
relativa precondizione preSparo()). La classe Partita verifica che la pre-
condizione sia vera. Se è cosı̀, l’interfaccia utente manda il comando alla
Partita, che reagisce con due azioni: rimanda il comando al quadrante
approppriato per l’esecuzione ed eventualmente cambia le abilitazioni.
Dai quadranti l’interfaccia utente riceve comandi di visualizzazione, che
delega alle sue componenti VistaQuadProprio e VistaQuadAvversario.
La classe InterfacciaUtente è anche dotata di un metodo messaggio()
che serve a visualizzare messaggi per il giocatore: ad esempio l’invoca-
zione di un comando illegale provoca l’emissione di un messaggio, cosı̀
come la conclusione della partita.

InDefinizione

schieramentiCompletati

InCorso

sparoGiocatore1
/IU1.quadProprio.sparo(c); IU1.disabilitaSparo()

AttesaSpari AttesaSparoGioc2
entry/IU1.abilitaSparo(); IU2.abilitaSparo() sparoGiocatore2/IU2.quadProprio.sparo(c)

sparoGiocatore1/IU1.quadProprio.sparo(c)

AttesaSparoGioc1
sparoGiocatore2
/IU2.quadProprio.sparo(c); IU2.disabilitaSparo()

[IU1.quadAvvers.flottaAffondata() or
IU1.quadProprio.flottaAffondata()]

Conclusa

Figura 5.5: Diagramma degli stati della classe Partita.

La classe Partita introduce l’attributo maxNavi, che indica quante na-


vi per ciascuna tipologia devono essere schierate nel quadrante. Il meto-
do omonimo consente di conoscere quante navi della dimensione spe-
cificata devono essere schierate. Inoltre l’istanza di partita è associata ai
due quadranti dei giocatori e alle due interfacce.
Di alcuni compiti di controllo della classe Partita abbiamo già parlato
Page 226 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

sopra; possiamo quindi descrivere la gestione dei turni. Le attività di con-


trollo della classe Partita possono essere espresse facilmente mediante
il diagramma degli stati della classe stessa, riportato nella Figura 5.5.
Il diagramma degli stati della classe Partita è caratterizzato da due
stati principali: InDefinizione, corrispondente alle fasi preliminari, in
cui si dispongono le navi, e InCorso, corrispondente alla fase di gioco ve-
ra e propria, in cui si spara. Lo stato InCorso è composito: i suoi sotto-
stati servono a regolare gli spari dei due giocatori. Inizalmente ci si tro-
va nello stato AttesaSpari, da cui si prende in considerazione uno sparo
allo volta: quando un giocatore ha sparato si istruisce la sua interfaccia
a non accettare altri spari e si passa in uno stato (AttesaSparoGioc1 o
AttesaSparoGioc2, a seconda dei casi) in cui si attende lo sparo dell’al-
tro giocatore; quando questo avviene, si riabilita l’interfaccia precedente-
mente disabilitata e si ritorna ad AttesaSpari.
Il diagramma degli stati della classe Partita riporta una descrizione
incompleta dello stato InDefinizione. In questo stato infatti vengono
ricevuti (dall’interfaccia utente) i comandi di posizionamento e sposta-
mento delle navi, che vengono rinviati agli opportuni quadranti. In base
alla situazione vengono anche mandati ai quadranti i comandi di abilita-
zione e disabilitazione di aggiunte e spostamenti di navi. Il funzionamen-
to è del tutto analogo rispetto al controllo degli spari.

5.2.3 Vincoli
Specifichiamo ora alcuni vincoli rilevanti cui devono sottostare le istan-
ze delle classi e associazioni illustrate in precedenza. Descriviamo ogni
vincolo sia informalmente, sia mediante OCL.
Come già accennato in precedenza, l’uso di OCL non è molto diffu-
so in ambito industriale, benché consenta di scrivere specifiche decisa-
mente più precise di quelle che si possono ottenere mediante notazioni
informali. Lasciamo al lettore il compito di confrontare le specifiche OCL
con le corrispondenti specifiche informali, e di conseguenza di valutare
quando siano preferibili le une o le altre.
I vincoli cui è soggetta la classe Quadrante, o meglio, le sue istanze
sono:
• il quadrante è quadrato, quindi ha un ugual numero di righe e di
colonne;

context Quadrante
inv: self.numColonne = self.numRighe
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 227

• non ci possono essere più colpi che interessano la medesima casel-


la;

context Quadrante
inv: self.colpo->forAll(c1, c2: Colpo |
c1 <> c2 implies c1.casella <> c2.casella)

• da ogni istanza di Quadrante sono accessibili —attraverso PropriaIU


e IUAvvers— due interfacce distinte.

context Quadrante
inv: self.propriaIU <> self.IUAvvers

Il vincolo cui sono soggette la istanze della classe Interfaccia è:


• da ogni istanza di InterfacciaUtente sono accessibili —attraverso
proprioQuad e quadAvvers— due quadranti distinti.

context InterfacciaUtente
inv: self.proprioQuad <> self.quadAvvers

Il vincolo cui sono soggette la istanze della classe Partita è:

• esiste un’unica istanza di Partita.

context Partita
inv: Partita.allInstances->size() = 1

I vincoli cui sono soggette le navi e le posizioni sono:


• le dimensioni di ciascuna nave devono essere comprese tra 1 e 5,
estremi compresi;

context Nave
inv: self.dimensioni > 0 and self.dimensioni < 6

• una stessa posizione non può essere occupata da più navi;

context Quadrante
inv: self.flotta->forAll(n1, n2 : Nave |
n1 <> n2 implies
not n1.posizione.conflitto(n2.posizione.inizio,
n2.posizione.fine))
Page 228 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

• per convenzione, ogni posizione fine ha numero di riga e/o colon-


na maggiore di inizio;

context Posizione
inv: self.inizio.riga <= self.fine.riga and
self.inizio.colonna <= self.fine.colonna

• ogni nave deve essere disposta orizzontalmente o verticalmente, inol-


tre la posizione della nave deve indicare un’occupazione di spazio
corrispondente alla dimensione della nave stessa;

context Posizione
inv: (self.inizio.riga = self.fine.riga and
self.fine.colonna - self.inizio.colonna = nave.dimensione - 1)
or (self.inizio.colonna = self.fine.colonna and
self.fine.riga - self.inizio.riga = nave.dimensione - 1)

• le coordinate di una posizione devono essere interne al quadrante


di gioco;
• l’attributo colpiSubiti è derivato e corrisponde al numero di colpi
caduti in una casella corrispondente alla posizione della nave.

5.2.4 Specifica dei metodi


In questo paragrafo diamo solo una definizione informale delle specifi-
che dei metodi per non appesantire eccessivamente la lettura del capito-
lo. Lasciamo al lettore, come esercizio, il compito di formalizzare in OCL
le definizioni informali che seguono. Osserviamo tuttavia che per quanto
riguarda la specifica dei metodi, c’è una ragione in più per preferire OCL:
esplicitare le precondizioni non contribuisce solo a definire il significato
del metodo, ma serve anche a stabilire quali sono le condizioni che devo-
no essere verificate affinché il metodo possa essere eseguito. Nel nostro
caso, specificare le precondizioni dei metodi della classe Quadrante signi-
fica contemporaneamente specificare gran parte dei metodi della classe
Partita, che deve controllare che i comandi inviati al Quadrante siano
correttamente eseguibili.
I metodi della classe Posizione sono:

• il metodo interessa() indica se la coordinata passata come argo-


mento appartiene alla posizione. Il numero di riga e di colonna del-
la coordinata possono essere passati anche come argomenti distinti;
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 229

• il metodo conflitto() indica se esiste un conflitto tra due posizio-


ni, cioè se le due posizioni hanno una casella in comune. Le coordi-
nate delle caselle di inizio e fine possono essere passati anche come
argomenti distinti.

I metodi della classe Quadrante sono:


• il metodo reset() svuota il quadrante da navi e colpi subiti, ripor-
tandolo alle condizioni iniziali;

• il metodo aggiungiNave() crea una nave delle dimensioni indica-


te e la posiziona alle coordinate indicate. Affinché l’operazione sia
possibile occorre che le dimensioni indicate siano legali, la posizio-
ne congruente con le dimensioni della nave, interamente contenuta
nel quadrante e libera, e il numero di navi della dimensione indica-
ta già presenti sul quadrante non sia pari al massimo previsto per la
partita che si sta giocando;

• il metodo spostaNave() cambia la posizione occupata da una nave


esistente, portandola nella posizione indicata;

• il metodo flottaAffondata() indica se la flotta dislocata nel qua-


drante è stata completamente affondata;

5.2.5 Diagrammi delle attività e di sequenza


Una volta modellati gli elementi del dominio del problema mediante i
diagrammi delle classi, è possibile specificare più precisamente quanto
già espresso in modo piuttosto informale con i diagrammi dei casi d’uso.
Possiamo infatti utilizzare le definizioni di classi, attributi e metodi per
scrivere diagrammi delle attività e/o diagrammi di sequenza che espri-
mono con maggior dettaglio e precisione quanto detto nei diagrammi dei
casi d’uso.
La Figura 5.6 riporta il diagramma che specifica quali sono le attività
che caratterizzano la fase di gioco e quali sono le condizioni che determi-
nano la conclusione e l’esito della partita.
Il diagramma indica che per prima cosa i due giocatori dispongono le
navi sul proprio quadrante, inizialmente vuoto. Queste attività sono svol-
te logicamente in parallelo dai due giocatori. Una volta finita la disposi-
zione, si ha un ciclo, a ogni iterazione del quale ciascun giocatore effettua
un’azione di sparo; anche in questo caso gli spari sono logicamente in pa-
rallelo, cioè non è rilevante l’ordine con cui sono effettuati. Dopo che i
Page 230 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

DisposizioneInizialeGioc1 DisposizioneInizialeGioc2

SparoGiocatore1 SparoGiocatore2

CalcoloDanniVisualizza

[not (quadGioc1.flottaAffondata() or quadGioc1.flottaAffondata()]

[quadGioc2.flottaAffondata() and not quadGioc1.flottaAffondata()]

VittoriaGioc1
[quadGioc1.flottaAffondata() and not quadGioc2.flottaAffondata()]

VittoriaGioc2
[quadGioc1.flottaAffondata() and quadGioc2.flottaAffondata()]

Pari

Figura 5.6: Diagramma delle attività per le fase del gioco.


CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 231

giocatori hanno sparato, il sistema calcola i danni e se almeno una flot-


ta è stata completamente affondata il gioco termina, altrimenti si effetua
un’altra iterazione. L’esito della partita è ben definito: se entrambe le flot-
te sono state affondate alla stessa iterazione la partita è pari, se invece una
sola flotta è stata affondata, la vittoria è del giocatore che ha almeno una
nave superstite.
Questo diagramma si applica correttamente anche quando un uni-
co giocatore si cimenti contro il calcolatore: infatti anche in questo caso
esiste un secondo giocatore, impersonato dal calcolatore stesso.
La Figura 5.7 riporta un diagramma delle attività che specifica qua-
li sono le attività che caratterizzano la fase di connessione e ricerca del
partner. Questo flusso di attività viene eseguito solo quando il giocatore
non opta per la partita contro il calcolatore, condizione sufficientemente
ovvia da non richiedere di essere modellata esplicitamente. Questa fase è
decisamente più complessa della fase di gioco. Le attività iniziano neces-
sariamente con la Connessione alla rete dei giocatori. Si hanno quattro
flussi di attività parallele. Il primo (flusso sinistro della fork) riguarda le
comunicazioni con gli altri sistemi, altri due (i flussi centrali) descrivono
la ricerca del partner, l’ultimo (flusso destro della fork) riguarda la pos-
sibilità di “abortire” la ricerca del partner. Durante la fase di ricerca del
partner dovrebbe sempre essere possibile per il giocatore rinunciare alla
ricerca, concludere il flusso di attività descritto dalla Figura 5.7 e quindi
scegliere di giocare contro il calcolatore o di non giocare affatto. Que-
sta possibilità si può modellare facilmente mediante l’attività Rinuncia
che, se completata, porta a uno stato finale (RicercaCompletata), e ciò
comporta la conclusione automatica di tutte le altre eventuali attività in
corso.
Esaminiamo in dettaglio le attività dedicate alle comunicazioni con
gli altri sistemi, che hanno lo scopo di mantenere ogni sistema aggior-
nato sulla situazione degli altri. Inizialmente il sistema locale attraverso
l’attività SegnalaDisponibilita comunica a tutti i sistemi attivi la dispo-
nibilità del giocatore a ricevere richieste di gioco. Poi il sistema median-
te AccettaNotifica riceve dagli altri sistemi comunicazioni circa i loro
cambiamenti di disponibilità. Queste informazioni sono utilizzate per
aggiornare (mediante AggiornaDisponibili) l’elenco locale dei giocatori
disponibili. Una volta completato l’aggiornamento, il sistema comuni-
ca la propria disponibilità, ma solo se le informazioni ricevute indicano
un nuovo sistema disponibile, che deve a sua volta essere informato della
disponibilità del giocatore locale.
La ricerca del partner procede a sua volta attraverso due flussi di at-
Page 232 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

Connessione

AggiornaDisponibili
[rinuncia]

SegnalaDisponibilita
Rinuncia

AccettaNotifica
RicezioneDisponibili

RicercaAbbandonata

[rifiuta]
VisualizzaDisponibili RicezioneRichieste

InviaRichiesta Risposta
[accetta]

[rispostaPositiva]
RicezioneRisposta InviaNotifica

[rispostaNegativa]

PerfezionaAccordo
RicercaCompletata

Figura 5.7: Diagramma delle attività per lafase di connessione e ricerca del
partner.
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 233

tività indipendenti: uno riguarda la ricerca attiva da parte del giocatore,


l’altro la gestione delle richieste da parte degli altri giocatori.
La ricezione dell’elenco dei giocatori disponibili (RicezioneDisponi-
bili) dà inizio alla ricerca attiva: l’elenco viene poi mostrato (Visualizza-
Disponibili), una richiesta inviata (InviaRichiesta) e la relativa rispo-
sta ricevuta (RicezioneRisposta). A questo proposito vanno fatte alcune
precisazioni: la prima è che qui abbiamo ipotizzato che il giocatore fac-
cia una sola richiesta alla volta (se cosı̀ non fosse il diagramma andrebbe
modificato opportunamente); la seconda è che poiché la risposta potreb-
be non arrivare, è opportuno inserire nell’attività RicezioneRisposta un
meccanismo di time-out, che concluda l’attività generando una risposta
negativa se entro un certo tempo non sono arrivate risposte. Lasciamo la
specifica di questi particolari alle successive fasi dello sviluppo.
In caso di risposta positiva ci si avvia alla conclusione, caratterizza-
ta da due attività: InviaNotifica, che serve ad avvertire gli altri sistemi
che il giocatore locale non è più disponibile, e PerfezionaAccordo che
comprende i dialoghi necessari per iniziare la partita.
La ricerca passiva è costituita da un semplice ciclo di ricezione di ri-
chieste (attività RicezioneRichieste) e invio delle relative risposte (atti-
vità Risposta). Il ciclo si conclude quando viene inviata una risposta posi-
tiva. Anche in questo caso ci si avvia a InviaNotifica e a PerfezionaAccordo,
concludendo cosı̀ il flusso delle attività.
Notiamo che quando uno dei due flussi di ricerca di partner raggiun-
ge lo stato finale, tutte le altre attività eventualmente in corso vengono
concluse automaticamente. Secondo le specifiche date il giocatore può
“saltare” liberamente da attività di ricerca attiva ad attività di ricerca pas-
siva e viceversa. È ovviamente responsabilità del giocatore comportar-
si in modo ragionevole (ad esempio non ha senso rifiutare un’offerta e
contemporaneamente emettere una richiesta nei confronti del medesimo
giocatore).
Con un opportuno diagramma di sequenza si può invece specificare
meglio quanto detto in precedenza a proposito del controllo degli spari da
parte della classe Partita. Il diagramma riportato nella Figura 5.8 illustra
appunto una sequenza di sparo.
Lo scenario rappresentato è piuttosto normale: dopo che Partita ha
abilitato gli spari da parte del giocatore 1, questi segnala all’interfaccia la
volontà di sparare alla coordinata c (Sparo(c)). L’interfaccia prende in
considerazione la richiesta, essendo questa abilitata, e chiede quindi al-
la Partita di verificare se le precondizioni sono soddisfatte, ricevendone
risposta positiva. A questo punto bisogna dare corso all’azione di spa-
Page 234 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

IU1: InterfacciaUtente : Partita quadGioc2: Quadrante IU2: InterfacciaUtente


Giocatore1: Giocatore

abilitaSparo()

sparo(c)

preSparo(c)

true

sparo(c)

disabilitaSparo()

sparo(c)

: Colpo
visualizzaQuadAvvers()

visualizzaQuadProprio()

Figura 5.8: Diagramma di sequenza relativo a uno sparo.

ro: l’interfaccia invia la richiesta di sparo alla Partita, che la reinvia al


Quadrante opportuno (quello del giocatore 2, essendo lo sparo effettuato
dal giocatore 1), dopo aver disabilitato l’interfaccia del giocatore 1 dal-
l’accettare ulteriori spari. Il quadrante che riceve la richiesta di sparo crea
un’istanza di Colpo e la connette a sé, dopo di che invia alle interfacce
dei due giocatori la richiesta di visualizzarsi con la modalità opportuna
(il giocatore 1 vedrà il quadrante avversario, mentre il giocatore 2 vede il
proprio).
Non è mostrata nel diagramma la riabilitazione agli spari dell’interfac-
cia del giocatore 1: questa avverrà solo dopo che anche il giocatore 2 ha
sparato.

5.2.6 Gioco contro il computer


Le specifiche viste finora si applicano al gioco tra due giocatori. Quando la
partita ha luogo tra un giocatore e il calcolatore, ci sono alcune differen-
ze: il giocatore corrispondente al calcolatore dispone di un’ “interfaccia
utente” particolare, che non è dotata di visualizzazione e che genera au-
tonomamente i comandi che normalmente arriverebbero dal giocatore.
A questo proposito ipotizziamo che una generazione casuale di comandi
sia appropriata. In effetti per la battaglia navale la disposizione casua-
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 235

le delle navi, e la determinazione altrettanto casuale delle coordinate di


sparo, costituiscono una tattica corretta ed efficace. Questo consente di
omettere la specifica precisa del gioco a uno, riducendola alle poche righe
di testo riportate qui sopra.
Le cose sarebbero assai diverse se il calcolatore dovesse giocare secon-
do criteri specifici (per esempio ricordando dove ogni giocatore preferisce
disporre le navi e i colpi, e comportandosi di conseguenza). Come ulti-
ma osservazione, notiamo che potrebbe essere interessante dotare anche
il calcolatore di un’interfaccia con visualizzazione dei quadranti. Infatti
questi potrebbero essere utili in fase di debugging, per osservare come si
comporta il calcolatore.

5.3 Progettazione
Questo paragrafo definisce il progetto del sistema partendo dalla speci-
fica dei requisiti dettagliata nei paragrafi precedenti. Per mantenere una
chiara separazione rispetto a quanto definito in fase di analisi, tutte le
classi di progetto sono state inserite in un package separato denominato
battagliaNavale. È opportuno notare che tali package conterranno clas-
si che in taluni casi possono coincidere con le classi del modello di do-
minio individuate durante l’analisi, ma pi spesso conterranno classi più
dettagliate che rappresentano un raffinamento (o più propriamente una
realizzazione) delle classi del modello di dominio. In particolare le classi
di progetto potranno contenere nuove operazioni non previste nelle cor-
rispondenti classi del modello di analisi, cosı̀ come operazioni presenti in
classi del modello di analisi potranno non essere presenti o apparire con
nuove denominazioni nel progetto.
La Figura 5.9 illustra i package principali definiti in fase di progetto e
le rispettive dipendenze. I package individuati e le rispettive responsabilit
generali sono identiche a quelle gi descritte in fase di analisi. Nei paragrafi
successivi analizzeremo in dettaglio il contenuto dei singoli package, evi-
denziando ove necessario eventuali differenze rispetto a quanto definito
in fase di analisi.

5.3.1 Architettura logica del sistema


Una buona regola in fase di progetto cominciare con la definizione del-
l’architettura del sistema. In questo paragrafo analizzeremo in particolare
Page 236 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

battagliaNavale

partita interfaccia

campoBattaglia
+ Colpo
+ Nave
+ Quadrante

Figura 5.9: Package principali.

l’architettura logica e individueremo i principali componenti architettu-


rali e le modalità di interazione.
Il diagramma nella Figura 5.10 illustra i principali componenti del-
l’architettura logica del sistema e le relazioni con i package, descritti nel
paragrafo precedente, dei quali i componenti costituiscono una realiz-
zazione. Il componente CampoBattaglia contiene la logica di gestione
dei quadranti, del posizionamento delle navi e di gestione degli spari,
ma non ha alcuna conoscenza del meccanismo di gestione dell’evolu-
zione della partita né della logica di visualizzazione dell’interfaccia uten-
te ed utilizzato dagli altri come componente di servizio. I componenti
Partita e Giocatore incapsulano rispettivamente tutta la logica di gestio-
ne della partita (dall’organizzazione alla gestione della sua evoluzione) e
la logica di gestione del singolo giocatore. Le porte Comandi, Notifiche e
Organizzazione definiscono il contratto fra i componenti Partita e Giocatore
e l’esterno in termini di interfacce offerte e richieste. In particolare i com-
ponenti Partita e Giocatore comunicano tra loro esclusivamente attra-
verso le tre interfacce IPartita, IGiocatore e IOrganizzazionePartita,
con le seguenti responsabilità:

• IPartita espone le operazioni utilizzate dai singoli giocatori (attra-


verso istanze del componente Giocatore) per comunicare al com-
ponente Partita uno sparo, l’eventuale rinuncia alla partita, il com-
pletamento dello schieramento nella fase iniziale di definizione del-
la partita.

• IGiocatore espone le operazioni utilizzate dal componente Partita


CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 237

campoBattaglia

interfaccia partita

<<component>>
CampoBattaglia
<<use>> <<use>>

: Quadrante
<<component>> <<component>>
Giocatore Partita

Comandi Comandi
: Partita
IPartita

Notifiche Notifiche
: GiocatoreController
IGiocatore

Organizzazione Organizzazione
: GestorePartita
IOrganizzazionePartita

Figura 5.10: Diagramma dei componenti per l’architettura logica del sistema.

per comunicare al giocatore la necessit di abilitare o disabilitare nel-


la propria interfaccia utente determinate opzioni di gioco, per noti-
ficare le coordinate di uno sparo effettuato dall’avversario, per in-
viare messaggi e notifiche di vario tipo ai giocatori.

• IOrganizzazionePartita espone le operazioni utilizzate dai gioca-


tori nella fase iniziale per gestire l’organizzazione di una partita, in
particolare la richiesta della lista dei potenziali giocatori disponibili,
la segnalazione della disponibilità di un giocatore a essere contat-
tato per partecipare a una partita, la comunicazione dell’avversario
selezionato per una nuova partita, la scelta della tipologia di gioco
(avversario umano o gioco contro il computer).

Le operazioni esposte dalle interfacce descritte sono dettagliate nel


diagramma della Figura 5.11. È opportuno osservare che in questa fase
iniziale di definizione del progetto ci si concentra esclusivamente sull’ar-
chitettura logica del sistema non considerando per ora i problemi di di-
stribuzione dei componenti in rete. L’introduzione delle interfacce sopra
descritte ci consentir di trattare agevolmente questi aspetti nel paragrafo
dedicato al progetto di dettaglio del sistema.
Page 238 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

<<interface>>
IPartita
+ sparo(): void <<component>>
+ rinuncia(): vodi Partita
+ schieramentoCompleto()
...

<<component>> <<interface>>
Giocatore IOrganizzazionePartita
+ getElencoGiocatori()
+ registraGiocatore()
+ selezionaGiocatore()

<<interface>>
IGiocatore
+ abilitaSparo()
+ disabilitaSparo()
+ abilitaSpostamenti()
+ disabilitaSpostamenti()
+ abilitaAggiunta()
+ disabilitaAggiunta()
+ visualizzaQuadProprio()
+ visualizzaQuadAvvers()
+ messaggio(t: String )
+ notificaSparo(c: Coordinata)
+ getNomeGiocatore(): String

Figura 5.11: Interfacce dell’architettura logica.


CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 239

5.3.2 Progetto di dettaglio

Definizione del campo di battaglia

Comiciamo a esaminare in dettaglio le classi contenute nel package cam-


poBattaglia. Tali classi rappresentano un raffinamento delle classi con-
tenute nel package omonimo definito nella fase di analisi dei requisiti.
Una prima differenza, evidenziata nella Figura 5.12 relativa alla modalit di
definizione delle navi che devono essere posizionate su un quadrante. La
classe Nave consente di specificare nel costruttore oltre alle dimensioni il
tipo (“sottomarino”, “cacciatorpediniere”, ecc.) e un nome scelto dall’uten-
te (ad esempio “Sottomarino giallo”, “Enrico Toti”, “Titanic”, ecc.). D’altra
parte nella battaglia navale di norma il tipo equivale alla dimensione (1
casella = sottomarino, 2 caselle = caccia, ecc.) e gestendo separatamen-
te i due attributi si rischia di avere navi di dimensioni non compatibili
con quelle normalmente associate al tipo (ad esempio una corazzata da
2 caselle). Per evitare questo problema il costruttore della classe Nave ha
visibilit protected e pu pertanto essere utilizzato soltanto da sottoclassi
e da altre classi all’interno dello stesso package. La creazione delle diver-
se tipologie di navi standard effettuata attraverso l’utilizzo di alcune op-
portune sottoclassi della classe Nave. I costruttori di queste sottoclassi si
fanno carico di mantenere la congruenza fra il tipo della nave e le dimen-
sioni. In particolare la Figura 5.12 evidenzia le classi utilizzabili per de-
finire navi con dimensione da 1 a 5 caselle e precisamente Sottomarino,
Cacciatorpediniere, Incrociatore, Corazzata, Portaerei. Ad esem-
pio, il costruttore della classe corazzata inizializza a 4 il valore dell’attri-
buto dimensioni e inizializza a “corazzata” il valore dell’attributo tipo.

La classe Posizione, rappresentata come classe di associazione fra


Nave e Quadrante nel modello di analisi, viene ora modellata in modo
leggermente differente: una posizione è associata a una specifica Nave
e a uno specifico Quadrante (attraverso l’interfaccia IQuadrante, che in-
dividua i metodi pubblici esposti dalla classe Quadrante) e presenta due
associazioni verso la classe Coordinata. Tali associazioni individuano le
coordinate iniziale e finale della posizione sul quadrante di gioco.
Per quanto riguarda le operazioni, in tutte le classi sono state man-
tenute quelle definite in fase di analisi e sono state aggiunte operazio-
ni get<nomeAttributo>() che svolgono il ruolo di mediatori nell’acces-
so ai valori degli attributi (la cui visibilit protected). I principali vin-
coli OCL definiti in fase di analisi sono stati modellati come operazioni
Page 240 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

Posizione
+ Posizione(inizio: Coordinata, fine: Coordinata, nave: Nave, quadrante: Quadrante)
+ conflitto(iniz: Coordinata, fine: Coordinata): boolean
+ conflitto(p: Posizione): boolean
+ interessa(c: Coordinata): boolean
+ interessa(riga: int, col: int ): boolean
+ getFine(): Coordinata
*
+ getInizio(): Coordinata
+ getNave(): Nave
+ getQuadrante(): QuadranteInterface
# invInizioFineOK(): boolean
# invConsistenzaCoordinateConQuadrante(): boolean
# invConsistenzaCoordinateConNave(): boolean
1 # quadrante ...
* * *
<<interface>>
IQuadrante

+ reset(): void
+ aggiungiNave(n: Nave, inizio: Coordinata, fine: Coordinata): void
+ sparo(c: Coordinata) : boolean
+ sparo(riga: int, colonna : int) : boolean
+ spostaNave(n: Nave, iniz: Coordinata, fine: Coordinata): void
+ flottaAffondata(): boolean
+ completo(): boolean
+ colpito(c: Coordinata): boolean
+ getColpi(): Collection
+ getNumColonne(): int
+ getNumRighe(): int
+ getPosizioneFlotta(): Collection
+ getNave(nome: String): Nave
+ getNave(c: Coordinata): Nave
+ getPosizione(n: Nave, inizio: Coordinata, fine: Coordinata): Posizione
+ getPosizione(n: Nave): Posizione
+ getPosizione(nomeNave: String): Posizione
+ esistePosizione(p: Posizione): boolean 1 # nave
+ esisteNave(n: Nave): boolean
+ esisteNave(nomeNave: String): boolean
Nave
+ eliminaPosizione(p: Posizione)
+ posizioneValida(p: Posizione): boolean # dimensioni: int
+ interna(c: Coordinata): boolean # colpiSubiti : int
# tipo : String = null
# nome : String = null
1 - posizioneFlotta * # count : int = 0
Quadrante
# Nave(dimensioni: int)
# numRighe: int # Nave(tipo: String, dimensioni: int)
# numColonne: int # Nave(nome: String, tipo: String, dimensioni: int)
... + affondata(): boolean
+ Quadrante(colonne: int, righe: int) + getColpiSubiti(): int
# esisteConflittoPosizioneFlotta(pos: Posizione): boolean + setColpiSubiti(colpi: int): void
# invDimensioniQuadranteOK(): boolean + getDimensioni(): int
# preSparoNonPresente(c : Colpo): boolean + getTipo(): String
# preSparoCoordinataOK(c : Colpo): boolean + getNome(): String
... ...
1 1
1 # fine # inizio
- colpi
*
* # casella Coordinata
Colpo
1

Sottomarino Incrociatore Corazzata CacciaTorpediniere Portaerei

Figura 5.12: Campo di battaglia e navi.


CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 241

protected all’interno delle rispettive classi. Queste operazioni “specia-


li”, invocate al momento della costruzione di un’istanza della classe e in
corrispondenza dell’invocazione delle operazioni pubbliche esposte dal-
la classe sono evidenziate nella Figura 5.12 attraverso l’uso degli stereotipi
<<invariant>> e <<precondition>>, che individuano rispettivamente gli
invarianti di classe e precondizioni associate ai metodi. La verifica degli
invarianti di classe e delle precondizioni associate alle operazioni viene
effettuata in corrispondenza della creazione di un’istanza o dell’invoca-
zione dell’operazione per la quale le precondizioni sono state definite. In
caso di fallimento nella verifica di un invariante o di una precondizione
un’opportuna eccezione viene generata e trasmessa al chiamante. Per la
classe Quadrante sono stati in particolare definiti i seguenti vincoli:

• l’invariante di classe invDimensioniQuadranteOK(), responsabile del-


la verifica di eguaglianza fra numero di righe e di colonne del qua-
drante al momento della creazione di un nuovo quadrante di gioco;

• le precondizioni preSparoCoordinataOK(Colpo) e preSparoNonPre-


sente(Colpo), verificate in corrispondenza dell’invocazione dell’o-
perazione sparo(Coordinata) su un’istanza di Quadrante. Queste
operazioni sono responsabili di verificare che il colpo associato al-
la coordinata di sparo sia interno al quadrante e che non esista un
colpo precedente associato alla stessa coordinata di sparo.

Per la classe Posizione invece abbiamo definito i seguenti invarianti


di classe, tutti verificati al momento dell’invocazione del costruttore:

• invInizioFineOK() verifica che ogni nave sia disposta orizzontal-


mente o verticalmente, che la coordinata fine abbia numero di riga
e/o colonna maggiore di inizio e che le coordinate siano caratte-
rizzate sempre da valori non negativi di riga e colonna;

• invConsistenzaCoordinateConQuadrante() verifica che le coordi-


nate associate alla posizione siano consistenti con la dimensione del
quadrante;

• invConsistenzaCoordinateConNave() verifica che le coordinate as-


sociate alla posizione siano consistenti con la dimensione della na-
ve.
Page 242

Richiedente
<<create>> ("Enterprise")
e: Portaerei q: Quadrante

<<create>> (0, 0)
i: Coordinata

quadrante.
<<create>>(4,0)
f: Coordinata

aggiungiNave(e, i, f)
<<create>> (i, f, e)
p: Posizione
posizioneValida(p)

true

esisteConflittoPosizioneFlotta(p)

false

esisteNave(e)

false

Figura 5.13: Diagramma di sequenza per il dispiegamento di una nave sul


CAPITOLO 5. BATTAGLIA NAVALE IN RETE
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 243

Il diagramma di sequenza nella Figura 5.13 mostra, a titolo di esempio


di utilizzo delle classi del package campoBattaglia, la sequenza di pas-
si seguiti per la creazione di una nave (nell’esempio la nave creata è la
portaerei “Enterprise”) e il suo inserimento in un quadrante preesistente.

• Il richiedente crea l’istanza della portaerei “Enterprise”. Osservia-


mo che il richiedente non è l’utente, ma è un oggetto istanza di una
classe che ha la responsabilità delle creazione delle navi nel sistema
e del relativo posizionamento sui quadranti (ad esempio una classe
nel componente Partita o nel componente Giocatore).

• Il richiedente crea le coordinate di inizio e fine per il posiziona-


mento della nave.

• Il richiedente invoca il metodo aggiungiNave() sull’istanza di Qua-


drante passando come parametri la nave e le coordinate di inizio e
fine.

• L’istanza di Quadrante crea una nuova istanza di Posizione per la


nave e le coordinate passate come parametri.

• Il quadrante verifica che la posizione sia interna al quadrante invo-


cando il metodo posizioneValida().

• Il quadrante verifica che la posizione creata non sia in conflitto con


altre navi precedentemente dispiegate sul quadrante invocando l’o-
perazione esisteConflittoPosizioneFlotta().

• Il quadrante verifica che una nave con lo stesso tipo, nome e di-
mensioni non sia gi stata dispiegata in una qualsiasi posizione del
quadrante.

Gestione dell’evoluzione della partita

Ora dobbiamo ridefinire le classi che implementeranno la gestione della


partita in modo consistente con i requisiti.
Il diagramma delle classi nella Figura 5.14 illustra le principali classi
coinvolte nella gestione dell’evoluzione della partita. La classe Partita,
che realizza l’interfaccia IPartita, riceve i comandi e messaggi inviati dai
due giocatori e gestisce l’evoluzione dello stato della partita. La classe
Page 244 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

<<interface>> <<interface>>
IGiocatore IPartita
+sparo(giocatore : int, c : Coordinata)
+rinuncia(giocatore : int)
+schieramentoCompleto(giocatore : int, quadranteGioco : Quadrante)
...
#organizzatore #avversario

ProprietaPartita
#statoPartita
+ ProprietaPartita() Partita StatoPartita
+ getAvversario()
+ getOrganizzatore() #partitaContext
+ getMaxNavi() #STATO_INDEFINIZIONE
+ getNumRigheQuadrante()
+ getNumColonneQuadrante() #STATO_ATTESASPARO

#partitaProp #STATO_ATTESASPAROGIOCATORE1

#STATO_ATTESASPAROGIOCATORE2

2 #STATO_PARTITACONCLUSA
Quadrante

AttesaSparoGiocatore1 InDefinizione PartitaConclusa AttesaSparoGiocatore2 AttesaSparo

Figura 5.14: Diagramma delle classi per la gestione della partita.

Partita mantiene il riferimento ai quadranti di gioco dei due giocatori at-


traverso una relazione di aggregazione verso la classe Quadrante. La clas-
se ProprietaPartita impostata al termine delle fase iniziale di organiz-
zazione della partita e mantiene i riferimenti verso i due giocatori (orga-
nizzatore e avversario), referenziati attraverso l’interfaccia IGiocatore.
Al termine dell’organizzazione della partita sono noti l’organizzatore e
l’avversario, oltre alle dimensioni del quadrante di gioco concordate tra
i giocatori e il numero di navi per ogni dimensione che dovranno essere
dispiegate dai giocatori sul quadrante di gioco. Queste informazioni sono
mantenute in una istanza di ProprietaPartita opportunamente creata e
passata al costruttore della classe Partita al momento dell’inizializzazio-
ne vera e propria della partita. Poiché la partita deve seguire la dinamica
propria della battaglia navale (Figura 5.5) dobbiamo fare in modo che sia-
no ben rappresentati i vari stati in cui si può trovare e che in ciascuno stato
siano disponibili tutte e sole le operazioni che possono essere legalmente
eseguite secondo le regole. Ad esempio, se il primo giocatore ha sparato,
la partita deve trovarsi in uno stato in cui solo il secondo giocatore può
sparare. Occorre cioè definire un insieme di classi che consenta di realiz-
zare una macchina a stati con un comportamento consistente con quello
definito in fase di analisi dei requisiti e illustrato nella Figura 5.5.
Esistono diversi modi possibili, noti in letteratura, per realizzare una
macchina a stati seguendo un approccio orientato a oggetti. Una model-
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 245

Context - context *
State
+ request() - state
+ handle()
+ setState(s : State)

ConcreteState ConcreteStateA ConcreteStateB


+ handle() + handle() + handle()

State

context: Context state: State

concreteState: ConcreteState

Figura 5.15: Classi e collaborazioni nel design pattern State.

lazione semplice ed efficace quella definita dal design pattern State de-
scritto in dettaglio in [GHJV95] e brevemente richiamato nella Figura 5.15
e nel diagramma di sequenza della Figura 5.16.
Il principio di funzionamento è semplice: in ogni istante l’istanza del-
la classe Context è associata a una (unica) istanza di una sottoclasse di
State, che rappresenta appunto lo stato attuale. Quando l’istanza di Context
riceve le richieste dai client (rappresentate da un’invocazione dell’opera-
zione request()), delega la gestione della richiesta allo stato corrente, il
quale la elabora e, sulla base del risultato dell’elaborazione, definisce la
transizione allo stato successivo. Un comportamento alternativo prevede
che sia il Context e non il singolo stato a definire l’eventuale transizione
allo stato successivo al termine dell’elaborazione della richiesta inviata
allo stato dal Context (Figura 5.16).
La Figura 5.17 illustra la contestualizzazione del pattern State alla ge-
stione dell’evoluzione della partita. Osserviamo che il role binding fra le
classi del package partita e la collaborazione State é stato rappresenta-
to con semplici relazioni d’uso (opportunamente decorate con lo stereo-
tipo role binding) per l’impossibilit di uso della corretta notazione
prevista dalla specifica UML 2 da parte dello strumento di modellazione
utilizzato.
Lo stato della partita è rappresentato dalla classe StatoPartita. Co-
Page 246 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

client: Client context1: Context concreteStateA: ConcreteStateA concreteStateB: ConcreteStateB

request()
handle()

alt
[Il singolo stato gestisce il passaggio allo stato successivo ]

setState(concreteStateB)

[context gestisce direttamente il passaggio allo stato successivo ]


setState(concreteStateB)

request()

handle()

Figura 5.16: Diagramma di sequenza per il pattern State.


CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 247

#partitaContext StatoPartita

# entraStato()
# turnoCompletato(): boolean
Partita # resetTurnoCorrente()
# sparoHelper(giocatore: int, avversario: int, c: Coordinata): boolean
# updateTurno(): void
+ getNomeStatoPartita(): String
+ StatoPartita(partitaContext: Partita)
#statoPartita + rinuncia(giocatore: int): void
Context + schieramentoCompleto(giocatore: int, quadranteGiocatore: Quadrante): void
+ sparo(giocatore: int, c: Coordinata): void
<<role binding>> ...
<<role binding>> State

InDefinizione

# entraStato(): void
# schieramentiCompletati(): boolean
# setSchieramentoCompletato(giocatore: int): void
+ InDefinizione(partitaContext: Partita): void
+ schieramentoCompleto(giocatore: int, quadranteGiocatore: Quadrante): void

ConcreteState
AttesaSparo PartitaConclusa
<<role binding>>
+ AttesaSparo(partitaContext: Partita) + PartitaConclusa(partitaContext: Partita)
ConcreteState # entraStato(): void # entraStato(): void
+ sparo(giocatore: int, c: Coordinata): void
<<role binding>>
State <<role binding>> ConcreteState

<<role binding>> AttesaSparoGiocatore1 AttesaSparoGiocatore2

ConcreteState + AttesaSparoGiocatore1(partitaContext: Partita) + AttesaSparoGiocatore2( partitaContext: Partita)


# entraStato(): void # entraStato(): void
+ sparo(giocatore: int, c: Coordinata): void + sparo(giocatore: int, c: Coordinata): void

<<role binding>> ConcreteState

Figura 5.17: Dettaglio delle classi e contestualizzazione del design pattern State.
Page 248 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

me previsto dal design pattern State quest’ultima è una classe astratta,


specializzata da molte sottoclassi, ciascuna delle quali rappresenta uno
specifico stato della macchina a stati. È abbastanza intuitivo riconoscere
la corrispondenza tra le sottoclassi di StatoPartita e gli stati del diagram-
ma illustrato nella Figura 5.5. L’unica differenza notevole sta nel fatto che
nel diagramma della Figura 5.17 lo stato di partita conclusa è rappresenta-
to esplicitamente mediante una sottoclasse. Poiché l’organizzazione del-
le classi illustrata nella Figura 5.14 e nella Figura 5.17 rispetta sostanzial-
mente la dinamica della partita descritta nella Figura 5.5 non si ritiene
utile descrivere nuovamente la dinamica della classe Partita mediante
un nuovo diagramma di stato.
La classe Partita rappresenta il Context del pattern State e in ogni
istante l’istanza di Partita mantiene un riferimento verso una specifi-
ca istanza di una sottoclasse di StatoPartita. Ad esempio, all’atto della
creazione della partita, il metodo costruttore farà in modo che l’istanza di
Partita sia connessa a un’istanza di InDefinizione, poich appunto all’i-
nizio della partita si ha la fase di definizione, cioé di dislocamento delle
navi sul campo di battaglia.
Questa rappresentazione degli stati prevede due modalità di gestione
delle sottoclassi di StatoPartita.

• Si possono creare tutte le istanze necessarie a rappresentare gli sta-


ti all’atto dell’istanziazione della partita, e poi cambiare semplice-
mente l’associazione che rappresenta lo stato corrente.
• Ad ogni cambio di stato si può creare dinamicamente l’istanza che
rappresenta il nuovo stato.

Nel nostro caso abbiamo scelto la prima opzione, anche perché il nume-
ro di stati è piccolo, e vengono generalmente attraversati tutti. Questa
scelta spiega la presenza nella Figura 5.14 delle associazioni fra la clas-
se Partita e StatoPartita denominate STATO <NOME STATO> (un esempio
l’associazione etichettata con il nome di ruolo STATO INDEFINIZIONE).
Per quanto riguarda le operazioni, la classe StatoPartita fornisce un’im-
plementazione per quelle disponibili in ogni stato (che non devono quin-
di essere ridefiniti nelle sottoclassi; un esempio dato dal metodo rinuncia()).
Per le operazioni che prevedono una ridefinizione nelle sottoclassi la clas-
se StatoPartita fornisce un’implementazione vuota. Un approccio alter-
nativo e assolutamente equivalente poteva consistere nel marcare que-
ste operazioni come astratte e non fornire quindi alcuna implementa-
zione nella classe StatoPartita. L’unica operazione astratta della classe
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 249

StatoPartita entraStato(). Quest’operazione viene invocata dall’ope-


razione setStato() di PartitaContext quando si verifica una transizione
di stato e simula in un certo senso l’azione entry prevista dalla specifica
UML per le macchine a stati.

Giocatore e interfaccia utente


Occupiamoci ora della struttura interna del componente Giocatore, illu-
strata dal diagramma delle classi della Figura 5.18. Il componente Giocatore
è una realizzazione del package interfaccia ed è responsabile dell’inte-
razione con il componente di gestione della partita (attraverso le inter-
facce descritte in precedenza) e della visualizzazione della sua evoluzione
sull’interfaccia utente di ciascun giocatore.
Per garantire un buon disaccoppiamento tra gli oggetti che rappresen-
tano le informazioni associate al giocatore durante la partita e la logica di
presentazione abbiamo deciso di strutturare internamente il componen-
te Giocatore secondo quanto previsto dal pattern Model-View-Controller
(MVC) [GHJV95,BMR+96]. Questo pattern prevede la presenza delle se-
guenti tre tipologie di elementi.

• Model. Incapsula lo stato del sistema o sottosistema considerato,


ovvero rappresenta gli oggetti (i dati) veri e propri, funzionali all’ap-
plicazione. Il Model è indipendente dalle specifiche rappresentazio-
ni e modalità di visualizzazione ed è indipendente dalle modalità di
input dei dati da parte dell’utente.

• View. È responsabile della gestione delle modalit di rappresenta-


zione e visualizzazione del modello o di una sua parte (ad esempio
della visualizzazione sullo schermo dell’utente). Una View ottiene i
dati per la presentazione dal Model cui associata. Possono esserci
(e tipicamente esistono) viste multiple per lo stesso modello.

• Controller. Incapsula il comportamento del sistema o sottosistema


considerato definendo un insieme di regole che stabiliscono le rea-
zioni della presentazione sullo schermo in relazione alle interazioni
con l’utente. Ciascuna View ha associato un componente control-
ler, il cui compito è ricevere gli input dell’utente (ad esempio co-
me eventi a seguito di operazioni con mouse o tastiera). Il Control-
ler traduce gli eventi in richieste di servizio per il modello o la vista
cui è associato. L’utente interagisce con il sistema o il sottosistema
considerato solo ed esclusivamente attraverso i Controller.
Page 250 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

<<interface>>
IQuadrante
+ reset(): void
<<interface>>
+ aggiungiNave(n: Nave, inizio: Coordinata, fine: Coordinata): void IGiocatore
+ sparo(c : Coordinata): boolean
+ sparo(riga: int, colonna: int): boolean + abilitaSparo(): void
+ spostaNave(n: Nave, iniz: Coordinata, fine: Coordinata): void + disabilitaSparo(): void
+ flottaAffondata(): boolean + abilitaSpostamenti(): void
+ completo(): boolean + disabilitaSpostamenti(): void
+ colpito(c: Coordinata): boolean + abilitaAggiunta(): void
+ getColpi(): Collection + disabilitaAggiunta(): void
+ getNumColonne(): int + visualizzaQuadProprio(): void
+ getNumRighe(): int + visualizzaQuadAvvers(): void
+ getPosizioneFlotta(): Collection + messaggio(t: String): void
+ getNave(nome: String): Nave + notificaSparo(c: Coordinata): void
+ getNave(c: Coordinata): Nave + getNomeGiocatore(): String
+ getPosizione(n: Nave, inizio: Coordinata, fine: Coordinata): Posizione
+ getPosizione(n: Nave): Posizione
+ getPosizione(nomeNave: String): Posizione
+ esistePosizione(p: Posizione): boolean
+ esisteNave(n: Nave): boolean
+ esisteNave(nomeNave: String): boolean GiocatoreController
+ eliminaPosizione(p: Posizione)
+ posizioneValida(p: Posizione): boolean
+ interna(c: Coordinata): boolean
~controller

VistaQuadProprio ~view

+ visualizza(): void ~quadranteProprio


+ reset(): void GiocatoreView

+ GiocatoreView(model: GiocatoreModel, controller: GiocatoreController)


VistaQuadAvversario + update(giocatoreObservable: GiocatoreObservable): vod

+ visualizza(): void
+ reset(): void ~quadranteAvversario

<<use>>
~model
#quadranteAvversario
Quadrante GiocatoreModel
#quadranteProprio ~model

GiocatoreObservable
<<interface>>
IGiocatoreObserver
+ attach(giocatoreObserver: GiocatoreObserver): void
+ detach(giocatoreObserver: GiocatoreObserver): void <<use>> + update(giocatoreObservable: GiocatoreObservable): void
+ notifyObserver(): void

Figura 5.18: Dettaglio delle classi per la gestione dell’interfaccia utente.


CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 251

Model-View-Controller

<<role binding>>
<<role binding>>
<<role binding>>

Controller Model View

GiocatoreController GiocatoreModel GiocatoreView

Observer Observable Observer

<<role binding>>
<<role binding>> <<role binding>>

Observer

Figura 5.19: Classi e collaborazioni nel design pattern MVC.

La Figura 5.19 illustra le classi del sistema che partecipano alla realiz-
zazione del pattern MVC.

• GiocatoreController interagisce con il componente di gestione del-


la partita nelle due direzioni, nel senso che invia al componente
Partita (attraverso l’interfaccia IPartita) i comandi del giocatore e
riceve dal componente Partita (attraverso l’interfaccia IGiocatore)
le notifiche relative ai colpi sparati dall’avversario, eventuali mes-
saggi informativi inviati dal gestore della partita e richieste di abi-
litazione o disabilitazione di funzionalità particolari sull’interfaccia
utente. Responsabilità di GiocatoreController è tradurre le azio-
ni dell’utente sulla View e le operazioni invocate dal componente
Partita in richieste di aggiornamento del Model.

• GiocatoreModel mantiene un riferimento al quadrante di gioco del


giocatore e al quadrante associato al giocatore avversario (conte-
nente la mappa dei colpi sparati con i relativi risultati). GiocatoreModel
riceve le richieste di aggiornamento da GiocatoreController, ag-
giorna il Model e invia alle View e ai Controller associati al modello
una notifica dell’avvenuto cambiamento nel Model.

• GiocatoreView è l’unico responsabile della logica di presentazione


dei dati, dalla visualizzazione dei quadranti di gioco del giocatore e
Page 252 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

dell’avversario alla visualizzazione di eventuali messaggi e notifiche


inviate dal componente Partita. GiocatoreView interagisce diret-
tamente con l’utente e trasmette le azioni dell’utente sull’interfaccia
in messaggi per il Controller associato alla View.

Come noto dalla letteratura, un approccio semplice e consolidato per


realizzare il pattern MVC consiste nel ricorrere a un altro design pattern di
base: Observer [GHJV95,BMR+96], che trova il suo utilizzo naturale in tutti
i casi in cui è necessario disaccoppiare oggetti in modo tale che cambia-
menti in uno influenzino gli altri senza che l’oggetto modificato debba co-
noscere alcun dettaglio sulla natura degli altri oggetti interessati a ricevere
informazioni sulle sue modifiche (e viceversa). La Figura 5.19, oltre a mo-
strare che il pattern MVC possa essere visto come una realizzazione del
pattern Observer, evidenzia il role binding fra le classi GiocatoreModel,
GiocatoreController e GiocatoreView e le collaborazioni che identifica-
no i due pattern considerati.

3.2: attach(giocatoreController)

giocatoreController: GiocatoreController giocatoreModel: GiocatoreModel

3.1: setController(giocatoreController)
2.1: attachgiocatoreView)

giocatoreView: GiocatoreView

3: create(giocatoreModel, giocatoreView)
1: create()

2: create(giocatoreModel)

giocatoreManager: GiocatoreManager

Figura 5.20: Fase iniziale di creazione delle relazioni fra i partecipanti al pattern
MVC.

Il diagramma di comunicazione nella Figura 5.20 illustra la fase inizia-


le di creazione delle relazioni fra i partecipanti al pattern MVC. Le singole
interazioni sono dettagliate nel seguito.

• giocatoreManager (istanza della classe GiocatoreManager) crea una


nuova istanza della classe GiocatoreModel.
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 253

• giocatoreManager crea una nuova istanza della classe GiocatoreView


passando come parametro un riferimento all’istanza di GiocatoreModel
precedentemente creata.
• Il costruttore di GiocatoreView registra l’istanza in costruzione co-
me osservatore delle modifiche nel modello invocando sul riferi-
mento all’istanza di GiocatoreModel passata come parametro il me-
todo attach() e passando il riferimento a se stesso come argomento
nell’invocazione.
• giocatoreManager crea una nuova istanza della classe Giocatore-
Controller passando come parametri i riferimenti alle istanze di
GiocatoreModel e GiocatoreView precedentemente create.
• Il costruttore di GiocatoreController registra l’istanza in costruzio-
ne come osservatore delle modifiche nel modello invocando sul ri-
ferimento all’istanza di GiocatoreModel passata come parametro il
metodo attach() e passando un riferimento a se stesso come argo-
mento nell’invocazione.

<<component>>
partita: Partita

2: elaborazione interna 1: comando()


3.1: notifyObserver()

3: aggiorna stato

giocatoreController: GiocatoreController giocatoreModel: GiocatoreModel


3.3: update(giocatoreModel)
3.2: update(giocatoreModel)
4: richiedi dati modello

5: richiedi dati modello


giocatoreView: GiocatoreView

Figura 5.21: Aggiornamento del modello e propagazione delle notifiche.

Il diagramma di comunicazione nella Figura 5.21 mostra le interazioni


principali fra gli oggetti che realizzano il pattern durante la fase di aggior-
namento del modello.
• giocatoreController, attraverso l’interfaccia IGiocatore (non mo-
strata nel diagramma) riceve un comando o una notifica dal compo-
nente di gestione della partita Partita.
Page 254 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

• giocatoreController elabora la richiesta ricevuta e richiede l’ag-


giornamento del modello (rappresentato dall’oggetto giocatoreModel)
invocando uno dei metodi della classe GiocatoreModel.

• giocatoreModel riceve la richiesta di aggiornamento, aggiorna il pro-


prio stato e successivamente inizia il processo di notifica a viste e
controllori precedentemente registrati attraverso l’invocazione del
metodo notifyObserver(). Questo metodo itera sulla lista dei ri-
ferimenti alle istanze di osservatori registrati e su ciascuno di essi
invoca il metodo update().

• Ciascuna vista o controllore, quando riceve la notifica di cambia-


mento nel modello interagisce direttamente con il modello stesso
per recuperare le informazioni aggiornate o modificate (messaggio
denominato richiedi dati modello).

Come ultima nota prima di concludere questo paragrafo, osserviamo


che l’approccio basato sull’utilizzo del pattern MVC consente di definire
l’interfaccia utente del sistema con grande libert e con una ragionevole
certezza di operare senza impatto sugli altri componenti del sistema. De-
finendo opportune sottoclassi di GiocatoreView, che rappresentano viste
diverse e indipendenti sul modello, è possibile costruire diverse tipolo-
gie di interfaccia utente utilizzate dal giocatore per l’interazione con il si-
stema. Potremo ad esempio costruire un’interfaccia puramente testuale
realizzata senza particolare cura estetica semplicemente per validare le
funzionalità del sistema, ma anche una GUI sofisticata realizzata sfrut-
tando tutte le funzionalit messe a disposizione dal linguaggio scelto per
l’implementazione (ad esempio una sofisticata interfaccia Swing nel caso
di implementazione in linguaggio Java).

5.3.3 Giocare in rete


Con la trattazione svolta nei paragrafi precedenti possiamo considerare
completa la descrizione del nostro sistema? La risposta è, ovviamente,
negativa. Infatti per esigenze di spazio abbiamo dovuto limitare la descri-
zione a un piccolo, ancorché significativo, insieme di diagrammi. Molti
altri avrebbero potuto essere presentati per descrivere in dettaglio tutte le
interazioni fra gli elementi del sistema.
Non possiamo però esimerci dal considerare un aspetto fondamentale
e finora trascurato: la descrizione dell’architettura fisica del sistema, con
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 255

particolare riferimento agli aspetti relativi alla distribuzione dell’applica-


zione. Uno dei requisiti principali del sistema è infatti quello di consenti-
re l’interazione a giocatori che risiedono su nodi diversi della rete. Finora
nella nostra trattazione non abbiamo, almeno esplicitamente, conside-
rato i problemi di distribuzione dei componenti del sistema sui nodi. In
realtà questi erano noti e sono sempre stati tenuti in conto nelle scelte di
progetto effettuate, che sono sufficientemente flessibili da consentirci ora
di affrontarli in modo abbastanza agevole.
La notazione UML ha sin dalle prime versioni messo a disposizione
strumenti per la descrizione dell’architettura fisica di un sistema, che —
pur sufficientemente espressivi— non sono stati mai molto utilizzati dai
progettisti. Il motivo è dovuto al perdurare della (deprecabile) consue-
tudine a trascurare la descrizione dell’architettura fisica di un sistema e
della sua evoluzione a tempo di esecuzione (runtime).
Gli estensori della versione 2 della specifica UML hanno fatto un note-
vole sforzo per fornire al progettista costrutti rigorosi ed espressivi per la
descrizione dell’architettura fisica e degli aspetti di distribuzione e dispie-
gamento dei componenti sui nodi. L’intento stato in parte raggiunto, ma
alcuni aspetti e problematiche fondamentali per descrivere il comporta-
mento di un sistema a runtime risultano tutt’ora di non facile (se non im-
possibile) descrizione: ad esempio non ci sono strumenti per descrivere
l’evoluzione di un processo (ovvero del codice con un proprio stato che in
esecuzione) su un nodo. Un grande passo avanti è comunque stato fatto
rispetto alle versioni precedenti della specifica UML e in questo paragra-
fo cercheremo di illustrare un esempio concreto di utilizzo di alcuni degli
strumenti messi a disposizione da UML 2 per la descrizione dell’archi-
tettura fisica della nostra applicazione di gestione della battaglia navale.

La situazione di riferimento è quella di utenti che eseguono sul pro-


prio calcolatore un’applicazione Java che consente il collegamento verso
il server responsabile dell’organizzazione della partita e della sua gestio-
ne. Il singolo utente comunica inizialmente al server la propria volontà
a partecipare al gioco. Successivamente richiede al server l’elenco dei
giocatori disponibili e ne contatta uno. I giocatori non comunicano mai
direttamente tra loro: la comunicazione è sempre mediata dal server di
gestione dell’organizzazione ed evoluzione della partita. Il protocollo di
comunicazione utilizzato per la comunicazione è Java RMI.
La Figura 5.22 individua i nodi del sistema e mostra il dispiegamen-
to sui nodi degli artifact che manifestano i componenti del sistema de-
scritti nei paragrafi precedenti. In particolare i nodi coinvolti sono PCUtente
Page 256 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

e gestorePartita.

• PCUtente ospita gli artifact associati ai componenti responsabili del-


la gestione dell’interfaccia e del campo di battaglia. Comunica con il
nodo gestore della partita in tutte le fasi di organizzazione e di gioco.

• gestorePartita ospita gli artifact associati ai componenti respon-


sabili della gestione della partita del campo di battaglia. Comunica
con i nodi associati ai giocatori in tutte le fasi di organizzazione e di
gioco.

<<component>> <<component>> <<component>>


Giocatore CampoBattaglia Partita

<<manifest>> <<manifest>> <<manifest>>

<<artifact>> <<artifact>> <<artifact>>


Giocatore CampoBattaglia Partita

<<deploy>> <<deploy>> <<deploy>> <<deploy>>

PCUtente gestorePartita: Server

<<executionEnvironment>> <<executionEnvironment>>

JavaVirtualMachine JavaVirtualMachine

Figura 5.22: Diagramma di deployment per la battaglia navale.

Il diagramma di deployment nella Figura 5.23 illustra a titolo di esem-


pio il caso di 3 giocatori connessi al sistema.

5.4 Codice
I diagrammi riportati nei paragrafi precedenti forniscono una descrizione
piuttosto accurata sia della logica dell’applicazione (espressa soprattutto
attraverso le classi che corrispondono al dominio del problema) sia degli
elementi implementativi (espressi nelle classi che realizzano i pattern e i
meccanismi di distribuzione).
nodoGiocatore1: PCUtente nodoGiocatore3: PCUtente

<<executionEnvironment>> <<executionEnvironment>>
JavaVirtualMachine JavaVirtualMachine
<<artifact>>
<<artifact>>
RMI Giocatore1
CampoBattaglia
<<protocol>>

<<artifact>>
RMI
<<artifact>> CampoBattaglia1
<<protocol>>
Giocatore

nodoGestorePartita: Server

<<executionEnvironment>>
JavaVirtualMachine
CAPITOLO 5. BATTAGLIA NAVALE IN RETE

<<artifact>>
nodoGiocatore2: PCUtente Partita

<<executionEnvironment>>
JavaVirtualMachine <<artifact>>
CampoBattaglia
<<artifact>>
Giocatore
RMI

<<artifact>> <<protocol>>
CampoBattaglia

Figura 5.23: Dispiegamento degli artifact sui nodi nel caso di 3 giocatori.
Page 257
Page 258 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

La codifica delle classi illustrate non presenta quindi a questo punto


particolari difficoltà o, comunque, richiede solo capacità che non rien-
trano negli argomenti coperti da questo testo. Nel seguito mostreremo
pertanto soltanto alcuni frammenti ritenuti particolarmente significativi.

5.4.1 Invarianti e precondizioni


Come primo esempio di codifica consideriamo la classe Posizione, mo-
strando in particolare un frammento di codice con l’implementazione de-
gli invarianti di classe definiti nel progetto del sistema. Ricordiamo che
un invariante di classe deve essere sempre verificato durante tutta la “vi-
ta” di ogni oggetto istanza della classe (si ritiene accettabile che un inva-
riante di classe non sia temporaneamente verificato durante l’esecuzione
dei metodi della classe). La classe Posizione è di tipo particolare poiché
non espone metodi che consentono la modifica dei valori dei suoi attri-
buti. In questo caso è sufficiente verificare gli invarianti al momento della
costruzione delle istanze della classe.

/**
* Gestione della posizione di una nave su un quadrante
*/
public class Posizione {
/** Coordinata iniziale della nave */
protected Coordinata inizio;
/** Coordinata finale della nave */
protected Coordinata fine;
/** Quadrante associato alla posizione */
protected Quadrante quadrante;
/** Nave associata alla posizione */
protected Nave nave;

/**
* Costruttore
*
* @param inizio coordinata di inizio della posizione
* @param fine coordinata di fine della posizione
* @param nave nave da dispiegare nella posizione
* @param quadrante quadrante di riferimento
* @throws PosizioneNonValidaException
*/
public Posizione(Coordinata inizio, Coordinata fine,
Nave nave, Quadrante quadrante)
throws PosizioneNonValidaException {
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 259

this.fine = fine;
this.inizio = inizio;
this.nave = nave;
this.quadrante = quadrante;
// check Invarianti
if (!invInizioFineOK())
throw new PosizioneNonValidaException(
"Posizione:: Verifica Invariante fallita." +
"Coordinate non valide: [inizio="+inizio+" fine="+fine);
if (!invConsistenzaCoordinateConQuadrante())
throw new PosizioneNonValidaException(
"Posizione:: Verifica Invariante fallita." +
"Coordinate non consistenti con dimensione quadrante: " +
" [inizio=" + inizio + " fine=" + fine);
if (!invConsistenzaCoordinateConNave())
throw new PosizioneNonValidaException(
"Posizione:: Verifica Invariante fallita." +
"Coordinate non consistenti con dimensione nave: " +
" [inizio=" + inizio + " fine=" + fine +
" nave=" + nave);
}
...
...
...
// Invarianti di classe

/**
* Verifica che ogni nave sia disposta orizzontalmente
* o verticalmente, che fine abbia numero di riga e/o
* colonna maggiore di inizio e che le coordinate siano
* caratterizzate sempre da valori non negativi
*/
protected boolean invInizioFineOK() {
int rigaInizio = inizio.getRiga();
int rigaFine = fine.getRiga();
int colInizio = inizio.getColonna();
int colFine = fine.getColonna();
boolean iniziofineOK1 = false;
iniziofineOK1 = (rigaInizio>=0 && rigaFine>=0 &&
colInizio>=0 && colFine>=0) &&
(((rigaInizio==rigaFine) &&
(colInizio<=colFine))
||((colInizio==colFine) &&
(rigaInizio<=rigaFine)));
return iniziofineOK1;
Page 260 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

/**
* Verifica che le coordinate associate alla posizione
* siano consistenti con la dimensione del quadrante
*/
protected boolean invConsistenzaCoordinateConQuadrante() {
int rigaInizio = inizio.getRiga();
int rigaFine = fine.getRiga();
int colInizio = inizio.getColonna();
int colFine = fine.getColonna();
int nRigheQuadrante = quadrante.getNumRighe();
int nColonneQuadrante = quadrante.getNumColonne();
boolean consistenzaCoordinateOK = false;
consistenzaCoordinateOK = (rigaInizio>=0 &&
rigaFine<nRigheQuadrante)
&& (colInizio>=0 && colFine<nColonneQuadrante);
return consistenzaCoordinateOK;
}

/**
* Verifica che le coordinate associate alla posizione siano
* consistenti con la dimensione della nave
*/
protected boolean invConsistenzaCoordinateConNave() {
int rigaInizio = inizio.getRiga();
int rigaFine = fine.getRiga();
int colInizio = inizio.getColonna();
int colFine = fine.getColonna();

boolean consistenzaCoordinateOK = false;


consistenzaCoordinateOK =
((rigaInizio==rigaFine) &&
(colFine-colInizio==nave.getDimensioni()-1))
||((colInizio==colFine) &&
(rigaFine-rigaInizio==nave.getDimensioni()-1));
}
}

Come esempio di codice per la verifica delle precondizioni associate


ai metodi consideriamo il seguente frammento della classe Quadrante:

/**
* Gestione di un quadrante di gioco
*/
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 261

public class Quadrante implements QuadranteInterface {

protected int numRighe;


protected int numColonne;
protected Collection posizioneFlotta;
protected Collection colpi;

/**
* @param colonne
* @param righe
* @throws QuadranteNonValidoException
*
*/
public Quadrante(int colonne, int righe)
throws QuadranteNonValidoException {
numColonne = colonne;
numRighe = righe;
// verifica l’invariante dimensioni
if (!invDimensioniQuadranteOK())
throw new QuadranteNonValidoException();
reset();
}

/** */
public void reset() {
colpi = new java.util.ArrayList();
posizioneFlotta = new java.util.ArrayList();
}
...
...
...
// Invarianti di classe e pre-condizioni

/**
* Verifica che il numero di righe e di colonne del quadrante
* coincidano
*/
protected boolean invDimensioniQuadranteOK() {
return (numRighe == numColonne);
}
/**
* Verifica se la coordinata di sparo specificata e‘ gi stata usata
* @param c coordinata di sparo
* @return true se non esiste uno sparo precedente alla coordinata
* specificata
Page 262 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

*/
protected boolean preSparoNonPresente(Colpo c) {
boolean preSparoOK = (!colpi.contains(c));
return preSparoOK;
}

/**
* Verifica che la coordinata di sparo specificata sia interna al
* quadrante
* @param c coordinata di sparo
* @return
*/
protected boolean preSparoCoordinataOK(Colpo c) {
return isInterna(c.getCasella());
}

...
...
...
/**
* @param c
* @return
*/
public boolean isInterna(Coordinata c) {
int riga = c.getRiga();
int colonna = c.getColonna();
return (riga >= 0 && riga <= numRighe - 1 &&
colonna >= 0 && colonna <= numColonne - 1);
}
...
...
...
/**
* Riceve uno sparo alla coordinata specificata come parametro e
* verifica se lo sparo ha colpito una delle navi del quadrante.
* In caso positivo aggiorna il numero dei colpi subiti dalla nave
* @param c coordinata di sparo
* @return true se una nave \ stata colpita
* @throws SparoNonValidoException
*/
public boolean sparo(Coordinata c) throws SparoNonValidoException {
Colpo colpo = new Colpo(c);
// check prima pre-condizione del metodo sparo()
if (!preSparoCoordinataOK(colpo)) {
throw new SparoNonValidoException(
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 263

"Quadrante::sparo::Eccezione::Coordinate sparo non valide. "


+ "Coordinate sparo fuori dal quadrante: " + c);
}
// check seconda pre-condizione del metodo sparo()
if (preSparoNonPresente(colpo)) {
colpi.add(colpo);
if (colpito(c)) {
Nave n;
try {
n = getNave(c);
n.setColpiSubiti(n.getColpiSubiti()+1);
return true;
} catch (NaveNotFoundException e) {
// Non dovrebbe succedere, visto che colpito(c)
// ha restituito true
return false;
} catch (NumeroColpiNonValidoException e) {
// Non dovrebbe succedere, visto che
// preSparoNonPresente() e‘ true
return false;
}
} else {
return false;
}
} else {
throw new SparoNonValidoException(
"Quadrante::sparo::Eccezione::Coordinate sparo non valide. "
+ "Esiste gia‘ uno sparo nella posizione specificata");
}
}
...
...
...

5.4.2 Test di unità


Il test di unità delle classi del sistema è stato effettuato utilizzando JUnit,
un framework di supporto all’esecuzione di test di unità in ambiente Java
creato da Kent Beck insieme a Erich Gamma e ormai largamente diffuso
nella comunità degli sviluppatori. A titolo di esempio mostriamo alcuni
frammenti di casi di test JUnit definiti sulle classi Posizione e Quadrante.

package battaglianavale.test;
import battaglianavale.campoBattaglia.*;
Page 264 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

import battaglianavale.exception.*;
import junit.framework.TestCase;
import org.apache.log4j.Logger;

/**
*
*/
public class CampoBattagliaTest extends TestCase {
/** Logger for this class */
private static final Logger logger = Logger
.getLogger(CampoBattagliaTest.class);
/**
*
*/
protected Quadrante q1;

protected Nave sottomarino1 = new Sottomarino();


protected Nave sottomarino2 = new Sottomarino("sott123");
protected Nave sottomarino3 = new Sottomarino();
protected Nave sottomarino4 = new Sottomarino();
protected Nave cacciatorpediniere1 = new CacciaTorpediniere();
protected Nave incrociatore1 = new Incrociatore();
protected Nave corazzata1 = new Corazzata();
protected Nave portaerei1 = new Portaerei();
...
...
/**
* Setup quadrante di gioco utilizzato nei test
* @throws Exception
*/
protected void setUp() throws Exception {
super.setUp();
try {
q1 = new Quadrante(10,10);
} catch (QuadranteNonValidoException e) {
fail("Quadrante non valido: " + q1);
}
Nave[] navi =
new Nave[] {sottomarino1, sottomarino2, sottomarino3,
sottomarino4, cacciatorpediniere1, corazzata1,
portaerei1, incrociatore1 };
for (int i=0; i<navi.length; i++) {
if (logger.isDebugEnabled()) {
logger.debug("testNave() - : " + navi[i]);
}
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 265

}
}
...
...
...

/**
* Test posizioni valide/non valide
*/
public void testPosizione() {

assertTrue(
isPosizioneValida(new Coordinata(5,5), new Coordinata(5,5),
sottomarino1, q1));
assertTrue(
isPosizioneValida(new Coordinata(5,5), new Coordinata(6,5),
cacciatorpediniere1, q1));
assertTrue(
isPosizioneValida(new Coordinata(5,5), new Coordinata(7,5),
incrociatore1, q1));
assertTrue(
isPosizioneValida(new Coordinata(5,5), new Coordinata(8,5),
corazzata1, q1));
assertTrue(
isPosizioneValida(new Coordinata(5,5), new Coordinata(9,5),
portaerei1, q1));
...
...
...
assertFalse(
isPosizioneValida(new Coordinata(-5,5), new Coordinata(5,5),
sottomarino1, q1));
assertFalse(
isPosizioneValida(new Coordinata(5,5), new Coordinata(6,5),
sottomarino1, q1));
...
...
...
}
...
...
...

/**
* Test di sparo e di affondamento flotta
Page 266 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

*/
public void testSparo() {
q1.reset();
Coordinata cSparo;
try {
q1.aggiungiNave(sottomarino1,
new Coordinata(0,0), new Coordinata(0,0));
q1.aggiungiNave(cacciatorpediniere1,
new Coordinata(1,0), new Coordinata(1,1));
q1.aggiungiNave(incrociatore1,
new Coordinata(5,5), new Coordinata(5,7));
} catch (ConflittoPosizioneException e) {
logger.error("testSparo() - Eccezione :" + e.getMessage());
fail("testSparo FAILED");
} catch (PosizioneNonValidaException e) {
logger.error("testSparo() - Eccezione :" + e.getMessage());
fail("testSparo FAILED");
}
try {
logger.debug("Quadrante: " + q1);
logger.debug("Sparo " +
(cSparo=new Coordinata(0,0)) + ": " + q1.sparo(cSparo));

} catch (SparoNonValidoException e) {
logger.error("testSparo() - Eccezione :" + e.getMessage());
fail("testSparo FAILED");
}
// Verifica che sia sollevata una eccezione se la coordinata
// di sparo gi\ stata usata
try {
logger.debug("Sparo " +
(cSparo=new Coordinata(0,0)) + ": " + q1.sparo(cSparo));
fail("testSparo FAILED");
} catch (SparoNonValidoException e) {
logger.error("testSparo() - Eccezione :" + e.getMessage());
}

try {
logger.debug("Quadrante: " + q1);
logger.debug("Sparo " +
(cSparo=new Coordinata(1,0)) + ": " + q1.sparo(cSparo));
logger.debug("Sparo " +
(cSparo=new Coordinata(1,1)) + ": " + q1.sparo(cSparo));
logger.debug("Sparo " +
(cSparo=new Coordinata(5,5)) + ": " + q1.sparo(cSparo));
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 267

logger.debug("Sparo " +
(cSparo=new Coordinata(5,6)) + ": " + q1.sparo(cSparo));
logger.debug("Sparo " +
(cSparo=new Coordinata(5,7)) + ": " + q1.sparo(cSparo));
} catch (SparoNonValidoException e) {
logger.error("testSparo() - Eccezione :" + e.getMessage());
fail("testSparo FAILED");
}
logger.debug("Quadrante: " + q1);
logger.debug("Flotta affondata: " + q1.flottaAffondata());

assertTrue(q1.flottaAffondata());
}
// -----------------------------------------------
// Metodi di supporto
//

public boolean isPosizioneValida(Coordinata inizio, Coordinata fine,


Nave n, Quadrante q) {
Posizione p1 = null;
try {
p1 = new Posizione(inizio, fine, n, q);
} catch (PosizioneNonValidaException e) {
logger.error("Eccezione: " + e);
return false;
}
return true;
}

} // end of class CampoBattagliaTest

5.4.3 Macchina a stati per la gestione della partita

Come ultimo esempio di codice mostriamo l’implementazione della mac-


china a stati per la gestione dell’evoluzione della partita. In particolare
verranno illustrati frammenti dell’implementazione delle classi Partita,
PartitaState, InDefinizione e AttesaSparo.

Classe Partita

La classe Partita riceve i comandi inviati dai due giocatori e ne dele-


ga la gestione allo stato corrente, il quale elabora il comando ricevuto
Page 268 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

e, sulla base del risultato dell’elaborazione, può richiedere a Partita la


transizione verso un nuovo stato.

/**
* Gestione della partita
*/
public class Partita implements PartitaInterface {

/** Caratteristiche della partita concordate tra i due giocatori */


protected PartitaProperties partitaProp;

/** Interfacce utenti dei giocatori */


protected Collection IU = new java.util.ArrayList();

/** quadranti di gioco */


protected Collection quadranti = new java.util.ArrayList();
/** Stato corrente della partita */
protected PartitaState statoPartita;

protected final PartitaState STATO_PARTITACONCLUSA =


new PartitaConclusa(this);
protected final PartitaState STATO_INDEFINIZIONE =
new InDefinizione(this);
protected final PartitaState STATO_ATTESASPARO =
new AttesaSparo(this);
protected final PartitaState STATO_ATTESASPAROGIOCATORE1 =
new AttesaSparoGiocatore1(this);
protected final PartitaState STATO_ATTESASPAROGIOCATORE2 =
new AttesaSparoGiocatore2(this);

/**
* Costruttore
* @param partitaProp Caratteristiche della partita
* concordate tra i due giocatori
* @throws QuadranteNonValidoException
*/
public Partita(PartitaProperties partitaProp)
throws QuadranteNonValidoException {
this.partitaProp = partitaProp;
...
...
...
setState(STATO_INDEFINIZIONE);
}
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 269

// Metodi definiti in PartitaInterface


/**
* Comunicazione del completamento dello schieramento da parte
* di un giocatore
* @param giocatore identificatore del giocatore
* @param quadranteGiocatore quadrante del giocatore
* che ha completato lo schieramento
*/
public void schieramentoCompleto(int giocatore,
Quadrante quadranteGiocatore) {
statoPartita.schieramentoCompleto(giocatore, quadranteGiocatore);
}

/**
* Comunicazione di uno sparo da parte di un giocatore
* @param giocatore
* @param c coordinata di sparo
*/
public void sparo(int giocatore, Coordinata c) {
statoPartita.sparo(giocatore, c);
}

/**
* Comunicazione della rinuncia da parte di un giocatore
* @param giocatore identificatore del giocatore
*
*/
public void rinuncia(int giocatore) {
statoPartita.rinuncia(giocatore);
}
//-------------------------------------------------------------------
// Metodi di supporto

/**
* Imposta uno stato per la partita
* @param ps stato della partita da impostare
*
*/
public void setState(PartitaState ps) {
statoPartita = ps;
statoPartita.enterState();
}
}

...
Page 270 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

...
...
} // end of class Partita

Classe PartitaState

La classe PartitaState rappresenta lo stato generico della partita. È una


classe astratta specializzata dalle varie sottoclassi che rappresentano gli
stati concreti della macchina a stati. Pur essendo una classe astratta for-
nisce per alcune operazioni un’implementazione concreta (eventualmen-
te vuota) che viene ereditata dalle sue sottoclassi. Ciò è particolarmente
utile nel caso sia necessario definire un comportamento comune in tut-
ti gli stati della macchina a seguito di un comando inviato da un utente.
Un esempio di questo approccio è fornito dal metodo rinuncia(), la cui
implementazione definita nella classe PartitaState non viene ridefinita
nelle sottoclassi concrete. Tutte le istanze delle sottoclassi di PartitaState
risponderanno quindi nello stesso modo alla comunicazione di una ri-
nuncia alla partita da parte di un giocatore.

/**
*
*/
public abstract class PartitaState {
/** Contesto della classe State */
protected Partita partitaContext = null;

protected boolean turnoCorrenteCompletato = false;

/** Numero di spari effetutati nel corrente turno di gioco */


protected int nSpariInTurnoCorrente = 0;

protected String nomeStatoPartita = null;

/**
* Costruttore
* @param partitaContext riferimento alla partita
*/
public PartitaState(Partita partitaContext) {
this.partitaContext = partitaContext;
}

/**
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 271

* Metodo eseguito da context prima dell’ingresso in uno stato


*/
protected abstract void enterState();

/**
* Definisce il comportamento di default nel caso di comunicazione
* di schieramento completato da parte di un giocatore
* @param giocatore
* @param quadranteGiocatore
*/
public void schieramentoCompleto(int giocatore,
Quadrante quadranteGiocatore) {
;
}

/**
* Definisce il comportamento di default nel caso di comunicazione
* di rinuncia da parte di un giocatore
* @param giocatore
*
*/
public void rinuncia(int giocatore) {

int perdente = giocatore;


int vincente = (perdente==1) ? 2 : 1;
partitaContext.getIU(vincente).
messaggio("Hai vinto la partita per rinuncia dell’avversario");
partitaContext.getIU(perdente).
messaggio("Hai concesso partita vinta all’avversario");
partitaContext.setState(partitaContext.STATO_PARTITACONCLUSA);
}

/**
* Definisce il comportamento di default a seguito di uno sparo
* effettuato da un giocatore
* @param giocatore
* @param c coordinata di sparo
*
*/
public void sparo(int giocatore, Coordinata c) {
;
}
//--------------------------------------------
// Metodi di supporto
Page 272 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

...
...
...
} // end of class PartitaState

Classe InDefinizione
La classe InDefinizione rappresenta un primo esempio di sottoclasse
concreta di PartitaState. In tale classe viene ridefinito il metodo schie-
ramentoCompleto(), che viene invocato a seguito della comunicazione
del completamento del posizionamento delle navi sul quadrante da par-
te di un giocatore. Quando sono completati gli schieramenti di entrambi i
giocatori si richiede a Partita la transizione verso il nuovo stato AttesaSparo.

/**
* Implementazione dello stato InDefinizione della
* macchina a stati di gestione della partita
*
*/
public class InDefinizione extends PartitaState {

protected boolean schieramentoCompletatoGiocatore1 = false;

protected boolean schieramentoCompletatoGiocatore2 = false;

/**
* @param partitaContext
*/
public InDefinizione(Partita partitaContext) {
super(partitaContext);
this.nomeStatoPartita = "IN_DEFINIZIONE";

/**
*
*/
protected void enterState() {
schieramentoCompletatoGiocatore1= false;
schieramentoCompletatoGiocatore2 = false;
}

/**
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 273

* Definisce il comportamento nel caso di comunicazione


* di schieramento completato da parte di un giocatore
* @param giocatore
* @param quadranteGiocatore
*/
public void schieramentoCompleto(int giocatore,
Quadrante quadranteGiocatore) {
try {
partitaContext.setQuadrante(giocatore, quadranteGiocatore);
setSchieramentoCompletato(giocatore);
} catch (QuadranteNonValidoException e) {
// comunica messaggio di errore a giocatore
partitaContext.getIU(giocatore).messaggio(e.getMessage());
} catch (OperazioneNonValidaException e) {
// comunica messaggio di errore a giocatore
partitaContext.getIU(giocatore).messaggio(e.getMessage());
}
}
//-----------------------------------------------------------------
// Metodi di supporto

/**
* @return true se gli schieramenti dei due giocatori
* sono stati completati
*
*/
protected boolean isSchieramentiCompletati() {
return (schieramentoCompletatoGiocatore1
&& schieramentoCompletatoGiocatore2);
}

/**
* Imposta a completato lo schieramento del giocatore specificato
* Se sono stati completati entrambi gli schieramenti richiede alla
* partita la transizione verso lo stato ATTESA_SPARO
* @param giocatore
*/
protected void setSchieramentoCompletato(int giocatore) {
if (giocatore==1) schieramentoCompletatoGiocatore1 = true;
if (giocatore==2) schieramentoCompletatoGiocatore2 = true;
if (isSchieramentiCompletati()) {
partitaContext.setState(partitaContext.STATO_ATTESASPARO);
}
}
} // end of class InDefinizione
Page 274 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

Classe AttesaSparo
La classe AttesaSparo rappresenta lo stato in cui si attende lo sparo da
parte di uno dei due giocatori. Quando viene ricevuto uno sparo da uno
dei due giocatori la classe Partita invoca il metodo sparo() il quale, dopo
aver verificato l’eventuale affondamento della flotta del giocatore avver-
sario, richiede la transizione verso lo stato di attesa del prossimo sparo del
giocatore avversario (implementato dalle classi AttesaSparoGiocatore1
e AttesaSparoGiocatore2).

/**
*
* Implementazione dello stato AttesaSparo della
* macchina a stati di gestione della partita
*/
public class AttesaSparo extends PartitaState {

/**
* Costruttore
* @param partitaContext
*/
public AttesaSparo(Partita partitaContext) {
super(partitaContext);
this.nomeStatoPartita = "ATTESA_SPARO";
}

/**
* Metodo eseguito prima dell’ingresso nello stato
*/
protected void enterState() {
if (isTurnoCompletato()) {
if (partitaContext.checkAffondamentoFlotta()) {
partitaContext.comunicaAffondamentoFlotta();
partitaContext.setState(partitaContext.STATO_PARTITACONCLUSA);
partitaContext.getIU(1).disabilitaSparo();
partitaContext.getIU(2).disabilitaSparo();
return;
}
}
// Abilita gli spari sulle interfacce dei due giocatori
partitaContext.getIU(1).abilitaSparo();
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 275

partitaContext.getIU(2).abilitaSparo();
}
/**
* Definisce il comportamento a seguito di uno sparo
* effettuato da un giocatore
* @param giocatore
* @param c coordinata di sparo
*
*/
public void sparo(int giocatore, Coordinata c) {

int avversario = (giocatore==1) ? 2 : 1;

boolean sparoOk = sparoHelper(giocatore, avversario, c);


if (sparoOk) {
// Imposta lo stato successivo
if (giocatore == 1) {
partitaContext.setState(
partitaContext.STATO_ATTESASPAROGIOCATORE2);
} else if (giocatore ==2) {
partitaContext.setState(
partitaContext.STATO_ATTESASPAROGIOCATORE1);
}
// Aggiorna il turno
updateTurno();
}
}
} // end of class AttesaSparo

5.5 Esercizi
Pausa
Può succedere che durante una partita a battaglia navale uno dei gioca-
tori debba interrompere il gioco, ad esempio, per rispondere al telefono.
In questo caso non si vuole lasciare semplicemente l’avversario in atte-
sa, ma si desidera che la pausa sia gestita adeguatamente. Specificate e
quindi progettate una variante del gioco che contempli la gestione del-
le pause. Supponiamo che ogni giocatore abbia a disposizione nell’in-
terfaccia di gioco i comandi “inizio pausa” e “fine pausa”. Sono previste
due opzioni. Nel primo caso l’avversario viene semplicemente avvertito
che il giocatore è momentaneamente indisponibile. Nel secondo caso il
Page 276 CAPITOLO 5. BATTAGLIA NAVALE IN RETE

calcolatore “prende il posto” del giocatore indisponibile, senza avvertire


l’avversario. Durante queste pause il calcolatore può giocare in modo as-
solutamente banale, effettuando spari in posizioni a caso tra quelle non
ancora bersagliate.

Posizione dei sommergibili

Modificare tutti i semilavorati realizzati durante lo sviluppo in modo da


accogliere il seguente nuovo requisito relativo alla dislocazione delle na-
vi nel quadrante durante la battaglia navale. Dev’essere possibile che la
stessa posizione del quadrante sia occupata da un sommergibile e da una
nave di superficie. Un colpo sparato nella casella occupata da più navi
affonda il sottomarino e danneggia l’altra nave.

Battaglia navale dinamica

Modificate tutti i semilavorati realizzati durante lo sviluppo in modo da


accogliere il seguente nuovo requisito relativo alla dislocazione delle navi
nel quadrante durante la battaglia navale. Ad ogni turno, dopo che l’av-
versario ha sparato, ciascun giocatore può opzionalmente spostare un
massimo di due tra le proprie navi non danneggiate. Se una nave vie-
ne spostata, la nuova posizione deve essere tale che ciascuna delle caselle
occupate nella nuova posizione sia adiacente a una qualunque casella oc-
cupata nella posizione precedente. Qualora questo requisito risultasse in
conflitto con uno o più degli altri requisiti, lasciamo al lettore la facoltà di
modificarli opportunamente per ottenere una specifica coerente.
Page 277

Bibliografia

BMR+96 Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad,


Michael Stal, Peter Sommerlad e Michael Stal. Pattern-Oriented Soft-
ware Architecture, Volume 1, A System of Patterns. John Wiley & Sons,
1996.

Boo93 Grady Booch. Object-Oriented Analysis and Design with Applications.


Addison-Wesley, seconda edizione, 1993.

Fow03 Martin Fowler. UML Distilled. Addison Wesley, terza edizione, 2003.

GHJV95 Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides.


Design Patterns: Elements of Reusable Object-Oriented Software.
Addison-Wesley, 1995.

GJ97 Carlo Ghezzi e Mehdi Jazayeri. Programming Language Concepts.


John Wiley & Sons, terza edizione, 1997.

HN96 David Harel e Amnon Naamad. The STATEMATE Semantics of State-


charts. ACM Transactions on Software Engineering and Methodology,
5(4):293–333, Ottobre 1996.

Kru95 Philippe Kruchten. The 4+1 View Model of Architecture. IEEE Soft-
ware, 12(6):42–50, 1995.

LC03 Gary T. Leavens and Yoonsik Cheon. Design by Contract with JML.
Rapporto interno, 2003.

LG00 Barbara Liskov e John Guttag. Program Development in Java: Ab-


straction, Specification, and Object-Oriented Design. Addison-Wesley,
2000.
Page 278 Bibliografia

Mar03 Robert Cecil Martin. Agile Software Development: Principles, Patterns,


and Practices. Prentice Hall, 2003.

Mey00 Bertrand Meyer. Object-Oriented Software Construction. Prentice Hall,


seconda edizione, 2000.

WK03 Jos Warmer e Anneke Kleppe. The Object Constraint Language: Getting
Your Models Ready for MDA. Addison-Wesley, seconda edizione, 2003.
Page 279

Indice analitico

A attributo statico, 4
aggregazione, 7 metodo, 2, 3
analisi dei requisiti, 73, 110, 196 classe astratta, 91, 109
architettura logica, 211, 213 collaborazione, 149
associazione, 5 chat: registrazione partecipante, 149
classe di, 8 chat: registrazione partecipanti, 150
attore, 15 RMI, 159, 160
ruolo, 15 componente
azione, 23, 27 interfaccia, 31
composizione, 7
B comunicazione, 21
barra di attivazione, 19
D
C design pattern, 150, 220, 222, 225
casi d’uso, 15, 16, 74, 82, 83, 111 adapter, 193
biblioteca: aggiornamento oggetto-oggetto composto, 44
registro utenti, 77 diagramma degli oggetti, 11
biblioteca: aggiornamento automi a stati finiti, 68
situazione, 77 diagramma degli stati, 22, 23
biblioteca: bibliotecario, 80 classe Documento, 104
biblioteca: registrazione utente, 77 classe GestoreAscensore, 125
biblioteca: restituzione, 77 classe Motore, 121
biblioteca: ricerche bibliografiche, 81 classe Partita, 202
biblioteca: richiesta prestito, 80 classe Porta, 123
biblioteca: verifica registrazione, 77 classe Prestito, 102
estensione, 16, 17 classe PulsanteAscensore, 122
generalizzazione, 17 classe PulsanteSalita, 121
post-condizione, 16 classe Sensore, 122
pre-condizione, 16 classe UtenteRegistrato, 102
trigger, 16 myAir: livelli cliente, 54
classe, 2 diagramma dei casi d’uso, 15
attributo, 2 ascensore, 112
attributo derivato, 3 automi a stati finiti, 65
Page 280 Indice analitico

battaglia navale, 196, 197 rivendita automobili, 46


bibioteca, 75 valutazione polinomi, 43
chat, 132 diagramma di collaborazione, 225
controllo accessi, 164 battaglia navale, 220
myAir, 50 diagramma di comunicazione, 226, 227
diagramma dei componenti, 30 controllo accessi: scenario accesso
battaglia navale, 211 utente registrato, 170
chat: comunicazione RMI, 158 diagramma di deployment, 33
chat: definizione interfacce, 139 battaglia navale, 230
chat: dettaglio componenti, 155 chat, 162
chat: dettaglio delle interfacce, 141 diagramma di interazione, 18
chat: dettaglio interfacce, 160 comunicazione, 21
controllo accessi: accesso al profilo sequenza, 18
di un servizio, 178 diagramma di sequenza, 83, 220
controllo accessi: componenti ascensore: chiusura porte, 113
per filtraggio richieste, 174 battaglia navale, 206
controllo accessi: elementi battaglia navale: dispegamento navi, 217
principali, 171 battaglia navale: sparo, 209
controllo accessi: processo biblioteca: registrazione utente, 88, 89
di autenticazione, 179, 187 biblioteca: restituzione, 85
diagramma dei package, 12 chat: creazione stanza, 144
battaglia navale, 211 chat: inizializzazione chat, 145
diagramma delle attività, 27 chat: interazione RMI, 159
battaglia navale, 206 chat: registrazione moderatore, 142
battaglia navale: connessione, 208 chat:interazione partecipanti, 153
battaglia navale: fase di gioco, 206 chat:invio messaggio, 150
biblioteca: restituzioni, 83, 85, 87 chat:registrazione partecipante, 147
distributore di merendine, 56 chat:uscita partecipante, 152
megaGym: pagamenti palestra, 64 controllo accessi: accesso a risorsa
myAir: emissione biglietto, 54 web, 174
diagramma delle classi, 2, 89, 115 controllo accessi: autorizzazione
battaglia navale, 200 dinamica, 187
ascensore, 115, 117 controllo accessi: estrazione del profilo
automi a stati finiti, 66 di un servizio, 178
battaglia navale, 198, 213, 215, 219, controllo accessi: scenario
220, 222, 223 autenticazione, 181
biblioteca, 85, 90, 92, 94, 95, 97, myAir: accredito miglia, 52
100, 105–108 myAir: registrazione utente, 51
CAD tridimensionale, 45 myAir: richiesta premio, 52
controllo accessi: modello diagramma di struttura interna, 32
complessivo, 167 distributore di meredine, 58
distributore di meredine: controllo, 58 dipendenza esistenziale, 7
distributore di meredine: dati, 56 dominio del problema, 73, 74, 85, 89, 110,
megaGym, 60 111, 115, 196, 198
myAir, 54
Indice analitico Page 281

E attributo, 3
ereditarietà, 9 relazione, 5
multipla, 9
singola, 9 O
sottoclasse, 9 OCL, 34, 97, 98, 102, 117, 121, 203–205
superclasse, 9 post-condizione, 37
pre-condizione, 37
G oggetto, 2, 5, 11
generalizzazione, 9 operazione, 2

I P
implementazione, 230 package, 3, 12, 13
interfaccia, 10, 11 interfaccia, 13
invariante di classe, 35 namespace, 13
classe Abbonamento, 62 partecipante, 18
classe Automa, 69 pattern architetturali, 150
classe Cliente, 62 post-condizione, 102, 121
classe Documento, 98 classe Cliente, 63
classe Edificio, 119 classe Palestra, 64
classe Esecuzione, 69, 70 pre-condizioni, 102, 121
classe Interfaccia, 204 classe Cliente, 63
classe Nave, 204 classe Palestra, 64
classe Pagamento, 63 progetto di dettaglio, 213, 215, 217, 219,
classe Palestra, 61 220, 222, 223, 225–230
classe Partita, 204
classe Piano, 118, 119
classi Porta e Motore, 120
classe Posizione, 205 R
classe Prestito, 98 requisiti utente, 73, 74, 110, 111, 196,
classe PulsanteAscensore, 118 198, 200, 210
classe Quadrante, 203, 204 analisi, 74, 111, 198, 200
classe Scaffale, 98 RMI, 159, 160
classe Settore, 98 ruolo, 5
classe StringaIngresso, 69 navigabilità, 5
visibilità, 5
M
messaggi S
scambio di, 19 scenario, 15, 74, 82, 111
messaggio schema entità-relazioni, 46
messaggio iniziale, 20 rivendita automobili, 46
tipi di, 20 sequenza, 18
metodo costruttore, 100 cicli e condizioni, 20
modello concettuale frame di interazione, 21
chat, 133 linea di vita, 19
molteplicità specifica, 98, 120, 205
Page 282 Indice analitico

stato visibilità, 3
attività, 24 regole, 3, 4
finale, 23 vista
history, 26 a runtime, 136
iniziale, 23 architetturale, 135
di deployment, 190
T logico-funzionale, 135
transizione, 23 sul codice, 135
condizione, 23 vista logico-funzionale
evento, 23 chat, 137

V
vincoli, 97, 98, 117, 203–205

View publication stats

Potrebbero piacerti anche