Libro v2
Libro v2
net/publication/344569853
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:
All content following this page was uploaded by Luigi Lavazza on 09 October 2020.
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
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
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
Capitolo 1
Introduzione a UML 2
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
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:
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
Persona
- nome: String
- cognome: String
- dataNascita: Date + marito
- numPersone: int 0..1
+ siSposa(p: Persona): boolean
+ compieAnni(d: Date): boolean
+ moglie
0..1
matrimonio
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
1 Telaio
Automobile 1 Motore
1
4 Ruota
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
Veicolo Veicolo
Terrestre APedali
Triciclo Bicicletta
Disegno Figura
1..* 1..*
Geometrica
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 Vector
- vertici: List
+ add(o: Object): boolean
List + get(index: int): Object
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
Giovanni: Persona
Mario: Persona
1.1.3 Package
interfaccia
Grafica
PrelievoSoldi
Saldo
ListaMovimenti
cliente
RicaricaTelefono
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
PrelievoSoldi
cliente
<<include>>
SelezioneVoce
Interfaccia
AcquistoCon
CartaDiCredito
<<extend>>
AcquistoProdotto
Extension Points
modalità di pagamento
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
Volo
Cliente
Richiesta
aggiungi
conferma
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
prenota
preferenze
disponibilità
Prenotazione
associa
messaggio sincrono
messaggio asincrono
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-
prenota
preferenze
disponibilità
Prenotazione
associa
1.2: disponibilità
1.3: <<create>>
Sede Prenotazione
1.3.1: associa
Automobile
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).
Stato
entry/ attività1
exit/ attività2
do/ attività3
evento1 [condizione]/ attività4
evento2/ attività5
Attesa Warning
entry/visualizza
scatto [non luce]
scatto [luce]
spegni
Memorizzazione
spegni
do/memorizza
Warning Attesa
spegni
entry/visualizza scatto [non luce]
Memorizzazione
On
ConvertiEuro ConvertiDollari
euro dollari
H
spegni accendi
Off
1.5 Attività
Scegli Libro
Scegli Scegli
Pagamento Spedizione
ImmettiDati
EvadiOrdine
do tra le attività del Cliente, quelle della Banca e quelle dell’istituto che
emette la CartaDiCredito.
Scegli
Pagamento
PagaCon PagaCon
Bonifico CartaDiCredito
PagaCon
CartaDiCredito
Ricevi
Invia Conferma
Richiesta
Richiesta
AltraCarta
Aspetta
5 min
PagaCon
Pagamento EvadiOrdine
CartaDiCredito
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
<<component>>
Interfaccia
Utente
<<component>>
<<delegate>> ServerInformazioni
ISelezione
IDisplay
Prodotti
SceltaProdotto IContaSoldi
Distributore
Caffè Controllo
Pagamento
IGettoniera
SelezioneBevanda
ILatte ICaffè
ISelezione IDisplay
Prodotti
<<delegate>> <<delegate>>
1
Prodotto
IContaSoldi
<<delegate>>
1
Soldi
ILatte <<delegate>>
<<delegate>> 1
Latte
IGettoniera
ICaffè
<<delegate>> 1
Caffè
DistributoreCaffè
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
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
...
context Persona
inv: anni >= 0
context Persona
inv: self.azienda.fatturato > 10.000
context Persona
inv: self.azienda = self.superiore.azienda
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)
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
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
context Prestito
inv: self.banca.clienti->includes(self.intestatario)
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)
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:
Capitolo 2
Esercizi introduttivi
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
Oggetto Oggetto
Composto * 1..*
Modello3D
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
annoImmatricolazione
numChilometri
dataRevisione
Carta
DiIdentità descrizione
colore ID nome
targa
carburante
numTelaio
cilindrata 1..n
inclusione Optional
0..n
Automezzo
0..1
dataInizio codice
Camion Van 1..1
dataStipula ammontare
stipula Dilazione
cognome
Indirizzo
numero
CAP
via
città
CartaDiIdentità Dilazione
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
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.
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
Utente
Cliente
Situazione
miglia
cambioPunti
Accredito
Descrizione
Cliente Situazione
Premio
Utente
Registrato
premio
punti
cambioPunti
Premio
premioDisponibile
Ottenuto
saldoInsufficiente
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
BuonoSconto
- ammontare: int
-.negozio: String Accredito PremioOttenuto
- codiceVolo: String - descrizione: String
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
Aggiorna PremioNon
Situazione Disponibile
Premio
Disponibile
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
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]
Identifica
Utente
Leggi
Caratteristiche
Visualizza
Consiglio
Scegli
Prodotto
OK selezione
Memorizza
Selezione
Controlla
CreditoEDisp.
insuff. suff.
Segnala Registra
CreditoInsuf. Ordine
Gestisci
Credito
Eroga
Prodotto
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
IScheda
Autenticazione
IDisplay
Interazione
Controllore
Erogazione ITastierino
IErogatore
<<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
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
Soluzione Il diagramma delle classi del sistema per la gestione della pa-
lestra è mostrato nella Figura 2.17.
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
context Palestra
inv: Palestra.allInstances()->size = 1
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)
Pagamento
VerificaImporto
[not importoSufficiente]
Richiesta
ImportoCorretto
DaResto
[importoEccessivo]
Registra
Pagamento
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
• Q è un insieme di stati;
• δ ∗ (q, ) = q
Definizione
Automa Inserimento
StringaIngresso
<<include>>
Esecuzione
Automa
<<include>>
Esecuzione
Utente
PassoPasso
<<include>>
Figura 2.19: Diagramma dei casi d’uso per la simulazione di automi a stati finiti.
+ statoIniziale
+ 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
0
q0 q1 1
S0: Simbolo
s = ‘0’
statoIniziale
: Automa : Alfabeto
S1: Simbolo
s = ‘1’
statoProssimo
T1: Transizione carattereCorrente
statoCorrente
context Automa
Page 76 CAPITOLO 2. ESERCIZI INTRODUTTIVI
inv: self.stato->select(finale=true)->notEmpty()
context Automa
inv: self.stato->includes(self.statoCorrente)
context StringaIngresso
inv: self.simbolo->forAll(s: Simbolo |
self.esecuzione.automa.alfabeto->includes(s))
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
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
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
Esame di informatica
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
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.).
• Il sistema dovrà altresı̀ gestire l’elenco degli utenti con tutte le infor-
mazioni che possano risultare utili.
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
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.
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
Sollecito
<<include>> <<include>>
<<include>>
Bibliotecario
<<include>>
<<include>> <<include>>
Disabilitazione
Utente
Figura 3.2: Diagramma dei casi d’uso: funzionalità richieste dal bibliotecario.
Ricerca
Bibliografica
Utente Bibliotecario
Restituzione
Aggiorna
Situazione
Verifica
Scadenze
• 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.
Interfaccia SistemaGestione
Bibliotecario Biblioteca
Bibliotecario
aggiornaSituazione
aggiornaSituazione
controllaScadenze
controllaScadenze
ambiente
+ 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
Utente Bibliotecario
Verifica
Restituzione Restituzioni
[not restituzioniPresenti]
[restituzioniPresenti]
Aggiorna
Situazione
Verifica
Scadenze
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
[not restituzioniPresenti]
[restituzioni
Presenti] [chiusuraBiblioteca]
Aggiorna
Situazione
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.
richiestaRegistrazione
richiestaRegistrazione
richiestaRegistrazione
verificaRegistrazione
not registrato
effettuaRegistrazione
IDUtente
IDUtente
IDUtente
IDUtente
richiestaRegistrazione
richiestaRegistrazione
richiestaRegistrazione
verificaRegistrazione
registrato(IDUtente)
KO(IDUtente)
KO(IDUtente)
KO(IDUtente)
UtenteRegistrato RegistroUtenti
1
1
Biblioteca
0..*
(from ambiente)
Azione Prestito
Amministrativa
0..2 1
1
prestitoCorrente 0..1 0..* prestitiConclusi
1 1 1..*
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 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.
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 *
* * * * *
DescrizioneDocumento
- genere:String
- argomento:String
- lingua:String
0..1 - parolaChiave: String[]
- riassunto:String
context Documento
inv: self.scaffale->size() + self.scaffaleNovita->size() = 1
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
context UtenteRegistrato
inv: self.prestito->select(chiuso = false)->size() <= max
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.
context Settore
inv: (self.biblioteca->size()=1 and self.supersettore->size()=0) or
(self.biblioteca->size()=0 and self.supersettore->size()=1)
context Settore
inv: self.sottosettore->union(self.scaffale)->notEmpty()
Tabella 3.9: Vincoli relativi alle relazioni che coinvolgono la classe Settore.
• 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.
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
not self.registroUtenti.utenteRegistrato@pre->
exists(u | u.IDUtente = self.IDUtente)
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.
Normale
rinnovo/ Iniziale
dataScadenza +=7
restituzione(d)/
dataChiusura=d
Rinnovato
[oggi>dataScadenza]/sollecito Chiuso
Scaduto
CausaDiDisabilitazione
after (7 giorni)/
beneficiario.disabilita()
Abilitato
effettuaRegistrazione
disabilita/abilitato=false
Disabilitato
SulBancone
restituito
restituzione
Disponibile
acquisizione
restituzione
InPrestito
prestito
vendita
after (2 mesi)
PermanentementeIndisponibile
Venduto
Le rimanenti classi non hanno una dinamica degna di nota e non ven-
gono quindi dotate di diagramma degli stati.
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
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..*
* + argomentoSecondario * *
+ sottosettore
SettoreTrasversale Biblioteca
- argomento: String
*
*
0..1
1..* 1..*
* + sottosettore
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
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
3.2 Ascensore
In un edificio di diversi piani è presente un ascensore, che si vuole con-
trollare mediante un sistema informatizzato.
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.
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()
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
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
Display
- numPianoAscensore: int
+ setNumPianoAsc()
1 1
0..1 0..1
Piano Ascensore
- numero: int
context PulsanteAscensore
inv: self.numPiano = self.piano.numero
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
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
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
azionaSalita Avanti
ferma
Fermo
ferma
Indietro
azionaDiscesa
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).
Inattivo
arrivoAscensore/gestoreAscensore.alPiano(numero)
Sollecitato
partenzaAscensore
Aperta inChiusuraIniziale
chiudi [fotocellula.chiusa()]
entry/gestoreAscensore.portaAperta() entry/tInitMov=now
attesaChiusura finePassaggio
after(tempoMovimento)
inApertura after(tempoApertura)
inRiapertura passaggio
entry/tempoApertura=now - tInitMov
apri
chiusa
entry/gestoreAscensore.portaChiusa()
after(tempoMovimento) [fotocellula.chiusa()]
AttivoSalita
portaAperta
[nessunaPrenotazione()] AttivoSalitaFermo
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] /
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.
Specificate i tre casi descritti qui sopra, mediante i diagrammi più adat-
ti.
CAPITOLO 3. ANALISI DEI REQUISITI Page 145
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.
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.
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.
IngressoNellaChat
UscitaDallaChat
InvioMessaggioPrivato
EliminazioneStanza
InvioAvvisoAUtente
ModeratoreChat UtenteChat
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
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
Chat
UtenteChat
<<subsystem>>
Chat
<<use>>
<<subsystem>>
<<use>> ChatManager
<<use>>
<<use>>
<<use>>
<<subsystem>> <<subsystem>>
Partecipante Moderatore
<<use>> <<use>>
<<use>>
: Messaggio
: ModeratoreChat
<<subsystem>>
Moderatore
Comandi Messaggi
IModeratore
IChatAdmin
Comandi Messaggi
<<subsystem>> <<subsystem>>
Partecipante ChatManager
Comandi Comandi
IChat
Messaggi Messaggi
IPartecipante
• Interfaccia IPartecipante
• 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 [*]
<<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
<<subsystem>> <<subsystem>>
ModeratoreChat m: Moderatore chat: ChatManager
richiesta registrazione
registraModeratore(m)
alt
setModeratore(m)
[non esiste già un moderatore registrato ]
[else ]
Precondizioni: nessuna.
Svolgimento:
Svolgimento alternativo 1:
Svolgimento alternativo 2:
<<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
Svolgimento:
Svolgimento normale:
Svolgimento alternativo 1:
Svolgimento alternativo 2:
<<subsystem>> <<subsystem>>
: ModeratoreChat moderatore: Moderatore chat: ChatManager
ref
RegistraModeratore(moderatore)
opt
[registrazione moderatore OK ]
loop
[per ogni nome stanza ns nell'insieme ]
ref
CreaStanza(ns,moderatore)
Precondizioni: nessuna.
Svolgimento:
<<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
RegistraPartecipante
<<subsystem>>
chat: ChatManager[1]
<<subsystem>>
partecipanteChat: Partecipante[1]
stanzeChat: Stanza[*]
RegistrazionePartecipanti
: RegistraPartecipante
partecipanteChat partecipanteChat
<<subsystem>> <<subsystem>>
p2: Partecipante p3: Partecipante
stanzeChat
partecipanteChat
: Stanza[*]
<<subsystem>>
p1: Partecipante
chat
<<subsystem>>
: ChatManager
<<create>> m: Messaggio
msg(mittente, m)
loop
[per tutti i partecipanti nella stanza del mittente]
msg(mittente, m)
Svolgimento:
loop
[per ogni stanza s ]
esistePartecipante(p)
opt
[esiste partecipante p in stanza s ]
rimuoviPartecipante(p)
loop
[per ogni altro partecipante alla stanza s ]
Svolgimento:
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)
<<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
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
<<component>>
<<component>> RMIServer
RMIRegistry
<<delegate>>
ServerImpl
IPublish IPublish
ServerSkeleton
IService
ILookup
IService
<<delegate>>
<<component>>
RMIClient
Client
<<delegate>>
IService
IService
ServerStub
IService
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.
lookup(nomeServizio)
risultato
RMI
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
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.
<<interface>>
Remote
(JDK 5.0 Classes.java.rmi)
IModeratore
ModeratoreImpl
IChatAdmin
PartecipanteImpl ChatManagerImpl
IChat
IPartecipante
UnicastRemoteObject
(JDK 5.0 Classes.java.rmi.server)
Requisiti informali
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.
Attori
Casi d’uso
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
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
1
Qualifica + qualificaAbilitata 0..* Servizio ProfiloServizio
0..*
<<enumeration>> 1..*
tQualifica ProfiloAutorizzazione ProfiloAutenticazione
+ tipoQualifica
1
<<enumeration>>
Credenziale - tipoCredenziale
tCredenziale
1
UserNamePassword
- userName : String
- password : String
- tipoCredenziale <<enumeration>>
tCredenzialeDebole
- usernamePassword
Svolgimento:
Page 190 CAPITOLO 4. PROGETTAZIONE
Registrazione * Credenziale
CredenzialeDebole CredenzialeForte
1
UserNamePassword
ProfiloRegistrazione 1 PIN
- userName: String
- nome - password: String - PIN
- cognome
- codiceFiscale CertificatoX509
- ...
Accreditamento *
*
Utente 0..* - qualificaPosseduta Qualifica
3: richiestaAutenticazione(tCredenziale)
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
IIntegrationTier
: RisorsaWeb
<<forward>> <<subsystem>>
IntegrationTier
Utente
<<client page>> <<server page>>
<<builds>>
: PaginaHTML : PaginaServer
<<delegate>>
Svolgimento
interazione con
pagina HTML visualizzata
richiestaHTTP(risorsa web)
analizza richiesta
e individua risorsa
forward
<<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>>
<<component>>
GestoreProfiloServizio
: ProfiloServizio
IFiltro
: ProfiloAutenticazione : ProfiloAutorizzazione
<<interface>>
IServiceProfileLoader
+ loadServiceProfile(serviceName: String ): ProfiloServizio
<<component>>
Archivio Profili di servizio
: ProfiloServizio
estrae ProfiloAutenticazione
da profiloServizio
salvaProfiloAutenticazione
estrae ProfiloAutorizzazione
da profiloServizio
salvaProfiloAutorizzazione
<<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
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
attivazione filtro
opt
verifica compatibilita
(responsoAutenticazione,
profiloAutenticazione)
alt
[non esiste ResponsoAutenticazione precedente oppure esiste ma non è compatibile con il
ProfiloAutenticazione del servizio]
ref
Autenticazione
di autenticazione.
ref
Login
redirect su servizio di
GestioneResponsoAutenticazione del
Service Provider
redirect
forward
memorizza il ResponsoAutenticazione
nella sessione utente
Svolgimento:
TARGET=http://spHost:port/GRA?targetservice=http://sp
Host:port/servicePage?serviceParameters...
http://gestoreIdentitaHost:port/login?authType=weak&
TARGET=...
Scenario: login.
Precondizioni:
il GestoreIdentita ha ricevuto una richiesta di accesso al servizio
: Utente : WebBrowser : GestoreIdentita servizioDiLogin: RisorsaWeb : ArchivioProfiliRegistrazione
inserimento credenziali
submit LoginForm
submit
forward
CAPITOLO 4. PROGETTAZIONE
esito verifica
<<create>> : ResponsoAutenticazione
setAutenticazioneOK(esito verifica)
setTipoCredenziali(tipo credenziali)
Svolgimento:
<<component>>
GestoreAutorizzazione
IFiltro : Accreditamento
: ProfiloRegistrazione : ProfiloAutorizzazione
<<interface>>
IAccreditamento
+ getAccreditamenti(userName: String ): Accreditamento"[]"
<<component>>
ArchivioAccreditamenti
: Accreditamento
recupera ProfiloAutorizzazione
associato al servizio richiesto
getAccreditamenti(userName)
Accreditamento[]
alt
[esiste un accreditamento compatibile con
l'elenco qualifiche abilitate all'accesso ]
consenti l'accesso alla risorsa richiesta dall'utente
<qualificaAbilitata>amministratoreSistema
</qualificaAbilitata>
<qualificaAbilitata>operatoreSistema
</qualificaAbilitata>
</qualificheAbilitate>
</profilo-autorizzazione>
</servizio>
</profilo-servizio>
: GestoreIdentita
<<execution environment>>
: J2EEApplicationServer
<<execution environment>>
: J2EEApplicationServer
: WebServiceGestioneProfiliRegistrazione
SOAP
login: ServizioWeb
: PCUtente : DBProfiliRegistrazione
<<artifact>>
ServizioWeb <<component>>
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
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.
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.
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
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
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
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
context Quadrante
inv: self.colpo->forAll(c1, c2: Colpo |
c1 <> c2 implies c1.casella <> c2.casella)
context Quadrante
inv: self.propriaIU <> self.IUAvvers
context InterfacciaUtente
inv: self.proprioQuad <> self.quadAvvers
context Partita
inv: Partita.allInstances->size() = 1
context Nave
inv: self.dimensioni > 0 and self.dimensioni < 6
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
context Posizione
inv: self.inizio.riga <= self.fine.riga and
self.inizio.colonna <= self.fine.colonna
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)
DisposizioneInizialeGioc1 DisposizioneInizialeGioc2
SparoGiocatore1 SparoGiocatore2
CalcoloDanniVisualizza
VittoriaGioc1
[quadGioc1.flottaAffondata() and not quadGioc2.flottaAffondata()]
VittoriaGioc2
[quadGioc1.flottaAffondata() and quadGioc2.flottaAffondata()]
Pari
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
abilitaSparo()
sparo(c)
preSparo(c)
true
sparo(c)
disabilitaSparo()
sparo(c)
: Colpo
visualizzaQuadAvvers()
visualizzaQuadProprio()
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.
battagliaNavale
partita interfaccia
campoBattaglia
+ Colpo
+ Nave
+ Quadrante
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.
<<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
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
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
• 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.
<<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
Context - context *
State
+ request() - state
+ handle()
+ setState(s : State)
State
concreteState: ConcreteState
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
request()
handle()
alt
[Il singolo stato gestisce il passaggio allo stato successivo ]
setState(concreteStateB)
request()
handle()
#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
Figura 5.17: Dettaglio delle classi e contestualizzazione del design pattern State.
Page 248 CAPITOLO 5. BATTAGLIA NAVALE IN RETE
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
<<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
+ 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
Model-View-Controller
<<role binding>>
<<role binding>>
<<role binding>>
<<role binding>>
<<role binding>> <<role binding>>
Observer
La Figura 5.19 illustra le classi del sistema che partecipano alla realiz-
zazione del pattern MVC.
3.2: attach(giocatoreController)
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.
<<component>>
partita: Partita
3: aggiorna stato
e gestorePartita.
<<executionEnvironment>> <<executionEnvironment>>
JavaVirtualMachine JavaVirtualMachine
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
/**
* 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();
/**
* Gestione di un quadrante di gioco
*/
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 261
/**
* @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
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;
}
}
...
...
...
/**
* 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
//
Classe Partita
/**
* Gestione della partita
*/
public class Partita implements PartitaInterface {
/**
* 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
/**
* 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
/**
*
*/
public abstract class PartitaState {
/** Contesto della classe State */
protected Partita partitaContext = null;
/**
* Costruttore
* @param partitaContext riferimento alla partita
*/
public PartitaState(Partita partitaContext) {
this.partitaContext = partitaContext;
}
/**
CAPITOLO 5. BATTAGLIA NAVALE IN RETE Page 271
/**
* 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) {
/**
* 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 {
/**
* @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
/**
* @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) {
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
Bibliografia
Fow03 Martin Fowler. UML Distilled. Addison Wesley, terza edizione, 2003.
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.
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
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