Illustrazione del processo di analisi, progettazione e sviluppo in

Transcript

Illustrazione del processo di analisi, progettazione e sviluppo in
Illustrazione del processo di analisi, progettazione e
sviluppo in riferimento a un caso di esempio
(Agenzia turistica)
Prima scrittura: ottobre 2006
Ultima Revisione 6 settembre 2011
Scopo del documento
Lo scopo di questo documento è illustrare un metodo per l’intero processo di analisi, progettazione e sviluppo
di un sistema software in riferimento a un caso applicativo concreto, per quanto semplificato.
1
Il caso di studio
Un’agenzia turistica avente sede in un luogo vacanziero organizza escursioni. Le escursioni hanno durata
giornaliera e (per semplicità) sono di due tipi: gite in barca e gite a cavallo.
Per ogni escursione sono previsti alcuni optional acquistabili dall’eventuale partecipante. I tipi di optional
possibili sono tre: pranzo, merenda, visita a un sito. I tipi di optional associati a una data escursione possono
differire da caso a caso. Per esempio: la gita in barca del giorno x può prevedere il pranzo e la merenda, la gita
in barca di due giorni dopo può prevedere il solo pranzo, l’escursione del giorno y può non prevedere optional.
Il prezzo degli optional è fisso ed è determinato solo dal tipo. Invece, ogni singola escursione ha un suo
prezzo. Ogni escursione prevede un numero massimo di partecipanti. I prezzo di una escursione, il limite ai
partecipanti e tutto ciò che corrisponde alla definizione dell’escursione stessa viene fissato all’atto della sua
immissione nel programma dell’agenzia. Questi dati possono anche essere modificati, in corso di esercizio, dopo
che essi sono stati immessi.
Si deve realizzare un sistema in grado di gestire le prenotazioni. A tal fine il sistema deve:
1. Consentire di inserire, eliminare, modificare le escursioni nel programma stagionale dell’agenzia.
2. Permettere di registrare un partecipante ad una data escursione, consentendo, nel caso siano previsti, la
scelta di eventuali optional; calcolare il costo dell’escursione comprensivo degli optional.
3. Cancellare un partecipante già iscritto da una data escursione.
4. Aggiungere o eliminare un optional relativamente a un dato partecipante e una data escursione; calcolare
il nuovo prezzo dell’escursione.
1.1
Osservazione
Una specifica come quella precedente è quanto un ingegnere del software può aspettarsi nella pratica del mestiere.
Un esame anche superficiale della medesima mostra che essa è molto approssimata e certamente incompleta.
Per esempio si afferma che è possibile eliminare o modificare una escursione già a programma. In un contesto
reale l’eliminazione di una escursione avrebbe conseguenze non trascurabili: avvisare gli iscritti, rimborsarli di
quanto pagato, tenere conto della restituzione del danaro nella contabilità, ecc. Si tratta di funzionalità che
in un contesto reale non possono essere trascurate. Lo scopo dell’analisi è proprio quello della loro completa
identificazione. Risulta evidente che l’analista deve coinvolgere in questo processo il committente e gli utenti
finali del sistema. Più avanti, attraverso l’analisi dei requisiti preciseremo meglio le specifiche precedenti.
2
Il metodo seguito
Il metodo di analisi, progettazione e sviluppo si ispira a quello descritto in [2]. Più specificatamente esso prevede
i seguenti passi.
1
1. Analisi dei requisiti
• Specifica dei requisiti funzionali
• Costruzione del modello di dominio
• Analisi dei casi d’uso
• Validazione requisiti–casi d’uso
2. Analisi/Progetto preliminare
• Analisi di robustezza
• Rifinitura dei casi d’uso e eventuali aggiornamenti al modello di dominio
3. Progetto dettagliato
• Diagrammi di sequenza
• Assegnazione delle responsabilità (operazioni) alle classi
4. Realizzazione
3
La specifica dei requisiti funzionali
Con il termine “requisiti” si intende l’insieme delle proprietà che un prodotto deve possedere per soddisfare al
proprio scopo di uso.
Tradizionalmente i requisiti di un prodotto software vengono raccolti in un documento denominato SRS
(specifica dei requisiti software, ovvero software requirement specification). Talo documento ha lo scopo di
definire esattamente cosa il sistema farà (non come come lo farà).
Lo standard IEEE 830 (ultima edizione 1998)[1] definisce i criteri (e il formato) secondo cui deve essere
redatta una specifica dei requisiti. Essenzialmente, lo standard prevede l’elencazione dei requisiti funzionali e
propone alcuni metodi secondo cui essi devono essere descritti. Purtroppo lo standard in questione è piuttosto
vecchio e non contempla il ricorso ai casi d’uso, che, almeno da quando si è diffuso UML, costituiscono la tecnica
preferita per specificare il “cosa”.
Il metodo delineato al Paragrafo 2 è fortemente influenzato dall’analisi dei casi d’uso. Tuttavia, esso prevede,
come fase iniziale, la stesura delle specifiche in forma testuale, secondo tradizione consolidata. Si ritiene infatti
che sia comunque utile stendere un elenco ordinato e strutturato dei requisiti, in forma testuale. Del resto, nella
pratica professionale, l’elencazione dei requisiti è sempre il passaggio iniziale attraverso cui l’analista software e
il committente arrivano a concordare cosa ci si aspetta dal sistema. Spesso si parla di “raccolta” dei requisiti,
proprio per evidenziare che si tratta di un processo tramite il quale si cerca di ottenere, rendere manifeste tutte
le caratteristiche che il sistema deve possedere. È un processo che dovrebbe coinvolgere analista, committente
e utente finale.
Dopo le fasi di raccolta dei requisiti e di analisi dei casi d’uso è buona norma incrociare i requisiti con i casi
d’uso, individuando da quali casi d’uso sono “coperti” i vari requisiti, in modo di poter verificare se niente è
stato tralasciato.
La parte seguente costituisce dunque una sintetica SRS.1 Con riferimento allo standard IEEE 830, essa è
strutturata in tre paragrafi che identificano lo scopo, i vincoli e i requisiti funzionali.
• Scopo. La corretta identificazione dello scopo è importante in fase di analisi, in quanto ci aiuta a capire
quali sono le entità da modellare e come vanno modellate (evidentemente la scelta deve cadere su entità
che hanno senso rispetto allo scopo che il sistema si prefigge).
• Vincoli. Il vincoli sono condizioni o proprietà di carattere generale che il sistema deve rispettare.
• Requisiti funzionali. Funzionalità che il sistema deve esibire, elencate in modo ordinato e non ambiguo.
1 Data la semplicità del nostro problema, la specifica del Paragrafo 1 è quasi sufficiente a rappresentare i requisiti. Abbiamo
tuttavia osservato che la essa è incompleta e ambigua. I requisiti riportati al Paragrafo 3.1 hanno lo scopo di eliminare le ambiguità
rilevate.
2
3.1
Specifica dei requisiti
Scopo
Lo scopo del sistema è la gestione delle prenotazioni dell’agenzia.
Vincoli
• Il sistema viene realizzato come installazione unica, per funzionare all’interno di una agenzia avente una
sola sede.
• Tutte le operazioni vengono effettuate dal solo personale dell’agenzia e non c’è necessità di distinguere i
differenti impiegati. Ciò equivale ad assumere che ci sia un solo attore.
Requisiti funzionali
R1 Il sistema deve consentire l’inserimento di una nuova escursione nel programma dell’agenzia in qualunque
momento.
R1.1 Ogni escursione è caratterizzata da: data, tipo, descrizione, costo, numero massimo di partecipanti,
optional possibili.
R1.2 Le escursioni sono di 2 tipi: gite in barca e gite a cavallo. Costo e numero massimo di persone
inscrivibili sono stabiliti all’atto dell’inserimento e possono variare da escursione a escursione. Pure
i tipi di optional previsti da ogni escursione sono fissati all’atto dell’inserimento.
R1.3 I possibili tipi di optional sono 3: “Pranzo”, “Merenda”, “Visita”. I relativi costi sono fissi indipendentemente dall’escursione cui vengono associati.
R2 Il sistema deve permettere in qualunque momento la modifica o l’eventuale eliminazione di ogni singola
escursione.
R3 Il sistema deve permettere di registrare un partecipante ad una data escursione, consentendo, nel caso
siano previsti, la scelta di eventuali optional; deve calcolare il costo dell’escursione comprensivo degli
optional.
R3.1 Un partecipante è caratterizzato attraverso il suo codice fiscale, nome cognome e indirizzo.
I dati di un partecipante devono essere registrati alla prima prenotazione e non più cancellati dal
sistema, anche nel caso in cui un partecipante si iscriva a una sola gita e poi si cancelli.
R4 Il sistema deve permettere in qualunque momento sia la cancellazione di un partecipante da una data
escursione sia l’eventuale modifica del numero e del tipo di optional scelti; nel caso di modifica degli
optional scelti deve essere calcolato il nuovo costo risultante.
Si osservi ora che i requisiti R2 e R4 in un sistema reale avrebbero delle implicazioni non trascurabili: in caso
di cancellazione di una escursione occorrerebbe produrre gli avvisi per gli iscritti, rimborsarli du quanto pagato,
ecc.. In altre parole, è necessario stabilire ulteriori vincoli.
Vincoli ulteriori
• Per semplicità si assume di trascurare le implicazioni e gli effetti indotti dalla modifica o dalla cancellazione
delle escursioni.
• Per semplicità si assume di trascurare le implicazioni e gli effetti indotti dalla cancellazione di un partecipante a una escursione o dal cambio degli optional scelti.
• Per semplicità non si distingue tra escursioni già tenute (antecedenti la data corrente) e escursioni future,
nel senso che tutto può essere modificato.
3
4
Il modello di dominio
Come indicato al Paragrafo 2, il metodo seguito prevede che la costruzione del modello del dominio applicativo
preceda l’analisi dei casi d’uso.
Non è questa una scelta convenzionale. Infatti, in larga parte della letteratura si propone di far precedere la
costruzione del modello di dominio dall’analisi dei casi d’uso. Ciò può portare alla stesura di casi d’uso troppo
astratti, non collegati allo specifico problema, di poca utilità per il programmatore [2].
In questa fase non è necessario che costruire un modello di dominio dettagliato in ogni suo aspetto, bensı̀
costruire un modello che evidenzi le principali relazioni tra le entità applicative. Quanto dettagliare il modello
è una questione di sensibilità e di utilità. Conviene limitarsi agli aspetti strutturali (le relazioni tra le classi),
utili nella fase di robustness analysis, senza addentrarsi troppo nella definizione degli attributi né tantomeno
dei metodi, da rinviare alla fase di rifinitura del modello e di progettazione dettagliata.
4.1
Costruzione del modello di dominio
L’analisi del testo del problema permette di evidenziare un certo numero di sostantivi: escursione, programma,
optional, gita in barca, costo, ecc. Essi individuano concetti che devono essere trasferiti nel modello, in forma
di classe o in forma di attributo. Per esempio, il sostantivo “escursione” è certamente deputato a rappresentare
una classe del modello, mentre termini come “costo” e “numero massimo di partecipanti” sono manifestamente
attributi della stessa escursione.
I seguenti punti illustrano il processo iniziale di costruzione del modello:
• Il testo dice che c’è un programma (dell’agenzia) del quale fanno parte le escursioni. Si deve quindi
prevedere la classe Programma e la classe Escursione, con la prima che aggrega elementi della seconda.
• Un’escursione può, ovviamente, avere più partecipanti, mentre un partecipante può essere iscritto a più
escursioni. Occorrerà quindi una associazione tra le classi Partecipante e Escursione.
• Il testo dice che un’escursione può prevedere più optional. In realtà, se si legge bene il testo, ci si convince
che il significato è che un’escursione può prevedere più “tipi di optional”, nel senso che il partecipante
sceglie quel tipo di optional (il pranzo) NON uno specifico optional. In altre parole si deve tenere traccia
del fatto che un partecipante sceglie uno o più tipi di optional tra quelli disponibili per una data escursione;
gli stessi che possono essere scelti da un altro partecipante.
La situazione sarebbe diversa se il partecipante potesse scegliere il proprio optional “su misura”, distinto
da quello scelto da altri. Per esempio: potrebbe essere il caso che, tra l’agenzia e il ristorante a cui vengono
portati i partecipanti a una data gita, ci sia un accordo che prevede che l’agenzia comunichi in anticipo il
dettaglio delle scelte, in modo che il ristorante possa procedere all’acquisto dei prodotti commestibili in
giusta misura. Allora, per ogni partecipante che sceglie l’optional pranzo si dovrebbe istanziare l’oggetto
Pranzo, con un numero adeguato di attributi quali: primo piatto (minestra, spaghetti, lasagne, ..), secondo piatto (carne, pesce, ...), dessert (gelato, dolce...), ecc., in modo che l’agenzia presenti un quadro
esatto di quel che viene richiesto.
Nel caso in esame un optional è caratterizzato da suo tipo (“Pranzo”, “Merenda” o “Visita”) e dal relativo
costo che è determinato solo dal tipo. Dunque un dato tipo di oggetto optional è una sorta di “costante”,
nel senso che esso è unico per tutto il sistema, qualunque sia l’escursione e chiunque sia a selezionarlo.
Il precedente ragionamento ci convince a cambiare nome alla classe che rappresenta gli optional, ridenominandola TipoOptional.
Per come è specificato il problema ogni escursione aggregherà fino a un massimo di 3 tipi di optional.
Si ottiene cosı̀ il diagramma di Figura 1.
Si noti che in Figura 1 è stata indicata la molteplicità generica agli estremi dell’associazione tra “Escursione” e “Partecipante”; inoltre, mentre questa associazione è stata fatta navigabile in entrambe le direzioni,
l’aggregazione delle escursioni è navigabile solo in un verso. Questo indica l’ovvio fatto che dal programma
si vuole risalire alle escursioni, mentre non interessa l’inverso. Nel caso in cui fossero previsti più programmi avrebbe senso rendere l’aggregazione navigabile in entrambi i versi. Si noti anche che è stato fissata la
molteplicità dei tipi di optional nella misura prevista dalla specifica.
Alcuni autori sostengono che questo genere di considerazioni dovrebbe essere rimandato alla fase successiva
di rifinitura del modello di dominio (Paragrafo 11.1). Tuttavia, siccome le considerazioni appena fatte sono
venute in modo del tutto naturale nello stendere il diagramma di Figura 1, non avrebbe nessun senso tenerle
4
Figura 1: Primo passo nella costruzione del modello di dominio.
nascoste per non dare l’impressione che stiamo andando oltre i confini dell’analisi preliminare. Al contrario esse
costituiscono concetti essenziali alla comprensione del modello stesso, concetti che potranno rivelarsi utili nelle
fasi successive dello sviluppo.
Per quanto si riferisce invece al fatto che la specifica afferma che le escursioni sono di due tipi: in barca
o a cavallo e che ci sono tre tipi differenti optional, si sarebbe tentati di introdurre altre classi (per esempio
“GitaBarca”, “Pranzo”), sfruttando il meccanismo dell’ereditarietà. Ciò non aggiungerebbe molto alla struttura
del modello e rischierebbe di introdurre dettagli soggetti a essere rivisti in fase di progettazione. Pertanto
conviene rimandare il trattamento di questi aspetti alla fase di rifinitura del modello di dominio.
Resta ora da modellare le associazioni tra i partecipanti e gli optional. Si tratta di tener traccia di quali tipi
di optional abbia scelto un partecipante in riferimento a una data gita. Si faccia caso che non si può stabilire
una associazione diretta tra Partecipante e Optional, infatti il partecipante x può avere scelto l’optional
Pranzo per l’escursione e1 e gli optional Merenda e Visita a sito per l’escursione e2. In altre parole si tratta
di una relazione ternaria che richiede una classe di associazione. Il problema è che questa classe non può essere
Escursione, in quanto essa svolge una funzione differente dal rappresentare il legame tra Partecipante e
Optional. Occorre introdurre una nuova classe, con funzione di associazione tra Partecipante e Optional in
riferimento a una data escursione. Chiameremo Scelta questa classe. Essa è relativa (dipende) da una (singola)
Escursione. Si ha cosı̀ il diagramma di Figura 2. Notare che nel diagramma non si è usata la notazione UML
che indica dipendenza (tra Scelta e Escursione),ciò perché nel caso specifico la dipendenza di Scelta da
Escursione consiste proprio nel riferimento a Escursione.
Figura 2: Il modello di dominio aggiornato.
C’è però un problema: se un partecipante si iscrive a una escursione e poi si cancella dall’escursione, per il
requisito R2.1 i suoi dati devono restare nel sistema. Con modello di Figura 2 la cancellazione del partecipante
da una escursione porta all’eliminazione delle associazioni che il partecipante ha con l’escursione e con gli
5
optional. A questo punto il partecipante risulterebbe scollegato dalle escursioni e rappresenterebbe un oggetto
a sé stante.2
Delle due l’una:
• Si cambia il requisito R2.1 nel modo seguente: I dati di un partecipante devono essere registrati alla
prima prenotazione. I dati del partecipante vengono mantenuti nel sistema fintantoché egli risulta iscritto
almeno ad una escursione, in caso contrario del partecipante non si tiene più traccia.
• Si mantiene il requisito R2.1, ma in questo caso occorre aggiungere un ulteriore oggetto in modo che essa
contenga direttamente tutte le persone che almeno una volta si sono iscritte. Tale oggetto può essere
l’agenzia stessa, che diventa cosı̀ il “contenitore” di tutti gli oggetti del sistema.
Assumeremo di non cambiare il requisito R2.1. Pertanto il diagramma delle classi si trasforma com in
Figura 3.
Si noti che l’aggiunta della classe Agenzia ha anche l’ovvia conseguenza di poter accedere all’insieme dei
partecipanti in modo diretto. È fin troppo ovvio prevedere che tra le funzionalità del sistema ce ne saranno
alcune che si svolgeranno proprio a partire dalla conoscenza del partecipante. Con lo schema di Figura 3 per
trovare un partecipante basta cercare nell’insieme dei partecipanti, con lo schema di Figura 2 per trovare un
partecipante occorre passare per le escursioni.
Figura 3: Il modello di dominio reso più flessibile.
A questo punto osserviamo che il modello di Figura 3 assume che l’agenzia abbia più programmi (stagionali).
Il modello è stato sviluppato tenendo conto del testo del problema.
Nei requisiti del Paragrafo 3.1 non c’è traccia di come debba essere trattato il concetto di programma “Programma”. Facciamo perciò ora un’ulteriore assunzione semplificativa, congruente con la specifica dei requisiti:
supponiamo che l’agenzia abbia un unico programma indifferenziato, indipendente dalla stagione o altro. Tale
assunzione deve essere aggiunta ai “vincoli ulteriori” di pagina 3 il seguente vincolo:
• Per semplicità si suppone che l’agenzia abbia un unico programma indifferenziato, indipendente dalla
stagione o altro.
Si deve però osservare che con questo ulteriore assunzione la classe Programma di Figura 3 è del tutto
superflua, per cui il modello si riduce a quello di Figura 4.
Conclusioni
La costruzione del modello di dominio serve a dare il contesto rispetto al quale il sistema deve essere sviluppato.
Le entità che vi compaiono hanno un nome preciso (e un ruolo appena delineato).
Definire significato dei termini è il modo migliore per evitare le ambiguità di interpretazione. La costruzione
del modello di dominio produce il glossario dei termini.
La parte che segue renderà evidente il vantaggio dell’aver fatto precedere la definizione del modello di dominio
all’analisi dei casi d’uso (Cfr. Paragrafo 11.1).
2 Nel
senso che per raggiungerlo occorrerebbe un riferimento preciso allo specifico partecipante. Ovviamente ciò non ha senso
quando si ha una gran quantità di oggetti; in tal caso gli oggetti devono essere tenuti in un qualche “contenitore” e individuati
attraverso i propri attributi. Con lo schema di Figura 2 i partecipanti si trovano attraverso le escursioni che a loro volta si trovano
attraverso il programma. In Java, se si recide il riferimento al partecipante in escursione (assumendo che nel sistema non vi siano
altri riferimenti al partecipante), il partecipante è perso.
6
Figura 4: Il modello di dominio nella versione finale.
5
Analisi dei casi d’uso
Il testo del Paragrafo 1 e i requisiti del Paragrafo 3 indicano chiaramente che il sistema ha un solo attore:
l’impiegato dell’agenzia che chiameremo Agente.
Infatti, il cliente – cioè il possibile partecipante a una o più escursioni – non è un attore. Lo sarebbe se
egli stesso avesse accesso al sistema (per esempio via web), ma nel caso specifico ciò non è previsto e dunque il
cliente dell’agenzia non ha nessuna interazione diretta col sistema. Per l’unico attore (l’agente) il cliente è un
“dato” da inserire nel sistema.
Le specifiche del Paragrafo 3 identificano già i casi d’uso, anche in modo assai dettagliato. Per come sono
organizzati, i requisiti sembrano indicare 4 casi d’uso, corrispondenti ai requisiti R1-R4. Tuttavia, per quanto
la cancellazione di una escursione o la modifica dei suoi dati siano state raggruppate assieme, conviene, almeno
in prima istanza, considerarle come casi separati. Il prosieguo dell’analisi dirà se essi sono da ritenere distinti o
da considerare un unico caso. Seguendo questo ragionamento si intravvedono 6 casi d’uso:
• Inserimento di una escursione a programma.
• Cancellazione di una escursione.
• Modifica dei dati di una escursione.
• Iscrizione di un partecipante.
• Cancellazione di un partecipante da una data escursione.
• Modifica della scelta degli optional da parte di un partecipante.
Il corrispondente diagramma è in Figura 5.
Il diagramma dei casi d’uso serve a fornire una visione intuitiva d’insieme; però, per capire davvero cosa
succede occorre entrare nel dettaglio.
5.1
Caso d’uso: Inserimento escursione
Consideriamo per primo i caso d’uso CU1: “Inserimento (di una) escursione (a programma)”. Di esso viene
data una descrizione dettagliata in Tabella 1.
7
Figura 5: Il diagramma (iniziale) dei casi d’uso.
8
CU1: Inserimento di una escursione a programma
Attore: Agente
Precondizione:
Il sistema è idle e sullo schermo viene presentata la finestra con il menu
principale (schermata “Menu Principale”).
Sequenza eventi:
1. il caso d’uso inizia quando l’attore clicca il bottone “Inserimento” sul
“Menu Principale”
2. il sistema mostra una nuova schermata (“Inserimento Escursioni”),
contenente vari widgets (campi, checkbox, listbox, pulsanti ecc.),
adeguati all’inserimento dei dati richiesti per definire un’escursione.
3. l’attore agendo sugli elementi presenti sul video inserisce i dati relativi all’escursione (data, barca/cavallo, descrizione, ecc.); al termine
preme il bottone “Inserisci”;
4. il sistema controlla i dati immessi; se i dati inseriti sono corretti il
sistema presenta la finestra di dialogo “Conferma Inserimento”, con
la quale chiede di confermare la scelta di aggiungere l’escursione al
programma.
(a) In caso affermativo il sistema aggiunge l’escursione al programma
(b) In caso contrario niente viene modificato
indipendentemente da quale dei due passi precedenti sia stato eseguito, il sistema torna a presentare la schermata del punto 1, mostrando
gli eventuali dati già inseriti a video.
Invariante:
A partire dal punto 1 il caso d’uso termina incondizionatamente in qualunque
momento venga premuto il bottone Esci
Sequenza alternativa:
Se al punto 1 si verifica che i dati non sono corretti, viene presentata la
finestra di dialogo “Errore di inserimento” nella quale si indica quale campo
deve essere aggiornato. Quando l’operatore preme il bottone “OK” presente
tale finestra il caso d’uso riprende dal punto 1 (senza apportare modifiche
al contenuto dei campi già immessi).
Postcondizione:
Le eventuali escursioni inserite sono state memorizzate; sullo schermo viene
ripresentato il menu principale
Tabella 1: Specifica del caso d’uso “CU1: Inserimento Escursione”
9
Quello di Tabella 1 è il modo classico di rappresentare i casi d’uso. Ci sono questi ingredienti: Numero
d’ordine e nome del caso d’uso, condizioni iniziali, percorso principale, eventuali percorsi alternativi, condizioni
di uscita. Nel caso specifico è stato aggiunto anche un “invariante”, cioè una condizione sempre verificata a
partire dal punto, caratteristica della modalità di funzionamento dell’interfaccia.3
Il caso d’uso prevede che esso contenga al suo interno possibili iterazioni del processo di inserimento delle
escursioni. E’ previsto un percorso alternativo a quello principale nel caso in cui i dati immessi si rivelino non
accettabili al controllo.
Lo schema di Tabella 1 è quello suggerito in molti libri. Esso è piuttosto pesante da scrivere e non è detto
che tutte le informazioni che esso prevede siano di effettiva utilità. Ovviamente non è obbligatorio seguire tale
schema. Ciò che serve è una descrizione che metta in luce il comportamento di attore e sistema, mostrando la
successione di passi nella forma di “azione-reazione”.
Poiché le interfacce tipo GUI sono diventate lo standard nell’interazione tra uomo e macchina, è molto più
facile spiegare un caso d’uso se si mostrano le schermate a video. Ovviamente non ci si deve perdere troppo
nei dettagli, che inevitabilmente sono soggetti a subire modifiche in fase realizzativa. Si tratta di dare conto di
come si svolgerà il caso d’uso. In Figura 6 viene mostrato un esempio di prototipo per la schermata del caso
d’uso CU1.
Figura 6: Prototipo di schermata per il caso d’uso CU1.
Un esame attento della descrizione del caso d’uso di Tabella 1 evidenzia che il troppo formalismo può anche
essere ridondante. Per esempio, la precondizione è ridondante in quanto è assorbita dalla descrizione del passo 1.
In altre parole, il formalismo è utile ma non è essenziale. È utile perché una rappresentazione schematica come
quella di Tabella 1 è di agevole lettura e di più immediata comprensione di una descrizione non strutturata.
Il requisito essenziale della descrizione di un caso d’uso sta nel rappresentare nel modo più preciso possibile
la sequenza di eventi, dando nome e cognome alle entità che vi partecipano. Per esempio, in Tabella 1 si è dato
un nome a tutte le schermate video che possono entrare in gioco e, per rendere più chiaro cosa succede, si è
costruito un prototipo della schermata “Inserimento Escursioni”.
5.2
Caso d’uso: Rimozione escursione
Il caso d’uso è descritto in tabella 2, mentre la schermata corrispondente è mostrata in Figura 7. La parte
destra della figura mostra la dialog box che si apre dopo che è stato cliccato il bottone “Rimuovi”; la dialog
3 E’
difficile trovare invarianti nei casi d’uso pubblicizzati.
10
box scompare dopo che viene premuto “Conferma”. Come si vede la schematizzazione delle schermate video è
molto utile a capire il “comportamento” del sistema (la dinamica delle interazioni).
CU2: Rimozione escursione
Sequenza eventi:
1. il caso d’uso inizia quando l’attore clicca sul bottone “Rimozione”
sulla finestra del “Menu Principale”
2. il sistema presenta la schermata “Rimozione escursioni”, nella quale
viene mostrata, la lista delle escursioni in programma.
3. l’attore seleziona l’escursione da eliminare e preme il bottone
“Rimuovi”;
4. il sistema presenta la finestra di dialogo “Conferma rimozione” con
la quale viene chiesto di confermare la scelta di eliminare l’escursione
selezionata.
(a) In caso affermativo l’escursione viene eliminata
(b) In caso contrario niente viene modificato
indipendentemente da quale dei due passi precedenti sia stato
eseguito, il sistema torna a presentare la schermata del punto ??.
Invariante:
A partire dal punto 1 il caso d’uso termina incondizionatamente in qualunque
momento venga premuto il bottone Esci
Postcondizione:
Le escursioni per cui è stata confermata la rimozione sono state eliminate;
sullo schermo viene ripresentato il menu principale
Tabella 2: Specifica del caso d’uso “CU2: Rimozione Escursione”. Non è stato riportato l’attore (perché ovvio),
né la condizione iniziale (perché assorbita dal primo passo).
Figura 7: Prototipo di schermata per la rimozione delle escursioni. A destra viene mostrata la dialog box che
si apre dopo che è stato cliccato il bottone “Rimuovi”; la dialog box scompare dopo che viene premuto uno dei
due pulsanti. Se viene premuto “Sı̀” l’escursione viene effettivamente rimossa; se viene premuto “No” niente
accade. Nota per la finestra di dialogo di conferma le Swing fanno tutto!!
11
5.3
Caso d’uso: Modifica escursione
Questo caso d’uso ha in comune ai precedenti le medesime precondizioni, il primo passo, la condizione invariante
e la condizione di uscita. Tenuto conto di ciò esso viene espresso in forma discorsiva qui di seguito:
Caso d’uso CU3: Modifica escursione
Il caso d’uso inizia quando l’attore clicca il bottone “SalvaMod” del menu principale. Il sistema
presenta una schermata contenente la lista delle escursioni e i campi per inserire i dati che definiscono
le escursioni. L’attore seleziona una escursione dalla lista e modifica i campi; al termine clicca
sul bottone “SalvaMod”. Il sistema chiede conferma e in caso affermativo aggiorna i dati relativi
all’escursione selezionata. Il ciclo può essere iterato. Il caso d’uso ha fine quando viene cliccato il
bottone “Esci”.
In Figura 8 è mostrata la schermata corrispondente.
Figura 8: Prototipo di schermata per la modifica delle escursioni. Vengono mostrati i passi dalla selezione
dell’escursione fino al comando di modifica. Con riferimento alla schermata di Figura 6, la modifica consiste nel
togliere l’optional “Merenda”, e cambiare il limite massimo degli inscrivibili. La figura mostra nel dettaglio una
possibile sequenza che porta alla modifica di un’escursione: 1: viene selezionata l’escursione da modificare; 2:
viene tolta la spunta dall’opzione “Merenda”; 3: viene modificato il numero massimo degli inscrivibili; 4: viene
dato il comando di salvare la modifica.
Osservazione La sequenza di Figura 8 suggerisce che la prima operazione sia quella di selezionare un’escursione e poi apportare le modifiche ai dati relativi. Si potrebbe anche stabilire che l’interfaccia non imponga un
simile vincolo, decidendo che l’attore possa iniziare modificando i dati (supponendo che egli sappia esattamente
cosa inserire) e solo successivamente selezionare l’escursione alla quale apportare le modifiche secondo quanto
introdotto. Dare una simile flessibilità impone però che, se l’attore preme il pulsante “SalvaMod” senza che
sia stata selezionata n’escursione, venga presentato un messaggio di allarme che invita l’utente a effettuare la
selezione. La soluzione alternativa, anche se più rigida, consiste nello stabilire che la prima azione che l’attore
può fare sulla schermata “Modifica Escursioni” sia obbligatoriamente la scelta dell’escursione da modificare.
Si tratta di un dettaglio che non è appropriato trattare a questo punto dell’analisi. Potrà essere presa l’una
o l’altra soluzione in fase di sviluppo, anche in base alle caratteristiche del framework per la costruzione delle
interfacce, e non è detto che la soluzione apparentemente più facile (quella più rigida) si dimostri effettivamente
tale per il programmatore.
12
5.4
Possibile rivisitazione dei primi tre casi d’uso
A questo punto siamo in grado di tracciare la schermata iniziale almeno per quanto riguarda i casi d’uso CU1,
CU2 e CU3. Si tratta semplicemente di prevedere i tre bottoni “Inserimento”, “Rimozione” e “Modifica”. La
schermata del menu principale assumerebbe l’aspetto di Figura 9.
Figura 9: Il menu principale relativo all’inserimento, cancellazione e modifica delle escursioni.
Osserviamo ora che i tre casi d’uso hanno molte cose in comune. L’azionamento di uno qualunque dei
pulsanti di Figura 9 porta sostanzialmente alla medesima schermata. Ciò suggerisce di prevedere un solo
bottone sul menu principale, denominato “Inserimento/Rimozione/Modifica” e rimandare la scelta di quale
azione intraprendere alla schermata che esso determina. Ciò equivale a dire che il caso d’uso è unico e che ha
tre sottocasi d’uso, ovvero tre estensioni, come illustrato in Figura 10.
Figura 10: Trasformazione del diagramma di Figura 5.
Ovviamente le tre estensioni sono esclusive l’una dell’altra come risultato finale, ma possono avere parti in
comune. Sembra logico che occorra prevedere un meccanismo che, a partire dalla schermata che segue il click
sul bottone “Inserisci/Elimina/Modifica”, determini quale estensione abbia luogo.
Si tratta di una questione che, a questo punto dell’analisi converrebbe evitare di trattare troppo a fondo,
rimandando il suo esame alla fase realizzativa, in quanto il framework usato per la realizzazione della GUI
potrebbe avere un’influenza determinante nella scelta dei dettagli. Tuttavia, a scopo didattico, procediamo
nell’approfondimento della questione, perché si possono scoprire cose interessanti.
13
Diamo anzitutto una descrizione del caso d’uso “Inserimento/Eliminazione/Modifica Escursioni” (ma non nella
forma strutturata delle Tabelle 1 e 2).
Caso d’uso “Inserimento/Eliminazione/Modifica Escursioni”
Il caso d’uso inizia quando l’attore clicca sul bottone ‘Inserisci/Elimina/Modifica” sulla finestra
“Menu Principale”. Il sistema presenta la schermata “Inserimento/Rimozione/Modifica escursioni”,
sulla quale sono presenti i tre bottoni “Inserisci”, “Rimuovi”, “SalvaMod” e il bottone “Esci”. I
primi tre hanno la funzione di concludere i tre differenti sottocasi determinando le azioni da essi
previste; il quarto fa concludere in ogni momento il caso d’uso. I modelli della schermata del menu
principale e della schermata “Inserimento/Rimozione/Modifica escursioni” sono in Figura 11.
All’apertura della finestra “Inserimento/Rimozione/Modifica escursioni” i tre pulsanti “Inserisci”,
“Rimuovi” e “SalvaMod” sono disabilitati, mentre il pulsante “Esci” è abilitato (e cosı̀ resta per
sempre).
Il caso d’uso termina in qualunque momento premendo il pulsante “Esci”.
Figura 11: Struttura delle schermate rivisitata. A sinistra il menu principale (relativo alla sola gestione delle
escursioni), a destra l’interfaccia per l’inserimento, l’eliminazione e la modifica delle escursioni.
Analizziamo ora i sottocasi.
Sottocaso d’uso Inserimento
Percorso principale
L’estensione “Inserimento” viene presa quando l’attore, come prima cosa, seleziona una data (quella
a cui vuole aggiungere un’escursione); a questo punto il pulsante “Inserisci” si attiva. Successivamente l’attore può immettere gli altri dati relativi all’escursione da inserire. Il sottocaso si conclude
quando viene cliccato il pulsante “Inserisci”; se i dati sono corretti, viene presentata la finestra Conferma registrazione, alla quale l’attore risponde cliccando sul pulsante “Sı̀” o “No”. Se viene
cliccato il pulsante “Sı̀” l’escursione viene registrata e la sua descrizione inserita nella lista a video; se
viene cliccato il pulsante “No” niente accade; in ambedue i casi viene ripresentata la schermata “Inserimento/Rimozione/Modifica escursioni” (con i tre pulsanti “Inserisci”, “Rimuovi” e “SalvaMod”
disabilitati) sulla quale è possibile effettuare un nuovo inserimento, ovvero cancellare un’escursione,
ovvero modificare un’escursione.
Percorso alternativo
Se premendo il pulsante “Inserisci” i dati immessi non risultano accettabili (p.e., un costo negativo)
viene presentata una finestra con le informazioni circa i campi da reimmettere. Al click sul pulsante
“OK” sulla finestra, si ritorna la finestra “Inserimento/Rimozione/Modifica escursioni”.
14
Se ora si passa all’esame dei due sotto casi “Rimozione” e “Modifica” di Figura 10 è facile convincersi che
essi differiscono solamente per il pulsante premuto alla fine (“Rimuovi” o “SalvaMod”). Conseguentemente si
può pensare di raggrupparli in un unico sottocaso da cui se ne originano due. Ne consegue che il diagramma di
Figura 10 si modifica in quello di Figura 12.
Sottocaso Rimozione/Modifica
Percorso principale L’estensione “Rimozione/Modifica” viene presa quando l’attore, come prima cosa,
seleziona un’escursione dalla lista; a questo punto si attivano ambedue i pulsanti “Rimuovi” e “SalvaMod”. Successivamente l’attore può modificare i dati relativi all’escursione scelta (se intende
modificare). Al termine l’attore clicca uno dei due bottoni “Rimuovi” o “SalvaMod”. Nel primo
caso l’escursione viene rimossa, nel secondo modificata. In ambedue i casi il sistema chiede conferma.
Al compimento dell’azione viene ripresentata la schermata “Inserimento/Rimozione/Modifica escursioni” (con i tre pulsanti “Inserisci”, “Rimuovi” e “SalvaMod” disabilitati) sulla quale è possibile
effettuare un nuovo inserimento, ovvero cancellare un’escursione, ovvero modificare un’escursione.
Percorso alternativo
Se premendo il pulsante “SalvaMod” i dati immessi non risultano accettabili (p.e., un costo negativo)
viene presentata una finestra con le informazioni circa i campi da reimmettere. Al click sul pulsante
“OK” sulla finestra, si ritorna la finestra “Inserimento/Rimozione/Modifica escursioni”.
Figura 12: Aggiornamento del diagramma dei casi d’uso.
5.4.1
Alcune osservazioni
Nel realizzare l’interfaccia si farà ricorso a qualche framework. E’ possibile che i componenti utilizzabili sull’interfaccia (bottoni, liste a scorrimento, calendari, ecc.) abbiano delle caratteristiche che suggeriscano di
apportare variazioni a quanto siamo andati delineando.
Per esempio, è possibile che, una volta selezionata una data,e quindi entrati nel percorso di inserimento di
una nuova escursione, attivando il bottone “Inserisci”, sia possibile cambiare idea e selezionare un’escursione tra
quelle in lista, passando all’altro percorso, disattivando il bottone “Inserisci” e attivando i bottoni “Rimuovi” e
“SalvaMod”.4
4 Di
fatto è quello che accadrà nel nostro caso, con i componenti Swing di Java e alcuni componenti presi da librerie pubbliche.
15
Come evidenziato già in precedenza, questi aspetti possono essere trattati più appropriatamente in fase di
realizzazione, anche se ciò può portare a qualche leggera modifica dei casi d’uso. Si tratterebbe comunque di
modifiche di nessun impatto di carattere concettuale.
Abbiamo detto che i sottocasi sono estensioni del relativo caso d’uso principale. Perché “estensioni” e
non “generalizzazioni”? Perché i sottocasi aggiungono comportamenti diversi al comportamento di base. Ma
si potrebbe anche pensare che i tre sottocasi rappresentano differenti specializzazioni del caso generale. Se
si rinunciasse alle estensioni e alle generalizzazioni (come pure alle inclusioni) e si ricorre al solo stereotipo
<<invoca>> la questione non si porrebbe nemmeno.
5.5
Caso d’uso: Iscrizione
L’iscrizione di un partecipante ad un’escursione si effettua scegliendo l’escursione e introducendo i dati del
partecipante e selezionando gli optional che egli intende acquistare. La descrizione del caso d’uso è in Tabella 3.
CU4: Iscrizione di un partecipante
Attore: Agente
Precondizione:
Il sistema è idle e sullo schermo viene presentata la finestra con il menu
principale (schermata “Menu Principale”).
Sequenza eventi:
1. il caso d’uso inizia quando l’attore clicca il bottone “Iscrizione” sul
“Menu Principale”
2. il sistema mostra una nuova schermata (“Registrazione di un cliente
alle escursioni ”), contenente i campi che servono a descrivere il
cliente, le checkbox per selezionare gli optional, ecc..
3. l’attore agendo sugli elementi presenti sul video seleziona l’escursione,
inserisce i dati del cliente, seleziona gli eventuali optional. La sequenza
può non necessariamente iniziare con la selezione dell’escursione, ma
anche con l’immissione dei dati del cliente; in ogni caso per selezionare
gli optional è necessario che l’escursione sia già stata selezionata. Inoltre sulla stessa schermata può essere presente un pulsante per verificare (in base al codice fiscale) se il cliente è già presente nel sistema
e prelevare automaticamente i suoi dati.
4. quando l’attore preme il bottone “Registra” i dati vengono immessi.
Il processo può essere iterato.
Invariante:
A partire dal punto 1 il caso d’uso termina incondizionatamente in qualunque
momento venga premuto il bottone Esci
Sequenza alternativa:
Al punto 3 se i dati immessi non sono corretti viene presentata la finestra
di dialogo “Errore nei dati immessi per il cliente” nella quale si indica quale
campo deve essere aggiornato. Quando l’operatore preme il bottone “OK”
presente tale finestra il caso d’uso riprende dal punto 1 (senza apportare
modifiche ai contenuti dei campi già immessi).
Postcondizione:
L’eventuale nuovo cliente è stato memorizzato. Viene ripresentato il menu
principale
Tabella 3: Specifica del caso d’uso “CU4: Iscrizione”
In Figura 13 viene riportato il prototipo della schermata di iscrizione.
16
Figura 13: Modello di menu per l’iscrizione di un partecipante alle escursioni.
5.6
Caso d’uso CU5: Cancellazione/ModificaOptional
Se si ragiona come al Paragrafo 5.4 ci si convince che i due casi d’uso “Cancellazione” e “ModificaOptional”
delle figure precedenti si riducono ad un unico caso d’uso, schematicamente cosı̀ descritto:
Caso d’uso Cancellazione/Modifica
Percorso principale Il caso uso inizia quando viene premuto il pulsante “Cancellazione/ModificaOptional”
sul menu principale. Il sistema presenta la schermata “Cancellazione/ModificaOptional”.
L’attore deve anzitutto inserire il codice fiscale del partecipante e premere il pulsante “Cerca”.
Se il codice immesso corrisponde effettivamente a quello di un partecipante, il sistema risponde
presentando i dati del partecipante e la lista delle escursioni a cui è iscritto. Per ogni escursione la
lista riporta tutti i dati di interesse, costo per il partecipante, marcamento degli optional scelti.
L’attore seleziona una escursione tra quelle in lista; se ci sono optional scelti le relative checkbox
della parte del display in cui si inseriscono gli optional riportano il segno di spunta. A questo punto
l’attore può modificare la spunta degli optional.
Se l’attore preme il pulsante “SalvaModifica” si determina il cambiamento degli optional secondo la
nuova spunta. Se preme il pulsante “Cancella” il partecipante viene cancellato dall’escursione.
Il ciclo può essere iterato e ha fine al click di “Esci”. Dopo l’uscita viene ripresentato il menu
principale.
Percorso alternativo Se non si trova il partecipante non viene presentata alcuna lista delle escursioni
e non vengono attivati i pulsanti “Cancella” e “SalvaModifica”, per cui non resta che uscire.
In Figura 14 viene dato il modello del menu “Cancellazione/ModificaOptional”.
5.7
Riesame
È logico porsi la domanda se non sia il caso di riportare i due casi d’uso “Iscrizione”, “Cancellazione/Modifica”
a tre sottocasi di un unico caso d’uso. Tuttavia, diversamente da quel che è stato fatto al Paragrafo 5.4.1,
qui la differenza resta, in quanto l’iscrizione ha un percorso che inizia dalle Escursioni, mentre la cancellazione
del partecipante da un’escursione come pure la modifica degli optional scelti hanno un percorso che inizia
dai Partecipanti. Mettere tutto a un’unica videata rischia di renderla confusa per chi la usa e complessa da
realizzare. Per questo manterremo la separazione tra “Iscrizione” e “Cancellazione/Modifica”, per la quale però
prevederemo du sottocasi “Cancellazione” e “ModificaOptional” .
17
Figura 14: Prototipo di schermata per la cancellazione dalle escursioni o per la modifica degli optional scelti.
Avendo ipotizzato (Figura 14 che la cancellazione di un partecipante da un’escursione o la modifica degli
optional scelti richiedono la conoscenza del codice fiscale del partecipante, occorre aggiungere da qualche parte
la funzionalità di visualizzazione del codice fiscale dei partecipanti. Si può aggiungere un caso d’uso specifico
“Visualizza Clienti” Il caso d’uso è ovvio ed evitiamo di descriverlo5 . Esso comporta il bottone “Lista Clienti”
sul menu principale.
Mettendo assieme di ragionamenti del Paragrafo 5.4.1, con i precedenti si arriva al diagramma finale dei casi
d’uso di Figura 15 e al menu principale schematizzato in Figura 16. In Figura 15 si è preferito usare la relazione
di invocazione tra caso d’uso principale e subordinati. Si faccia attenzione a come i casi d’uso principali sono
identificati in Figura 15; useremo tale identificazione nel resto del documento.
5.8
Conclusioni
Possiamo trarre alcune conclusioni e alcuni insegnamenti da quanto abbiamo fatto.
5.8.1
Cosa rappresentano i casi d’uso
I casi d’uso dicono cosa il sistema fa. Sono una specifica del comportamento del sistema come percepito dai
suoi utenti finali.
5.8.2
Quanto devono essere astratti
È bene che i casi d’uso siano concreti: casi d’uso scritti in modo astratto servono a poco o nulla. Per questo
motivo devono fare riferimento a un modello di dominio. In tal modo i casi d’uso sono orientati verso le entità
alla cui manipolazione essi sono preposti. Un modello di dominio fornisce, come minimo, il significato dei termini
usati (glossario). L’impiego di termini il cui significato è chiaro riduce il rischio di specifiche ambigue.
5.8.3
Come si scrivono i casi d’uso
Dovendo rappresentare la dinamica del tipo “azione e reazione”, ovvero illustrare l’esecuzione di sequenze di
passi6 , i casi d’uso devono essere espressi usando verbi al tempo indicativo presente.
Al fine di evitare ambiguità le entità devono essere menzionate con il nome univoco ad esse assegnato nel
modello di dominio. Per la stessa ragione si deve dare un nome specifico alle singole viste (videate).
5 Questa funzionalità viene aggiunta soprattutto ai fini della sperimentazione col sistema. Infatti, nella realtà, una persona è in
grado di fornire il proprio codice fiscale. Invece, nel fare le prove si inseriscono usualmente codici a caso, che poi sono difficili da
ricordare! Abbiamo deciso di prevedere un caso d’uso a parte proprio per non toccare i precedenti casi d’uso che vorrebbero essere
fedeli alla realtà.
6 Si osservi che, diversamente dai casi d’uso, i requisiti sono espressi come clausole.
18
Figura 15: Diagramma finale dei casi d’uso
Figura 16: Prototipo finale del menu principale
Allegare alla descrizione testuale di un caso d’uso i prototipi delle schermate che esso prevede. Un prototipo
di schermata è un modo di comunicazione quasi sempre più efficace delle parole
Prevedere sempre i possibili percorsi alternativi al percorso principale di ciascun caso d’uso.
5.8.4
Come si validano
Alla fine dell’analisi occorre fare un esame critico al fine di validare quanto fatto. Questo esame deve essere
condotto dall’analista assieme agli utenti/committenti del sistema, in modo che sia evitata la spiacevole situazione in cui vengono a trovarsi non pochi progetti di sviluppo software, quando, in fase avanzata, ci si accorge
che quel che fa il sistema (quello che è stato realizzato) non corrisponde a quello che il cliente/committente si
aspettava.
Dopo la necessaria validazione non è detto che il lavoro sui casi d’uso sia finito: il passo successivo del
processo, l’analisi di robustezza, può evidenziare la necessità di tornarci sopra al fine di eliminare incoerenze ed
ambiguità nascoste.
19
5.8.5
Ma, alla fin fine, che cosa è stato prodotto?
Se si mettessero assieme e si riorganizzassero le descrizioni dei casi d’uso (trasformando frasi come “l’attore
preme il pulsante ... il sistema mostra la finestra ...” in “premere il pulsante ... viene mostrata la finestra ...”),
si otterrebbe.....il manuale utente!
Questo è il risultato di maggior rilievo, riassumibile nell’equazione
Analisi dei casi d’uso = Stesura del manuale utente
Fare l’analisi dei casi d’uso vuol dire definire esattamente come il sistema si comporterà; molto prima di dar
corso alla sua realizzazione.
6
Casi d’uso e requisiti
Come è stato affermato in precedenza, è bene che i casi d’uso vengano esaminati criticamente dall’analista
assieme agli utenti/committenti del sistema.
Un aspetto importante da verificare è se tutti i requisiti sono stati considerati. A tale scopo conviene
incrociare i requisiti con i casi d’uso al fine di stabilire se tutti i requisiti sono “coperti” dai casi d’uso.
Il nostro sistema è particolarmente semplice. La verifica di copertura può essere fatta ricorrendo a un foglio
elettronico come in Figura 177 . Si noti che CU4, introdotto per la ricerca del codice fiscale di un cliente, non
copre nessun specifico requisito.
Figura 17: Copertura dei requisiti da parte dei casi d’uso. Non si sono riportati i sottocasi perché ciò non
avrebbe aggiunto alcuna informazione in più.
7
Un intermezzo: modellazione dell’interfaccia con le macchine a
stati
Nel fare l’analisi dei casi d’uso abbiamo presentato le schermate in forma anche troppo dettagliata. Come
abbiamo già osservato al termine del Paragrafo 5.4, non vale la pena di esagerare con i dettagli, in quanto
il framework impiegato nella realizzazione della GUI potrà imporre scelte obbligate, contrastanti con quanto
ipotizzato. I prototipi delle finestre hanno lo scopo di fornire una chiara rappresentazione delle funzionalità che
esse implicano.
L’analisi aveva anche messo in mostra una certa difficoltà nel descrivere il comportamento dell’interfaccia.
Per questo motivo, e al fine di facilitare il successivo lavoro di analisi, conviene descrivere il comportamento
dell’interfaccia attraverso i diagrammi di stato.
7.1
Diagramma di stato aggregato
In Figura 18 viene data la rappresentazione del diagramma di stato in forma aggregata. Sono stati previsti 4
stati in corrispondenza ai 4 casi d’uso di Figura 15. Il diagramma non ha bisogno di commenti: dallo stato S0
l’interfaccia (e il sistema) passa allo stato che corrisponde all’espletamento della funzionalità richiesta. Si noti
che in corrispondenza dei cambiamenti di stato vengono presentate le schermate corrispondenti.
7 Alcuni
strumenti Case forniscono supporto allo svolgimento di questo tipo di analisi.
20
Figura 18: Diagramma di stato dell’interfaccia.
7.2
Inserimento/Rimozione/Modifica escursioni
Il diagramma di stato corrispondente a questo macrostato è in Figura 19. Si osservi che la selezione iniziale della
Figura 19: Esplosione dello stato aggregato SIRMEsc di Figura 18.
data fa passare allo stato di inserimento, nel quale l’attore può riempire i campi della maschera a video; mentre
la selezione iniziale di una escursione fa passare allo stato di rimozione/modifica. Il diagramma mostra che c’è un
passaggio tra questi due stati in base alla selezione di una data o di una escursione. A ogni passaggio il video viene
aggiornato (in particolare vengono attivati/disattivati i pulsanti “Inserisci” oppure “Rimuovi” e “SalvaMod”.
Al click su un di questi pulsanti avviene la memorizzazione/aggiornamento dei dati e l’aggiornamento del video.
Da ogni stato si esce al click “Esci”.
21
7.2.1
Iscrizione
Il diagramma di stato è in Figura 20.
Figura 20: Diagramma di stato relativo all’iscrizione.
7.3
Cancellazione da una escursione/Modifica optional
Il diagramma di stato relativo alla cancellazione da una escursione e alla modifica degli optional scelti è a sinistra
in Figura 21. A destra il diagramma relativo all’elencazione dei clienti.
Figura 21: A sinistra la Cancellazione/Modifica optional; a destra l’elencazione dei clienti.
8
Analisi
Dobbiamo ora fare l’analisi del come vengono ottenute le funzionalità che il sistema deve presentare. Si tratta
della cosiddetta analisi di robustezza (robustness analysis). Essa consiste, per ciascun caso d’uso nell’identificare
le funzioni da svolgere e le classi del modello che intervengono. Le funzioni verranno poi allocate a eventuali
classi applicative aggiuntive e, in parte, quando sia conveniente, alle classi del modello di dominio. Lo scopo
dell’analisi di robustezza è collegare l’analisi dei casi d’uso alla specifica dettagliata di progetto in cui vengono
definite le operazioni di sui è responsabile ciascuna classe.
Nel fare l’analisi seguiremo questo criterio: Inizialmente identificheremo le funzioni applicative prevedendo
per ciascuna di esse uno specifico controllore, successivamente potremo accorpare più funzioni applicative in
un medesimo controllore. Nel fare l’analisi approfondiremo anche il ruolo delle classi del modello. Ciò potrà
suggerire (piccole, sperabilmente) variazioni al modello stesso.
L’analisi fa riferimento ai tre stereotipi (di analisi) Boundary, Controller, Entity.
22
• Entity (entità): classi di oggetti che rappresentano la semantica del dominio applicativo;
• Controller (controllori) : classi di oggetti che determinano il modo in cui lapplicazione risponde agli input
degli attori;
• Boundary (Viste, Interfacce): classi di oggetti che rappresentano linterfaccia tra gli attori e il (resto del)
sistema.
Regole
Seguiremo queste regole:
• Entità e viste possono comunicare con i controllori (e viceversa), ma non fra di loro.
• I controllori possono comunicare con entità e viste (e viceversa) e possono comunicare tra di loro.
Queste regole sono dettate dall’idea di identificare le funzioni. Più avanti verranno rilassate.
Si tratta ora di prendere ciascun caso d’uso e tracciare per esso il relativo diagramma di analisi.
8.1
Analisi di CU1
Cominciamo con la parte a comune del caso d’uso. La Figura 22 riporta l’inizio della tracciatura del diagramma.
E’ stata introdotta la funzione (il controllore) displayVP responsabile della presentazione del (video del) menu
principale (VPrinc).
Figura 22: Primo passo nella costruzione del diagramma di robustezza del caso d’uso CU1. Il diagramma a
destra mostra la situazione iniziale, prima che venga cliccato il bottone “Inserimento/Eliminazione/Modifica”.
La Figura 23 mostra il diagramma della parte comune di CU1 (cioè quanto riportato testualmente nella
figura stessa). Il click del pulsante “Inserimento/Eliminazione/Modifica” (indicato come “InsRimM” per ragioni di spazio) viene rilevato da ContrVPrinc, il controllore del menu principale (ovvero il controllore associato
alla vista VPrinc). È responsabilità di quest’ultimo presentare il video VIRM relativo al menu di inserimento/rimozione/modifica (la schermata è riportata nella parte destra di Figura 11). Poiché in questo video viene
presentato l’elenco delle escursioni già nel programma dell’agenzia è necessario che ContrVPrinc abbia accesso
al modello, ovvero alla sua interfaccia Agenzia, per prelevare l’elenco delle escursioni e presentarle. Il modo
in cui ContrVPrinc acquisirà l’elenco delle escursioni è un aspetto da rimandare alla fase successiva, quella
dell’anali attraverso i diagrammi di sequenza, con i quali si identificheranno i metodi delle classi, ovvero le loro
responsabilità.
8.1.1
Inserimento
La descrizione testuale del sottocaso, nella sua versione finale, è stata riportata a Pagina 14 del Paragrafo
5.4. Abbiamo già osservato (Paragrafo 5.4.1) la descrizione è fin troppo dettagliata e che certe assunzioni sul
23
Figura 23: Diagramma corrispondente alla parte comune di CU1.
comportamento dell’interfaccia avrebbero potuto essere cambiate in base al comportamento del supporto per
l’interfaccia grafica. Per questo motivo conviene semplificare la descrizione come in Figura 24, dove sono state
eliminate le frasi che illustrano dettagli da rimandare alla fase implementativa. Nella parte destra di Figura 24
viene riportato il corrispondente diagramma di robustezza.
Si noti che VIRM (cioè il video “Programma Escursioni” a destra in Figura 11), ha il suo controllore
ContrVIRM che, riferito a questo sottocaso d’uso, riconosce il click sul pulsante “Inserisci” e provvede a chiamare
la funzione di controllo di errore e quella di inserimento nella base di dati della nuova escursione che esso stesso
crea. Lo stesso ContrVIRM provvede ad aggiornare VIRM al termine dell’operazione.
Figura 24: Semplificazione del testo del sottocaso d’suo inserimento.
Si noti che abbiamo trascurato tutti gli aspetti relativi alla prima azione da compiere per entrare nel sottocaso. Abbiamo anche trascurato di entrare nei dettagli relativi all’abilitazione/disabilitazione dei pulsanti,
rinviando il problema alla fase implementativa.
In Figura 24, ContrVIRM e Inserisci sono, ovviamente, rappresentati come due controllori. Ricordiamo che
essi rappresentano le funzionalità che il sistema deve implementare. Possiamo prevedere sin d’ora che la funzionalità di controllo dell’errore dei dati immessi sia semplicemente parte del controllore ContrVIRM (potremo trarre
24
anche vantaggio dai controlli effettuati dal tool rispetto ai contenuti immessi nei campi), mentre è ragionevole
pensare che “Inserisci” sia una funzionalità da assegnare a un effettivo controllore diverso da ContrVIRM.
La funzionalità di controllo di errore determina il percorso alternativo della descrizione testuale.
Osserviamo anche che in Figura 24 la nuova escursione viene creata da Inserisci. Ciò presuppone che a
questo controllore vengano passati da ContrVIRM i dati richiesti per la sua creazione. Può essere ragionevole che
la creazione dell’escursione sia affidata a ContrVIRM. Anche questa è una scelta che potrà essere meglio definita
in fase di implementazione, quando sarà chiaro in che forma vengono resi i dati immessi.
8.1.2
Rimozione
In Figura 25 viene presentato il diagramma di robustezza relativo al sottocaso rimozione. Si noti che nella
descrizione testuale abbiamo rimosso alcune frasi di dettaglio.
Figura 25: Diagramma di robustezza per il sottocaso di rimozione delle escursioni. Dal testo sono state cancellate
frasi ritenute inutili a questo stadio dell’analisi.
8.2
Analisi di CU2
In Figura 26 viene presentato il diagramma relativo alla registrazione di un partecipante.
Figura 26: Diagramma relativo all’inserimento di un partecipante.
25
8.3
Continuazione
I diagrammi di robustezza relativi agli altri casi d’uso si ottengono analogamente. Si noti che nello sviluppare
l’analisi abbiamo seguito il criterio di mettere in evidenza le funzioni necessarie al caratterizzare l’esecuzione dei
casi s’uso, evitando i dettagli inutili, per non perdere la focalizzazione sugli aspetti caratterizzanti il problema.
Ciò ha comportato una parziale riscrittura dei casi d’uso.
L’analisi appena conclusa avrebbe potuto mettere in mostra la necessità di apportare modifiche anche al
modello di dominio e di maggiori interventi sui casi d’uso.
L’anali si conclude con la verifica che tutti i casi d’uso sono stati trattati e che le opportune modifiche sono
state apportate.
Il passo successivo è la stesura dei diagrammi di sequenza, in modo da identificare le reponsabilità delle classi, ovvero i metodi di interfaccia. I diagrammi possono essere stesi a partire dai diagrammi di robustezza,
rappresentando le funzioni identificate come altrettanti controllori indipendenti.
Poiché nel fare l’analisi abbiamo scoperto che il nostro sistema si compone dei controllori associati alle
differenti schermate e da eventuali altri controllori che interagiscono direttamente con gli oggetti del modello,
conviene prevedere sin d’ora quali saranno questi possibili controllori e allocare ad essi le funzioni applicative
identificate (Inserisci, Rimuovi, ecc.). Tenuto conto del fatto che si hanno operazioni che vanno a manipolare
le escursioni e operazioni che vanno a manipolare i partecipanti, è ragionevole prevedere sin d’ora due classi
applicative EscManager e ClientManager e allocare convenientemente ad esse le funzionalità individuate.
Stabiliamo anche (dopo aver dato uno sguardo alle Swing con cui si implementerà la GUI) di affidare, ove
possibile, ai controllori delle schermate le funzioni di controllo della correttezza degli ingressi, ecc..
9
Specifica delle classi (diagrammi di sequenza)
In base alle considerazioni ora svolte la stesura dei diagrammi è piuttosto agevole.
9.1
DS1: diagramma di sequenza relativo a CU1
Il diagramma è in Figura 27.
Figura 27: Diagramma di sequenza per la parte comune di CU1.
Come si vede, il controllore del video principale, per poter presentare le escursioni chiede a EscManager
l’elenco delle escursioni. A tal fine EscManager presenta il metodo getEscursioni() la cui esecuzione rimanda
all’esecuzione del metodo omonimo che deve essere previsto sull’interfaccia di Agenzia. Una volta costruito il
contenuto da presentare a video, ContrVPrinc non fa altro che istanziare la videata VIRM con tale contenuto.
9.1.1
DS1Ins: diagramma di sequenza relativo all’inserimento di una escursione a programma
Il diagramma in questione è in Figura 28.
26
Figura 28: Diagramma di sequenza relativo all’inserimento di una escursione nel programma dell’agenzia.
Il diagramma illustra che, prima di premere il pulsante di inserimento, l’attore può immettere i dati. Il tool
StarUML, a saperlo usare bene, è in grado di rappresentare cicli. Tuttavia anche qui siamo di fronte al fatto
che il modo in cui i dati verranno immessi potrà dipendere dalle caratteristiche dell’interfaccia. Lo schema di
Figura 28 ricorda che c’è una fase di immissione senza dare altri particolari da decidere nel seguito. In questo
caso l’applicazione EscManager deve presentare il metodo inserisci(). In Figura 28 si è fatta l’ipotesi che a
EscManager vengano passati i dati per istanziare la nuova escursione che poi viene aggiunta alla base dati ; a
tale scopo la classe Agenzia deve prevedere il metodo addEsc.
In Figura 29 viene mostrata la soluzione alternativa di far istanziare la nuova escursione da ContrVIRM.
Figura 29: Diagramma di sequenza relativo all’inserimento di una escursione nel programma dell’agenzia,
modificato rispetto a quello di Figura 28 facendo istanziare l’escursione direttamente da ContrVIRM.
Si tratta di una scelta più che legittima, in quanto ContrVIRM ha tutti i dati per istanziare l’escursione
27
appena immessa. Cosı̀ facendo deve passare a EscManager solo l’escursione e non una lunga sequenza di
parametri perché quest’ultimo possa istanziarla.
In questo caso al diagramma sono stati aggiunti alcuni commenti. Il ricorso ai commenti è utile per evitare
le complicazioni relative alla rappresentazione di cicli e dei percorsi alternativi sul diagramma.
9.2
DSCU2: Diagramma di sequenza relativo all’iscrizione di un partecipante
Il diagramma in questione è in Figura 30.
Figura 30: Diagramma di sequenza relativo all’iscrizione di un partecipante. Si noti che non viene fatto il test
per verificare se il cliente è o meno nel sistema. Il caso mostrato è quello in cui il cliente non è presente, per cui
esso deve essere istanziato (con i dati introdotti a video).
Si noti che il diagramma di Figura 30 non tiene conto del fatto che un partecipante deve effettuare una
scelta (eventualmente nulla) relativamente agli optional. La scelta (o le scelte - per ora facciamo conto che sia
una sola) richiede la sua istanziazione e il suo collegamento con il partecipante e con la relativa escursione. Il
diagramma si modifica come in Figura 31, tracciato trascurando la parte del controllo di errore di Figura 30.
Nel tracciare la figura si è fatta l’ipotesi che ClientManager abbia da ContrVreg non solo i dati relativi al
(nuovo) partecipante e alla sua scelta, ma anche il riferimento all’escursione (sia la scelta che il partecipante
potrebbero essere istanziati da ContrReg).
Il diagramma di Figura 31 presuppone che quando una Scelta viene creata le venga passato il riferimento
al partecipante e all’escursione e che poi il riferimento alla scelta venga passato al partecipante attraverso il
metodo linkScelta() di quest’ultimo.
9.3
Continuazione e commento
Per non tediare troppo il lettore ci fermiamo qui.
Nei diagrammi precedenti si è dato conto solo degli aspetti principali. Relativamente al modello di dominio,
questo ci ha portato a identificare il metodo addESC() di Agenzia, il metodo addPartecip() di Escursione e
linkScelta() di Partecipante.
In modo analogo si tracciano i diagrammi relativi agli altri casi d’uso –sempre con riferimento ai corrispondenti diagramma di robustezza– e si individuano i metodi delle classi del dominio, come pure possibili, eventuali
metodo aggiuntivi (a quelli trovati con l’analisi di robustezza) delle classi applicative.
28
Figura 31: Trattamento delle scelte nella registrazione di un partecipante.
10
Organizzazione
In Figura 32 viene illustrata la naturale suddivisione dei componenti in tre distinti packages corrispondenti ai tre
livelli di logica del modello MVC. Nello sviluppare il sistema potrebbe essere vantaggioso individuare ulteriori
packages, per esempio un package di carattere trasversale contenti classi di utilità come la Data o simili.
Figura 32: Organizzazione del software in più package.
11
11.1
Approfondimenti
Il modello
Vediamo ora di entrare maggiormente nel dettaglio per quanto attiene le classi del modello.
La prima questione che si pone è se, con riferimento al modello di Figura 4 si debba specializzare la
classe Escursione nelle due classi GitaInBarca e GitaACavallo. Valgono anche in questo caso considerazioni analoghe a quelle fatte al Paragrafo 4.1 in riferimento agli optional. Nel nostro sistema non c’è alcuna
differenza comportamentale tra la gita in barca e la gita a cavallo, dunque non ha nessun senso prevedere due
classi distinte. Esse si possono differenziare attraverso un semplice attributo (che chiameremo tipo). Diverso
sarebbe stato il caso in cui i due tipi di escursione si fossero differenziati quanto a numero di attributi e a metodi;
in tal caso sarebbe stato necessario prevedere classi derivate.
Un altro aspetto importante è come si realizza la classe di associazione. Ci sono due modi:
a) prevedere che una scelta identifichi un solo optional per una data escursione tra quelli selezionati dal
partecipante. In questo caso la classe Scelta ha solo due attributi: i riferimenti agli oggetti TipoOptional
e Escursione;
b) prevedere che una scelta identifichi tutti gli optional scelti dal partecipante in una data escursione. In
questo caso la classe Scelta può contenere fino a tre coppie dei due attributi del punto precedente;
29
La soluzione a) corrisponde al diagramma di Figura 33, mentre la soluzione b) corrisponde al diagramma di
Figura 34. Notare che nelle due figure oltre a rappresentare gli attributi delle classi sono stati anche rappresentati
i metodi individuati in precedenza con i diagrammi di sequenza. A questo proposito conviene fare le osservazioni
del paragrafo che segue.
Figura 33: Modello di dominio per il caso a) relativamente alla classe Scelta, con evidenziazione degli attributi
e dei metodi.
Figura 34: Modello di dominio per il caso b) relativamente alla classe Scelta.
11.2
Quando andare a fondo con i diagrammi di sequenza?
I diagrammi di sequenza sono un ottimo strumento per capire come avvengono le interazioni tra gli oggetti.
Ma non si deve pensare di poterli usare fino a “tirare fuori” ogni metodo. Cercare di definire tutti metodi delle
classi attraverso i diagrammi di sequenza sarebbe una inutile perdita di tempo. I diagrammi di sequenza devono
darci le interazioni fondamentali, i metodi di carattere secondario e di appoggio si fissano meglio al tempo di
programmazione. Al tempo di programmazione certe funzionalità possono essere raggruppate o suddivise o
spostate da una classe all’altra a seconda della convenienza. Ciò porta facilmente a rimettere in discussione
eventuali scelte di dettaglio fatte a livello di diagrammi di sequenza.
Per esempio, la Figura 33 mostra il metodo addPartecipante() della classe Escursione. E’ ovvio che
Partecipante dovrà avere il metodo duale; tale metodo è stato chiamato addEsc() in Figura 33 e 34. Esso non fa
altro che per aggiungere in Partecipante il riferimento all’escursione a cui egli viene iscritto; probabilmente tale
metodo sarà chiamato dal metodo addPartecipante() di Escursione (all’atto dell’iscrizione di un partecipante
a una escursione), passando come parametro l’escursione stessa. Il metodo addEsc() non è stato trovato
attraverso la stesura dei diagrammi di sequenza, ma con ovvi ragionamenti sul modello. Avremmo anche
30
potuto omettere la sua rappresentazione, in quanto dovrebbe essere un fatto che se si iscrive un partecipante a
una escursione è necessario anche mettere nel partecipante il collegamento all’escursione.
Quanto si debba andare a fondo è in larga parte dettato dalla sensibilità dell’analista e dal contesto in cui
opera (quanto è esperto e capace il programmatore, se diverso dall’analista).
In fase di programmazione converrà equipaggiare le classi del modello con metodi get e set degli attributi.
Per esempio, il partecipante sarà istanziato passando al costruttore il codice fiscale e il nome. Assumiamo
che questi attributi non possano essere cambiati e quindi non prevediamo i metodi setCF() e setNome(), ma
prevediamo i metodi getCF() e getNome() che serviranno nel caso in cui si debba cercare un partecipante in
base al suo CF (che lo individua univocamente). Possiamo stabilire la convenzione che metodi come set()
e get() non vengano mai rappresentati nei diagrammi e che il programmatore provveda al a seconda delle
necessità al tempo di programmazione.
In altre parole, il diagramma di figura ?? è un buon punto di partenza per iniziare la programmazione, ma
dobbiamo tenerci pronti ad aggiungere metodi alle classi del modello.
Per meglio chiarire il modo con cui si aggiungono i metodi, torniamo al diagramma di sequenza di Figura 30.
Come dice la didascalia della figura, la sequenza rappresentata corrisponde al caso in cui chi si iscrive non è
presente nel sistema. Questo presuppone che prima di creare il nuovo partecipante sia stata effettuata la relativa
verifica (non indicata in Figura 30). La verifica può essere effettuata prevedendo che l’interfaccia del modello
(Agenzia) esponga il metodo getPartecipante(String cF), sotto riportato.
Il metodo restituisce il partecipante se già presente nel sistema, restituisce null se il partecipante non
esiste. Con riferimento al modello di Figura 33, avendo chiamato partecipanti la collezione di partecipanti in
Agenzia, il metodo prenderebbe questa forma:
public Partecipante getPartecipante(String cF){
Iterator<Partecipante> it = partecipanti.iterator();
while(it.hasNext()){
Partecipante p = it.next();
if (p.getCF() == cF) return p}
}
}
return null;
}
Il ClientManager di Figura 30 si sincera dell’esistenza del partecipante, il cui codice fiscale è “RBRTGR56..”,
in questo modo
Partecipante p = agenzia.getPartecipante("RBRTGR56..");
if (p== null) p = new Partecipante("RBRTGR56..");
11.3
Altre divagazioni sul modello
Torniamo sulla questione degli optional. Abbiamo osservato che essi sono delle costanti usate sia da Escursione
che da Scelta (Cfr. Paragrafo 11.1). Si potrebbe pensare di modificare li classi Escursione e Scelta come in
Figura 35, dove:
• La classe Escursione ha ora 3 campi di tipo boolean (ovviamente da inizializzare all’atto dell’istanziazione) che dicono quali sono gli optional previsti
• La classe Scelta ha pure 3 campi di tipo boolean che dicono se un dato optional è stato scelto.
Naturalmente lo schema di Figura 35 presuppone che i tre attributi rappresentino ordinatamente i tre possibili
tipi di optional, il cui prezzo è fisso ed è descritto altrove, per esempio come un vettore che riporta il costo (ed
eventualmente il nome) per ciascun elemento.
Lo schema di Figura 35 è apparentemente più semplice, ma presenta qualche inconveniente. In particolare
supponiamo che ora si voglia prevedere un ulteriore tipo di optional. A tal fine occorre modificare (cioè ricompilare!) le classi Escursione e Scelta,oltre al vettore che descrive gli optional (oltre naturalmente a modificare
l’interfaccia prevedendo una ulteriore checkbox per i nuovo optional. Il programma ricompilato non sarebbe più
compatibile con gli oggetti creati in precedenza, ovvero, se ci fosse una base di dati, occorrerebbe convertire
anche la base di dati. Dunque, la soluzione appena prospettata è senz’altro da scartare.
31
Figura 35: Modifica al modello di dominio, per rappresentare gli optional non come oggetti .
Si potrebbe risolvere il problema della necessità di ricompilare prevedendo che gli optional, sia in Escursione
che in Scelta anziché attributi di tipo scalare fossero strutture dati, per esempio un vettore, o meglio ancora
una struttura tipo Collection di Java. Ma allora non conviene davvero che le componenti di queste strutture
(questi aggregati) siano di tipo boolean (che rimandano in modo implicito, cioè non manifesto nel modello,
alla definizione degli optional, con tutte le complicazioni pratiche per il programmatore, che deve ricordarsi chi
corrisponde a chi). Conviene che questi aggregati rimandino direttamente, in modo visibile nel modello e senza
convenzioni per il programmatore, ai tipi di optional. Ma questa è proprio la nostra soluzione!
Notare che se vogliamo che il programma sia espandibile nel modo detto, l’aggregato optional di Figura 33
e 34 non deve avere una molteplicità limitata a {0..3} ma deve essere generale {*}. Lo stesso si dovrebbe dire
per il corrispondente aggregato in Scelta di Figura 34.
Si noti che con le modifiche appena dette, l’aggiunta di un nuovo tipo di optional richiede solo che venga
istanziato il nuovo oggetto. Ciò introdurrebbe un nuovo caso d’uso, cui corrisponderebbe lo svolgimento della
funzione applicativa che istanzia, il nuovo tipo di optional (selezionabile attraverso l’interfaccia). Per quanto
riguarda quest’ultima, essa andrebbe modificata nel senso di rimpiazzare le 3 checkbox con una lista da cui
selezionare.
Si lascia al lettore l’esame di quale sia preferibile tra il modello di Figura 33 e quello 34.
Una prima realizzazione
Quanto abbiamo fatto ci consente di passare alla realizzazione di un prototipo senza il supporto della memorizzazione, ovvero come sistema in cui gli oggetti sono tutti in memoria centrale. Per non rompere il flusso di
esposizione degli aspetti concettuali, relativi alla realizzazione di un effettivo sistema, capace di trattare dati
persistenti, rinviamo all’Appendice A per la realizzazione del prototipo di soli oggetti in memoria.
32
12
La persistenza
Fino a questo punto abbiamo prestato attenzione al modello a oggetti come sei il sistema funzionasse solo in
memoria centrale. È evidente che in un applicazione come questa i dati devono essere persistenti, nel senso che
essi devono sopravvivere allo spegnimento/accensione del sistema. In altre parole i dati devono essere persistenti,
ovvero memorizzati permanentemente sulla memoria ausiliaria. Questo introduce il problema di come mappare
gli oggetti del modello sui dati persistenti e viceversa. La Figura 36 schematizza la natura del problema: da
un lato il mondo “vivo” degli oggetti in memoria (entità con comportamento), dall’altro il mondo “morto” dei
dati (record, tabelle, ecc.).
Figura 36: Il problema della persistenza.
Nelle cosiddette “applicazioni enterpise” di cui la nostra, nel suo piccolo, è un esempio, la permanenza è
gestita in forma di base di dati (relazionale). La Figura 37 mostra il classico modello a livelli di un qualunque
sistema. EIS sta per Enterprise Information System, la base di dati; il livello di persistenza è quello che
interfaccia il modo a oggetti dell’applicazione con la base di dati.
Figura 37: Schematizzazione a livelli di un sistema Enterprise. Il Domain layer va inteso come il livello che
contiene gli oggetti del (nostro) modello di dominio e gli oggetti che realizzano la logica applicativa.
In Figura 38 il livello di dominio è stato suddiviso in modo da separare gli oggetti del modello dagli oggetti
applicativi (ad ambedue è stata data una nuova denominazione).
12.1
Una tecnica elementare
Un modo semplice per realizzare la persistenza consiste nel ricorrere al concetto di “workspace”. In altre parole
quando il programma viene messo in esecuzione esso legge da memoria di massa i dati salvati alla seduta
precedente e costruisce il grafo dei data objects in memoria. Alla chiusura del sistema, l’intero grafo dei data
objects viene salvato su file.
Questo è il modo tipico di lavorare di programmi anche complessi ma che non abbiano come missione
fondamentale quella di costituire una base di dati.
33
Figura 38: A sinistra la schematizzazione UML del sistema a livelli (Figura 37); a destra l’esplosione del livello
intermedio. Si noti che questo livello si compone di service object, i nostri oggetti che implementano la logica
applicativa, e data objects, gli oggetti del nostro modello di dominio.
Java fornisce la serializzazione. Meglio ancora si può fare ricorso a librerie di pubblico dominio come
XStream.
12.2
Persistenza e basi di dati
Nelle cosiddette applicazioni Enterprise la soluzione del paragrafo precedente non può essere ovviamente usata.
Questi sistemi infatti sono caratterizzati da una operatività non stop ed hanno come fondamento l’esistenza di
una base di dati, normalmente di tipo relazionale.
In Figura 39 viene schematizzato il problema della mappatura tra il mondo a oggetti e quello relazionale.
In estrema sintesi, i metadati di mappatura servono al mapper per svolgere queste funzioni:
• trasformare il contenuto delle tabelle del data base negli attributi dei corrispondenti oggetti;
• trasformate i valori degli attributi degli oggetti nei valori dei campi delle (righe delle) tabelle della base
di dati relazionale.
Figura 39: Mappatura tra oggetti e relazioni (Object Relational Mapping, ORM).
In Figura 40 viene rappresentato il problema della mappatura in riferimento al cosiddetto impedance mismatch tra mondo relazionale e mondo a oggetti, ovvero al fatto che due rappresentazioni sono diverse non solo
perché gli oggetti hanno un comportamento contrariamente alle righe delle tabelle, ma anche perché i legami
tra oggetti si esprimono in diverso rispetto ai legami tra (righe nelle) tabelle.
12.3
Mappatura
La mappatura può essere:
• Manuale: quando il codice SQL per l’accesso ai dati viene prodotto manualmente dal programmatore e
integrato nell’applicazione
34
Figura 40: Il problema della mappatura: l’impedance mismatch tra modo relazionale e mondo a oggetti.
• Automatica: quando il livello di persistenza, attraverso i metadati di mappatura, provvede ad accedere
direttamente ai dati (genera automaticamente il codice SQL necessario). Si tratta evidentemente della
soluzione schematizzata in Figura 39.
• Trasparente: quando il modello di dominio non è sottoposto ad alcun vincolo dovuto allo strato di
persistenza;
• Invasiva: quando il modello di dominio deve implementare e/o estendere classi e interfacce dello strato di
persistenza per poter usufruire del servizio di mappatura, per esempio dotando i data objects con i metodi
per salvarli, caricarli, ecc., come schematizzato in Figura 41, dove alla classe Knight sono stati aggiunti i
metodi save(), load(), ecc.. Spetta a questi metodi effettuate le corrispondenti operazioni in termini di
chiamate a JDBC.
Figura 41: Aggiunta dei metodi per gestire la persistenza (esempio).
12.3.1
Connessione alla base di dati
Per l’accesso da parte dei programmi alle basi di dati si usa ODBC (Open Data Base Connectivity), una
API attraverso la quale i programmi possono inviare stringhe SQL alla base di dati senza conoscerne le API
proprietarie del DBMS (ciò richiede uno specifico driver tra ODBC e l’effettivo DBMS). JDBC è la versione
ODBC per Java.
Schematicamente, per accedere a una base di dati relazionale da un programma Java, occorre:
• Aprire una connessione verso la base di dati (istanziando l’oggetto con).
• Preparare uno statement SQL (istanziando l’oggetto stmt).
• Eseguire la query (tramite il metodo executeQuery() di stmt), in modo da ottenere un result set.
Per esempio, se ipotizziamo che nella base di dati ci sia la tabella PARTECIPANTE con gli statement
seguenti si ottiene un result set corrispondente alla tabella Partecipante.
35
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select * from PARTECIPANTE");
Il result set ha, inizialmente, il cursore posizionato in posizione antecedente la prima riga: il metodo next()
porta il cursore a puntare alla prima riga; successive esecuzioni di next() fanno avanzare il cursore. Sul result set
di Java è definita una varietà di operazioni. Ad esempio, il metodo getString(String columnName) restituisce
in forma di stringa il contenuto della colonna indicata per la riga individuata dal cursore.
12.4
Mappatura manuale, invasiva e non trasparente
Con riferimento a quanto detto al termine del Paragrafo 11, consideriamo la ricerca di un partecipante. Assumiamo di non vole cambiare in alcun modo il ClientManager mantenendo la classe Agenzia, in modo che le
applicazioni non siano in alcun modo influenzate dalla presenza della base di dati.
Agenzia avrà ancora il metodo getPartecipante(String cF), solo che questa volta l’oggetto, se non è già
in memoria perché ci è stato portato in precedenza, deve essere costruito a partire dalle informazioni nella base
dati. Schematicamente, il metodo si trasforma in questo modo:
public Partecipante getPartecipante(String cF){
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select * from PARTECIPANTE");
while (rs.next()){
String cFx = rs.getString("CF");
if (cFx == cF) {
Partecipante p = new Partecipante(cF);
p.setName(rs.getString("NAME"));
return p;
}
}
return null;
}
Abbiamo assunto che la tabella PARTECIPANTE abbia le colonne CF e NOME. Se nella tabella si trova il
partecipante, allora viene costruito l’oggetto Partecipante usando lo stesso codice fiscale e dandogli il nome
che si ricava sempre dalla base di dati. E’ ovvio che se l’oggetto da costruire ha molti attributi occorrono
altrettante operazioni set().
Nell’esempio che abbiamo fatto l’Agenzia ricostruisce il partecipante dalla base di dati e quindi deve avere
completa cognizione di quali sono i suoi attributi. Se ora un altro oggetto effettua un’analoga ricerca anche
questo deve avere cognizione di tutti gli attributi del partecipante. È molto meglio ipotizzare che è lo stesso
oggetto partecipante che preleva dalla base di dati (dal result set) i valori dei suoi attributi. Analogamente,
quando un oggetto deve essere salvato è meglio che sia esso stesso a scrivere nella base di dati i valori dei suoi
attributi, in modo da evitare di distribuire ovunque la conoscenza (non necessaria) della sua struttura interna.
Questo è il motivo per cui in Figura 41 i metodi per la persistenza sono stati messi entro gli oggetti del dominio.
L’architettura risultante per la mappatura manuale e invasiva è schematizzata in Figura 42 dove l’interfaccia
fornita da JDBC è indicata come sql. Ovviamente niente vieta che alla medesima interfaccia abbiano accesso
anche gli oggetti applicativi, anche ciò porta necessariamente alla loro modifica.
.
12.5
Mappatura manuale trasparente
Se vogliamo evitare di modificare gli oggetti del dominio occorre incapsulare a parte gli aspetti relativi alla
persistenza. Ciò si ottiene con il concetto di DAO (Data Access Object). Un DAO è semplicemente un oggetto
che espone un’interfaccia per le operazioni legate alla persistenza, nascondendo i dettagli al suo interno.
Il ruolo del DAO è illustrato dalla Figura 43. Gli oggetti applicativi usano i DAO per ottenere gli oggetti
o per memorizzare gli oggetti. I DAO nascondono la sorgente dei dati e implementano tutte le funzionalità
per accedere alla base di dati e/o per costruire gli oggetti. C’è un DAO per ciascuna classe, il quale fornisce
metodi come load(), store(), update(), ecc.. Forniranno anche metodi meno generali; ad esempio, il DAO della
classe Escursione potrebbe presentare il metodo getByPartecipante(Partecipante p) che restituisce la lista
36
Figura 42: La persistenza gestita in modo manuale e invasivo.
delle escursioni cui è iscritto p, oppure getByDate(Date d) che restituisce la lista delle escursioni alla data d.
Ovviamente i DAO vengono programmati come sopra, ma nascondono agli oggetti applicativi gli aspetti legati
alla gestione della persistenza ed evitano di sporcare i data objects.
La presenza dei DAO comporta (almeno per quanto riguarda le funzioni di ricerca, salvataggio, ecc.) qualche
modifica alle classi applicative che si avevano nel modello senza persistenza.
Figura 43: Uso dei DAO per la mappatura tra oggetti e contenuto della base di dati.
È interessante rilevare che con lo schema di Figura 43 gli oggetti applicativi hanno nei DAO l’interfaccia per
accedere agli oggetti del modello. Dunque l’oggetto Agenzia, che nel puro modello a oggetti aveva esattamente
questa funzione, diventa del tutto inutile e può essere eliminato.
12.6
Framework ORM
Negli anni recenti, in considerazione dell’affermarsi della programmazione orientata agli oggetti e del consolidarsi
del modello relazionale per i dati, sono cominciati ad apparire sistemi di mappatura che eliminano la necessità
di ricorrere alla programmazione in sql per DAO. Uno di questi sistemi è Hibernate. Si tratta di un sistema che
gestisce in modo automatico la mappatura, appoggiandosi su “metadati” che descrivono oggetti e tabelle e le
loro relazioni. I metadati sono raccolti in un file XML.
In Figura 44 viene data una schematizzazione dell’architettura risultante dall’impiego di Hibernate. Attraverso l’interfaccia session (ovvero attraverso il concetto di sessione o transazione) gli oggetti applicativi chiedono
37
a Hibernate di prelevare/salvare/ecc. gli oggetti. Hibernate usa i metadati di mappatura per creare/salvare gli
oggetti. I data objects non sono in alcun modo “sporcati”.
Figura 44: Schematizzazione dell’impiego di Hibernate per la mappatura.
Hibernate ha un suo linguaggio per accedere alla base di dati, denominato HQL, che permette di inserire
all’interno delle query anche le classi (cioè non solo stringe, interi, long, ecc. cone in SQL).
A titolo di esempio si riporta il seguente tratto di codice (che starà ovviamente entro un oggetto applicativo).
All’inizio viene creata una sessione e avviata una transazione. Successivamente viene ricercato un partecipante
attraverso il suo “id”. A questo punto viene effettuata una query per avere tutte le escursioni la cui lista di
iscritti contiene il partecipante p. Si noti che la stringa di query contiene il parametro :partecipante che,
attraverso il metodo setEntity() viene sostituito con l’oggetto p all’atto della query.
Session session = sessionFactory.getsCurrentSession() ;
session.beginTransaction() ;
Partecipante p = session.load(Partecipante.class, id) ;
String queryStr = "from Escursione as e where :partecipante in elements(e.iscritti)";
Query query = session.createQuery(queryStr).setEntity("partecipante ", p );
Come si vede Hibernate scherma notevolmente rispetto alla base di dati. Se si vuole una completa schermatura si può ricorrere anche in questo caso ai DAO, secondo lo schema di Figura 45. Ovviamente, i DAO sono implementati attraverso le funzionalità provviste da Hibernate, come quelle appena illustrate. Vorrà dire che ci sarà un
DAO per la classe Escursione, dotato del metodo public List<Escursione> getByPartecipante(Partecipante
p); che sostanzialmente corrisponde al codice che abbiamo appena commentato.
38
Figura 45: Uso dei DAO a fronte di Hibernate. I DAO provvedono un’interfaccia standardizzata per gli usi
degli oggetti applicativi e vengono implementati attraverso le funzionalità di Hibernate.
13
Appendice A
Illustriamo ora la realizzazione del sistema con riferimento al caso del puro sistema a oggetti (cioè senza
persistenza e con tutti gli oggetti in memoria) e senza interfaccia grafica.
Mancando l’interfaccia grafica, occorre simulare il comportamento di attore e interfaccia attraverso un programma, il programma principale che viene cosı̀ anche ad assumere il ruolo di programma di test funzionale
complessivo del sistema (senza interfaccia), nel senso che tale programma ha la funzione di esercitare il sistema,
saltando il passaggio attraverso attraverso l’interfaccia. Chiameremo Simulatore la corrispondente classe e
simulatoreAttore il package che la contiene (di esso faranno parte altre classi). La Figura 46 evidenzia il fatto
che il Simulatore ha la funzione di esercitare il sistema. Si noti che tutto ciò che si richiede è che il simulatore
si adegui alle interfacce delle classi applicative.
Figura 46: Uso di un programma di simulazione. Si noti che la classe Simulatore equivale a un programma di
test funzionale che esercita il sistema generando le chiamata ai metodi dell’applicazione.
Con riferimento a Eclipse, si avrà dunque la struttura dei package di Figura 47. Si sono aggiunti il package
39
test per raccogliere le classi di test JUnit e il package common per le classi di uso comune (la sola classe Data).
Inoltre, è stata prevista la classe Builder che serve a popolare con qualche elemento il modello, per evitare di
doverlo fare solo tramite il simulatore.
Figura 47: Struttura dei package sotto Eclipse. Si noti che del package simulatoreAttore fa parte la classe
Builder con la quale viene inizialmente popolato il modello.
Le classi di Figura 47 vengono allegate a questo documento. Il lettore è invitato a consultarle. Per comodità
riportiamo pezzi di codice.
40
13.1
13.1.1
Alcune parti del programma
Il package common
Il package common contiene la sola classe Data usata da più parti. La classe Data qui sotto riportata non tratta
l’anno. Di norma, per la data o altre funzioni di carattere generale conviene servirsi delle librerie Java. Il codice
seguente serve soprattutto a illustrare la definizione dell’operazione di uguaglianza.
package common;
public class Data { // non si tiene conto dell’anno
public int mese;
public int giorno;
public Data(int giorno, int mese){
this.giorno = giorno;
this.mese = mese;
}
public boolean uguale(Data d){
if ((this.giorno == d.giorno ) & (this.mese == d.mese ) ) return true;
else return false;
}
public int getGiorno(){
return giorno;
}
public int getMese(){
return mese;
}
}
13.1.2
Il package modello
La classe Agenzia
package modello;
import java.util.ArrayList;
import java.util.Iterator;
import common.Data;
public class Agenzia {
ArrayList<Partecipante> partecipanti; //lista dei partecipanti
ArrayList<Escursione> escursioni;
/*
* SINGLETON
*/
private static Agenzia instance;
private Agenzia() {
}
public static Agenzia getInstance() {
if (null == instance) {
instance = new Agenzia();
instance.setEscursioni();
instance.setPartecipanti();
}
return instance;
}
/*
* Il metodo clear() ci vuole per eliminare l’agenzia quando si
* fa una test suite. Infatti, essendo l’agenzia un sigleton, dopo
41
* che è stata creata non viene cancellata dal tearDown degli
* unit test. Col risultato che l’esecuzione di uno unit test
* che da solo funziona può dar luogo a malfunzionamento se
* esso prova a reistanziare il singleton, perché nel sistema
* resta quello di prima.
* Non è chiaro se questo comportamento di Junit è voluto o
* se è una sua deficienza.
*/
public void clear(){
instance = null;
}
private void setEscursioni() {
escursioni = new ArrayList<Escursione>();
}
private void setPartecipanti() {
partecipanti = new ArrayList<Partecipante>();
}
public ArrayList<Escursione> getEscursioni(){
return escursioni;
}
public ArrayList<Partecipante> getClienti() {
return partecipanti;
}
public Partecipante getCliente(String cF){
Iterator<Partecipante> ip = partecipanti.iterator();
while (ip.hasNext()){
Partecipante p = ip.next();
if (p.getCF()== cF) return p;
}
return null;
}
public void addEscursione(Escursione e){
escursioni.add(e);
}
public void addCliente(Partecipante p){// pe cf e controllare
partecipanti.add(p);
}
public Escursione getEscursione(Data d, String t){
// Assume che non ci siano due escursioni dello stesso
// tipo lo stesso giorno
Iterator<Escursione> it = escursioni.iterator();
while(it.hasNext()){
Escursione e= it.next();
if(e.getData().uguale(d) && e.getTipo()==t) return e;
}
System.out.println("L’escursione alla data " +d.giorno+"/"+
d.getMese()+ " non esiste!");
return null;
}
public void removeEsc(Escursione e){
Iterator<Escursione> it = escursioni.iterator();
while(it.hasNext()){
Escursione ex= it.next();
if(ex.uguale(e)) {
Iterator<Partecipante> ip = e.getPartecipanti().iterator();
while(ip.hasNext()){
ip.next().unlink(e);
42
}
it.remove();
return;
}
}
}
}
La classe Escursione
package modello;
import java.util.*;
import common.Data;
public class Escursione {
protected String tipo = "";
//GitaBarca, GitaCavallo, ..
protected int id;
protected double costo;
protected int MaxNpartec; //Numero massimo di partecipanti consentito
protected Data data;
protected ArrayList<TipoOptional> optional;
//Optional possibili
protected ArrayList<Partecipante> partecipanti; //partecipanti iscritti
protected int nIscritti = 0; //numero partecipanti correntemente iscritti
//si poteva derivare esaminando Partecipante[]
public Escursione(Data d, String t){
if ((t != "Gita in barca") && (t!= "Gita a cavallo")){
System.out.println("\nTipo Escursione incognito: " +
"è stata creata un’escursione fasulla (Data=null e tipo vuoto).");
return;
}
data = d;
tipo = t;
if (tipo == "Gita in barca"){
MaxNpartec = 6;
costo = 10.;
}
else if (tipo == "Gita a cavallo"){
MaxNpartec = 4;
costo = 40.;
}
optional = new ArrayList<TipoOptional>();
partecipanti = new ArrayList<Partecipante>();
}
public boolean uguale(Escursione e){
if ((this.data == e.getData() ) & (this.tipo == e.getTipo() ) ) return true;
else return false;
}
public int getNIscritti() {
return nIscritti;
}
public double getCost(){
return costo;
}
public String getTipo(){
return tipo;
}
43
public Data getData(){
return data;
}
public ArrayList<Partecipante> getPartecipanti(){
return partecipanti;
}
public void setOptional(TipoOptional t){
if (optional.contains(t)){
System.out.println("Optional "+t.getTipo()+ " già presente per l’escursione del "+
this.getData().getDay()+"/"+this.getData().getMese());
return;
}
optional.add(t);
}
public boolean hasOptional(TipoOptional o){
if (optional.contains(o)) return true;
else return false;
}
public void addPartecipante(Partecipante p){
Iterator<Partecipante> it = partecipanti.iterator();
while (it.hasNext()){
if (it.next().getCF() == p.getCF()){
System.out.println("Partecipante CF "+ p.getCF()+
" nome " + p.getNome()+" già iscritto!");
return;
}
}
if (nIscritti < MaxNpartec){
partecipanti.add(p);
nIscritti++;
p.link2Escursione(this);
}
else {System.out.println("Escursione" + tipo + " del " +
data.giorno + "/" + data.mese + " completa. " + p.getNome()+ " non è stato iscritto");
}
}
public ArrayList<TipoOptional> getOptional(){
return optional;
}
public void removePartecipante(Partecipante p){
Iterator<Partecipante> ip = partecipanti.iterator();
while (ip.hasNext()){
Partecipante pp = ip.next();
if (pp.getCF()==p.getCF()) { //rimozione partec e sue scelte
Iterator<Scelta> is = p.getScelteTutte().iterator();
while (is.hasNext()){ //rimozione scelta
// dalle scelte per questa escursione
if (is.next().getEscursione() == this) is.remove();
}
ip.remove();
//rimozione partecipante dall’escursione
nIscritti--;
p.unlink(this);
return;
}
}
44
System.out.println("Il partecipante non esiste");
}
La classe TipoOptional
package modello;
public class TipoOptional {
String tipo;
double costo;
private static TipoOptional
private static TipoOptional
private static TipoOptional
// realizzata il Singleton in tre versioni in base al tipo
pranzo = null;
merenda = null;
visita = null;
private TipoOptional(String t){
tipo = t;
if (t== "Pranzo") costo = 5.;
else if (t== "Merenda") costo = 2.;
else if (t== "Visita") costo = 1.;
else System.out.println("Impossibile: le chiamate sono sotto!!");
}
public static TipoOptional getIstancePranzo(){
if(pranzo == null)
pranzo = new TipoOptional("Pranzo");
return pranzo;
}
public static TipoOptional getIstanceMerenda(){
if(merenda == null)
merenda = new TipoOptional("Merenda");
return merenda;
}
public static TipoOptional getIstanceVisita(){
if(visita == null)
visita = new TipoOptional("Visita");
return visita;
}
public String getTipo(){
return tipo;
}
public double getCost(){
return costo;
}
}
13.2
Il package simulatoreAttore
La classe Simulatore
Riportiamo solo un piccolo tratto.
package simulatoreAttore;
import applicazione.ClientManager;
import applicazione.EscManager;
import modello.*;
import common.*;
45
public class Simulatore {
/*
* Il Main simula l’Agente e il video
*/
public static void main(String[] args) {
Agenzia a;
ClientManager cM; //Il gestore dei partecipanti
EscManager eM; //il Manager Escursioni
Escursione e; //generica escursione
Partecipante p; //generico partecipante
a = Builder.build(); //inizializza come richiesto
//build() è un metodo statico
cM = ClientManager.getClientManager();
cM.setAgenzia(a);
eM = EscManager.getEscManager();
eM.setAgenzia(a);
System.out.println("Versione con Agenzia, EscManager e ClientManager Singleton \n");
System.out.println("================================================" +
"\nLista escursioni dopo inizializzazione");
eM.stampaListaEscursioni();
cM.stampaListaCompletaClienti();
System.out.println("================================================");
e = eM.getEscursione(new Data(5,7), "Gita in barca");
p = cM.getPartecipante("1" );
if (p==null) System.out.println("Main: Il partecipante con CF = 1 non esiste nel sistema" +
"\n=====================================");
Il lettore è invitato a esaminare il testo del Simulatore, del ClientManager e di Escmanager allegato
cercando di capire quali azioni svolgono.
Peraltro scoprirà che Builder ha un solo metodo statico che restituisce l’agenzia popolata con qualche
escursione, partecipante, ecc.. Non c’è bisogno di istanziare Builder, in quanto esso è una pura funzione. Per
questo basta un metodo statico. Nel caso specifico anche ClientManager e Escmanager sono pure funzioni e
quindi i loro metodi avrebbero potuto essere dichiarati statici, senza bisogno di istanziare mai le due classi.
Se si guarda il relativo codice si scopre che queste due classi non hanno attributi interni che possono variare;
dunque gli eventuali oggetti da esse istanziati non avrebbero un proprio stato interno. Poiché le due classi ci
servono solo per implementare le funzionalità ad esse associate, si poteva fare a meno di istanziarle.
Riferimenti bibliografici
[1] IEEE. Ieee recommended practice for software requirements specifications. Technical report, 1998.
[2] D. Rosenberg and M. Stephens. Use Case Driven Object Modeling with UML - Theory and Practice. Apress,
2007.
46