2
generale, [5] e [6], mentre alla fine del capitolo si focalizza l'attenzione sui concetti che
interessano direttamente il meccanismo il progetto TIRAN[8].
Nel capitolo quattro viene descritto nei primi quattro paragrafi come il progetto TIRAN si
colloca nel panorama dei sistemi fault-tolerant [5], [6], [7]. Successivamente viene
descritta l'applicazione nel caso particolare dell'ENEL, [8].
Nel capitolo cinque in ultimo vengono analizzate le specifiche della DS indicate nei
documenti ufficiali di TIRAN, in particolare in [8], [9] e [10], rispetto le quali sono poi
fatte alcune ipotesi semplificative.
Allo scopo di fornire un supporto decisionale alla progettazione sono sviluppati quattro
modelli del meccanismo di sincronizzazione che sono descritti prima attraverso l'UML e
poi sono formalizzati con le SWN.
Per ogni modello sono calcolati degli indici di prestazione sia in presenza sia in assenza
di errori nel sistema in base ai quali è poi svolta un'analisi qualitativa confrontando le
prestazioni dei quattro modelli.
Concludiamo facendo un'osservazione sul linguaggio utilizzato. Si noterà che in tutta la
tesi sono molte le parole in lingua inglese, questo fatto non è casuale ma dipende da una
precisa scelta. Molti termini non sono stati tradotti per due motivi: il primo è per
permettere una corrispondenza immediata con la bibliografia, il secondo è che non
sempre esiste una precisa traduzione italiana. Ad esempio il termine dependability in
italiano può essere tradotto con "fidatezza" ma il suo significato è piuttosto complesso e
si è quindi preferito spiegarne il significato senza tradurre il termine.
3
Capitolo 1.
Introduzione all'Unified Modeling Language
(UML).
La progettazione di sistemi software si è sempre avvalsa di diverse tecniche di
rappresentazione che utilizzano diversi linguaggi a seconda di quali aspetti si vogliano
descrivere. L'Unified Modeling Language nasce allo scopo di fornire un unico linguaggio
che permetta di esprimere qualunque aspetto del progetto.
In questo capitolo vengono descritti gli elementi principali dell'UML.
La bibliografia di riferimento è l'[1], un breve saggio di introduzione agli aspetti
fondamentali dell'UML, ed il [2] che è invece la guida completa aggiornata al giugno
1999.
1.1 Cos'è l' UML.
L'ULM fa parte di un metodo per l'analisi a la progettazione Object-Oriented nato alla
fine degli anni '80. Un metodo é formato da due parti: un linguaggio per la modellazione
(modeling language) e un processo (process).
Modeling Language.
L'ULM é appunto un linguaggio (prevalentemente grafico) per esprimere progetti mentre
i processi sono delle regole (variabili a seconda del progetto) da seguire per la
realizzazione dei progetti stessi.
Allo stato attuale l'UML é formato da una notazione e da un meta-modello.
Notation e Meta-model.
La notazione é la struttura grafica, la sintassi del modello. Ad esempio la notazione che
riguarda il diagramma di classe definisce come siano rappresentati i concetti, quali le
associazioni, le classi e la molteplicità. I concetti vanno quindi opportunamente definiti.
In generale una definizione rigorosa va a discapito dell'utilità del metodo. Nel caso di
metodi OO un modo per avere maggior rigore senza perdere in utilità é definire un meta-
modello.
Il meta-modello é un diagramma, generalmente un diagramma di classi che definisce la
notazione.
Diamo ora una descrizione del processo generale per la realizzazione di un certo progetto
in UML. Teniamo però conto che l'ULM può essere associato ad un qualunque processo
e che la scelta del tipo di processo dipende dal progetto.
Possiamo individuare quattro fasi principali nel processo:
Inception->Elaboration->Costruction->Transition.
(Inizio) (Elaborazione) (Costruzione) (Transizione)
La prima cosa da osservare é che il processo é iterativo; il software non é rilasciato in un
sol colpo passando dalla fase di elaborazione a quella di transizione. La fase di
costruzione consiste in diverse iterazioni in ognuna delle quali si realizza solo una parte
del progetto, si testa, eventualmente s’integra e in fine la si passa ad un'altra iterazione.
Inception.
4
Si realizza ideando un progetto, ad esempio la commessa di un cliente per realizzare un
catalogo su web.
Elaboration.
Conoscendo la richiesta del committente in questa fase bisogna analizzare il problema
rispondendo a tre domande.
1) Che cosa ci é stato chiesto di realizzare?
2) Come abbiamo intenzione di costruirlo?
3) Che tecnologia abbiamo intenzione di usare?
Nel rispondere a queste domande bisogna prestare attenzione ad alcuni rischi quali ad
esempio dare delle risposte sbagliate. Può capitare infatti di realizzare un sistema che non
soddisfi le specifiche richieste dal committente (Requirement risk). Oppure si potrebbe
scegliere una tecnologia per la realizzazione in cui non si ha abbastanza esperienza
(Tecnological risk).
Evidentemente le specifiche del progetto sono il punto di partenza dell'elaboration.
L'ULM a questo riguardo é uno strumento adatto a descriverle attraverso i casi d'uso (use
cases). I casi d'uso sono le interazioni tra il sistema e un utilizzatore (ad esempio la
richiesta ad un programma gestore testi di stampare un testo in grassetto).
Un altro elemento importante dell'UML é il domain model. Con dominio del modello
intendiamo un qualunque modello il cui soggetto primario è il "mondo" che il sistema sta
supportando a qualunque livello sia lo stato del processo di sviluppo. Mettendo insieme i
due tipi di informazione, use case e domain model, si può costruire un modello del
progetto facilmente generalizzabile e quindi riutilizzabile per altri progetti. Per la
costruzione del modello del dominio sono particolarmente utili alcun tipi di diagrammi i
quali mettono in luce aspetti diversi: class diagrams, activity diagrams, interaction
diagrams.
La costruzione del domain model va pensata come uno scheletro iniziale arricchito
successivamente a mano a mano che si ha una maggiore comprensione del problema e
non come la costruzione di un modello di alto livello perché in questo modo sì perdono
troppi particolari.
Construction.
Durante questa fase si costruisce il sistema con una serie di iterazioni ognuna delle quali
é un mini progetto. Ogni passaggio é iterativo nel senso che richiede una riscrittura
parziale del codice per renderlo più flessibile ed efficiente.
Anche in questa fase é utile l'ULM attraverso i class e activity diagrams.
Transition.
In questa fase si fa dell'ottimizzazione che riduce la chiarezza e la lunghezza del codice
per incrementarne le performance. Inoltre sempre in questa fase si fissano i bugs.
1.2 Use Case.
Use case: tipica interazione tra un utilizzatore ed un computer (per esempio la richiesta ad
un gestore testi di scrivere in grassetto).
Proprietà degli use case sono: 1) un use case "cattura" una qualche funzione visibile
all'utilizzatore;
2) un use case può essere grande o piccolo;
3) un use case ha una discrete utilità per l'utilizzatore.
In questo modo si vede che per identificare un use case é sufficiente parlare con
l'utilizzatore e discutere con lui delle cose che vuole fare con il sistema. Si prende poi
ogni singola cosa gli si assegna un nome ed una breve descrizione. Durante la fase di
elaborazione non serve altro, i dettagli si aggiungeranno a mano a mano che il progetto
5
avanza. È necessario osservare che non sempre le interazioni con il sistema e l'obiettivo
dell'utilizzatore coincidono. Se ad esempio consideriamo un word processor le
funzionalità di cambiare stile e di muovere uno stile da un documento ad un altro possono
essere interpretate come use cases ma lo scopo dell'utilizzatore potrebbe essere descritto
come: realizzare documenti con lo stesso formato. Da quest’esempio si può anche
osservare che un approccio ai problemi che descriva direttamente le interazioni con il
sistema escludendo o analizzando troppo superficialmente gli scopi dell'utilizzatore può
far sì che si perdano di vista eventuali soluzioni alternative al problema.
1.2.1 Use case diagrams.
Descrivo ora gli elementi che compaiono in questi diagrammi: actors, extends, uses.
Actor. É il ruolo giocato dall'utilizzatore rispetto il sistema. Ogni actor può richiedere più
use cases e viceversa ogni use case può avere più actors. L'identificazione degli actors è
utile per identificare gli use cases specie se il sistema è molto grande e complesso.
Ci sono diversi modi di definire gli actors ognuno dei quali mette in luce problemi diversi
noi ci atterremo alla seguente definizione: gli actor sono tutto ciò che necessita degli use
case. Se ad esempio un sistema bancario ogni notte genera un file che è successivamente
utilizzato da un programma che lo usa per aggiornare i conti correnti allora il programma
è un actor in quanto è colui che necessita della realizzazione di quel file da parte del
sistema.
Un esempio di diversa definizione di actor è la seguente: in caso di interazioni esterne il
sistema esterno è considerato actor solo se è lui ad iniziare il contatto.
Una caratteristica importante da osservare è che gli actors non coincidono con gli
utilizzatori. Se ad esempio consideriamo un sistema che può essere configurato per
utilizzatori diversi ogni utilizzatore può essere identificato da una lista di actors che è
utilizzata per determinare quali use cases quell'utilizzatore può richiedere. Un altro
motivo per cui è utile individuare gli actors è il caso in cui ci sia la necessità di assegnare
delle risorse a domande in competizione. In questo caso infatti è particolarmente utile
sapere chi necessita e di che cosa.
Diciamo in fine che non è detto che l'individuazione di tutti gli actors permetta di
individuare tutti gli use cases.
Uses ed Extends. Nei diagrammi degli use cases oltre ai legami tra actors e use cases vi
sono altri due tipi di legami che rappresentano relazioni tra use cases.
Extends: si ha quando si hanno due use cases simili ma uno fa qualche cosa in più
dell'altro. Ad esempio il caso dell'addebito di un assegno ha modalità diverse a seconda
che con tale addebito il conto vada in rosso oppure no. Vediamo alcune regole di base per
identificare i rapporti extends:
1) identificare prima l'use case "normale" (addebito dell'assegno con sufficiente liquidità
sul conto);
2) per ogni passo svolto nell'use case "normale" bisogna chiedersi: "cosa può andare
storto" e "come si potrebbe risolvere differentemente";
3) disegnare tutte le variazioni come estensioni di un dato use case.
Uses: sono relazioni tra use cases che occorrono quando si ha un comportamento che è
ripetuto identico in diversi use cases e non si vuole farne copia per ognuno. È da
osservare che uses ed extends sono simili perché entrambe si riferiscono a comportamenti
comuni di use cases diversi ma nel caso degli extends gli actors hanno una relazione con
l'use case che viene esteso mentre per gli uses non è detto che ci sia un actor in relazione
all'use case da cui la relazione uses discende. Inoltre un actor che richieda un use case
extends è supposto che richieda anche l'esecuzione di tutti gli use cases da cui l'extends
6
discende mentre l'esecuzione di un use case di una relazione uses richiede solo
l'esecuzione dell'use case da cui discende direttamente.
Scenario. Si riferisce ad un singolo cammino all'interno di un use case individuato
attraverso la particolarizzazione delle condizioni descritte da quell'use case. Ad esempio
consideriamo l'ordinazione della spesa. Possiamo considerare un singolo use case con
diversi scenari:
1) trovo tutto ciò che voglio e ho soldi a sufficienza;
2) non c'è tutto ciò che voglio anche se ho soldi a sufficienza;
3) trovo tutto ciò che voglio ma la carta di credito viene rifiutata;
ecc....
1.3 Class Diagrams.
Un class diagrams descrive il tipo di oggetti del sistema che si sta analizzando e quali
relazioni statiche esistono tra loro.
Abbiamo due tipi di relazioni:
association (un commerciante può affittare un certo numero di videocassette);
subtypes (un'infermiera è un tipo di persona).
I class diagrams inoltre forniscono anche altre informazioni quali gli attributi, le
operazioni e una descrizione del modo in cui gli oggetti sono connessi.
Ci sono tre prospettive per disegnare un class diagram.
• Conceptual (Concettuale): in questa prospettiva si disegna un diagramma che
rappresenta i concetti nel dominio che stiamo studiando. In una prospettiva concettuale,
pur essendo i concetti correlati alle classi che li implementano, il modello dovrebbe
essere disegnato con poco o addirittura senza riguardo per il software che lo implementa.
• Specification (Descrittiva): in questa prospettiva si guarda invece alle interfacce del
software, il modo cioè con cui il software comunica con l’esterno. Osserviamo quindi i
tipi più che le classi
6
.
• Implemantation (Implementativa): è la prospettiva più usata e focalizza l'attenzione
sulle classi.
Sapere la prospettiva in cui ci si vuole porre è importante sia per la progettazione sia per
l'interpretazione dei class diagrams. Descriviamo ora le notazioni.
Association: rappresentano relazioni di tipo concettuale tra classi. Nell'esempio riportato
in figura 1.1 il diagramma indica che un Order deve venire da un singolo Customer e che
un Customer può fare più Order ognuno dei quali ha diverse Order Line, in particolare
una per ogni prodotto richiesto.
Ogni associazione ha due funzioni ognuna delle quali è una direzione d’associazione tra
classi; così l'associazione tra Order e Customer contiene in realtà due associazioni, una da
Order a Customer e l'altra da Customer ad Order. La direzione d’associazione è
particolarmente significativa se messa in relazione al concetto di molteplicità che viene
indicata nei diagrammi con il simbolo * o con dei numeri che sono posti al punto d’arrivo
dell'associazione. Ad esempio l'associazione tra Customer e Order è marcata con il
simbolo " * " per l'associazione Customer->Order; con tale simbolo s’indica che un
Customer può essere associato a più Orders (un cliente può evidentemente fare più
ordini), in particolare con * s’indica la possibilità teorica di fare infiniti Order da parte del
Customer. Sull'associazione Order->Customer invece c'è un uno il quale indica che ogni
6
Nei linguaggi OO la parola classe spesso si riferisce sia all'interfaccia sia all'implementazione.
7
Order può essere associato ad un solo Customer. In generale la molteplicità è indicata con
n..m dove n ed m sono gli estremi inferiore e superiore possibili.
Order
dateReceived
isPrepaid
number: String
price: money
dispatch()
close()
creditRating():String
Customer
name
address
Association
*
1
Costraint
Attributes
Operations
Class
Generalization
Order Line
quantity: Integer
price: Money
isSatisfied: Boolean
line
items
*
*
1
*
Product
{if Order.customer.creditRating is
"poor", then Orders.isPrepaid
must be true}
1
{creditRating()==
"poor"}
sales rep
0..1
Employee
Multiplicity:
optional
Multiplicity:
Many-valued
Multiplicity:
mandatory
Personal
Customer
creditCard#
Corporate
Customer
remind()
billForMonth(integer)
contractName
creditRating
creditLimit
Fig.1.1
Passiamo ora alla prospettiva descrittiva. In questa prospettiva le associazioni
rappresentano "responsabilità"; il fatto che ci sia un'associazione tra Customer e Order
implica che ci sia uno o più metodi in Customer che mi dicano quali Order il Customer
ha fatto ed implica anche che ci siano dei metodi in Order che mi permettano di
identificare quale Customer ha fatto un certo Order. Queste responsabilità non dicono
nulla su come devono essere strutturati i dati.
8
La prospettiva implementativa affronta invece proprio questo aspetto. L'esistenza di
un'associazione tra classi implica, infatti, l'esistenza di puntatori indicanti le classi
associate. Riferendoci al solito esempio il diagramma ci indica che Order deve avere un
vettore di puntatori ad OrderLine e un puntatore a Customer. In questa prospettiva è poi
possibile associare alle linee che rappresentano le associazioni tra classi la "Navigability"
attraverso delle frecce. Ad esempio in figura 1.2
Order
Customer
*
1
Navigability
Fig.1.2
la navigabilità significa che Order ha la responsabilità di indicare il proprio Customer e
quindi dovrà contenere un puntatore a Customer mentre Customer non ha l'obbligo di
contenere alcun puntatore ad Order.
Attributes. Nella prospettiva concettuale l'attributo "name" di Customer indica
semplicemente che i Customer hanno un nome. Dal punto di vista descrittivo invece
significa che Customer è in grado di comunicare il suo nome ed è in grado di settarlo. In
fine in una prospettiva implementativa significa che Customer ha un campo (detto anche
istanza di variabile o dato membro) per il suo nome.
Operations. Corrispondono ai metodi che operano su di una classe. A livello descrittivo
corrispondono alle operazioni pubbliche su un tipo. Implementativamente può essere
utile indicare se le operazioni siano private o protette, in questo caso si fa seguire
l'operazione da uno dei seguenti simboli:
+ per public; # per protected; - per private.
Dal punto di vista concettuale le operazioni non dovrebbero descrivere l'interfaccia di una
classe ma piuttosto le sue responsabilità principali.
Distinguiamo poi le operazioni in due gruppi a seconda che cambino o no lo stato di una
classe.
Definiamo Query Operations un'operazione che prende un valore di una classe senza
cambiare lo stato osservabile della classe; diciamo invece Modifiers un'operazione che
cambi lo stato osservabile della classe. Consideriamo per esempio un oggetto
ContoCorrente che calcoli il bilancio di un conto corrente in funzione delle entrate/uscite
attraverso l'operazione "bilancio". Supponiamo poi che ControCorrente memorizzi i
bilanci in un certo campo. La prima volta che viene chiamato l'operazione "bilancio" il
campo dove memorizzare il bilancio è vuoto e "bilancio" ne modifica lo stato
memorizzandoci appunto i vari bilanci calcolati. Abbiamo così che qualunque richiesta
d’informazioni sui bilanci del ContoCorrente (query) non modifica lo stato di
ContoCorrente mentre il metodo "bilancio" sì e quindi è un modifiers.
Vi sono poi i Setting Methods e i Getting Methods.
I setting methods si limitano ad inserire i valori nei campi mentre i getting methods li
ritornano ed entrambe non fanno altro. È da osservare che sia le queryes sia i modifiers
possono essere setting o getting methods.
9
Un'ultima distinzione da fare è tra operazioni e metodi. Mentre l'operazione è qualcosa
chiamato su un oggetto il metodo è il corpo della procedura. Questi due concetti possono
coincidere ma quando si ha a che fare con polimorfismi no, se, infatti, si ha una classe
con un operazione che chiamo f e due sottoclassi ognuna delle quali sovrascrive
l'operazione f allora abbiamo un'operazione e due metodi.
Generalization. Un tipico esempio di generalizzazione è quello del Customer indicato
figura 1.1.Un cliente, infatti, può essere sia un individuo sia un'azienda. Le similarità
possono essere poste in un sopratipo che abbiamo chiamato Customer, mentre delle
particolarità si tiene conto in due sottoclassi distinte (indicate anche come sottotipi) che
abbiamo chiamato PersonalCustomer e CorporateCustomer.
Anche la generalizzazione è soggetta ad interpretazioni differenti a seconda della
prospettiva.
Concettualmente possiamo dire che CorporateCustomer è un sottotipo di Customer se
tutte le istanze di CorporateCustomer sono anche istanze di Customer. In generale dire
che Corp.Cust. è sottotipo di Customer significa che qualunque cosa si dica per Customer
essa è valida anche per Corp.Cust.
Dal punto di vista descrittivo invece la generalizzazione indica che l'interfaccia del
sottotipo deve includere tutti gli elementi provenienti dall'interfaccia del sopratipo. Un
altro modo di interpretare questo fenomeno in questa prospettiva è quello che si richiama
al concetto di sostituibilità. Secondo questo concetto dovrei essere in grado di sostituire
un sottotipo con qualunque altro sottotipo che richieda lo stesso sopratipo.
Essenzialmente questo si traduce nel fatto che se scrivo del codice per il tipo Customer
allora esso dovrà funzionare ugualmente bene per ogni suo sottotipo.
Implementativamente invece il concetto di generalizzazione si traduce in quello
d’ereditarietà tra classi. Per comprendere la differenza tra il punto di vista descrittivo e
quello implementativo dobbiamo dire che mentre nel primo ci si riferisce a sottotipi ed
ereditarietà tra interfacce nel secondo ci si riferisce a sottoclassi ed ereditarietà delle
implementazioni.
Costraint Rules. Dal grafico considerato emergono immediatamente delle limitazioni o
caratteristiche peculiari del sistema cosi modellato. Una caratteristica è che ogni Order ha
un proprio Customer e quindi se due clienti vogliono ordinare 50 cappelli devono fare
due ordini separati, un'altra caratteristica è che le Line Item sono considerate una per ogni
oggetto ordinato e ciò fa sì che non si possa pensare ad un ordine di 10 cappelli bianchi,
neri, rossi ma ad un ordine di 10 cappelli bianchi, 10 cappelli neri, 10 cappelli rossi.
Oppure ancora dal grafico si vede che mentre il Corp.Cust. gode di credito il Pers.Cust.
no. Nell'UML non esiste una regola fissa per indicare questo tipo di specifiche, una
possibilità e quella di indicarle tra parentesi graffe.
Stereotypes. Il significato di stereotipo è una sorta di classificazione ad alto livello di un
oggetto che dà qualche indicazione di quali responsabilità abbia l’oggetto. Un esempio
chiarificatore è la differenza tra controller e coordinator. Spesso progettando con
linguaggi OO si può avere a che fare con una classe che sembra fare tutto. In tal caso
significa che si è fatta una progettazione di cattiva qualità perché significa avere un
controller molto complesso e quindi difficile da gestire. Dando più responsabilità agli
altri oggetti del sistema si ottiene l’effetto di semplificare il controller il quale diventa un
coordinator in quanto potrà limitare la sua attività al controllo della sequenza dei task
mentre la responsabilità di come questi siano inviati è demandata ad altri oggetti. Uno
stereotipo in genere è posto tra “ ”.
Aggregation e Composition. Il problema di definire le aggregazioni sta nel chiarire la
differenza tra aggregazione e associazione. Purtroppo non c’è ancora un’unica
definizione accettata da tutte le metodologie. In generale possiamo dire che
10
un’aggregazione è parte di una relazione; ad esempio è come dire che un’auto ha un
motore e le ruote come sue parti.
Nell’insieme delle aggregazioni l’U.M.L. prevede un tipo di aggregazione detta
composizione. In una composizione un oggetto parte può appartenere ad un solo tutto,
inoltre ci si aspetta che le parti vivano o muoiano con il tutto. Qualunque cancellazione
del tutto deve ricadere anche sulle parti.
Vediamo l’esempio di figura 1.3:
Fig1.3
Un’istanza di Punto può essere o un poligono o un cerchio ma non entrambe. Un’istanza
di stile può invece essere condivisa da più Poligoni o Cerchi. Dal grafico si ricava anche
che la cancellazione di Poligono o Cerchio cancella anche i Punti associati ma non Stile.
Derived association e derived attributes. Associazioni ed attributi derivati sono quelli
che possono essere ottenuti rispettivamente da altre associazioni ed altri attributi in un
class diagram.
Ad esempio l’attributo età di un oggetto Persona può essere ottenuto conoscendo la data
di nascita.
Anche per associazioni ed attributi è importante la prospettiva.
Nel caso della prospettiva descrittiva le caratterizzazioni derivate indicano un forte
legame tra i valori e non una semplice descrizione di cosa è stato calcolato e cosa sia
invece memorizzato come dato iniziale. Consideriamo la seguente classe:
Questo diagramma descrittivamente suggerisce che start ed end siano dati e duration sia
calcolato. In realtà un programmatore può implementare questa classe in qualunque modo
purché mantenga il funzionamento esterno della classe. Potrebbe infatti calcolare end
conoscendo start e duration. Dal punto di vista concettuale l’indicatore di derivato è utile
per ricordare dove queste derivazioni esistono.
1.4 Package Diagrams.
Poligono
Stile
colore
è pieno.
Cerchio
raggio
Punto
1
1
1
* *
3..*
Composizione
Aggregazione
Time Period
start:Date
end:Date
/duration:Date
11
Con Package ci si riferisce ad un meccanismo che permette di raggruppare le classi in
unità di più alto livello dette appunto Package. L’idea di package può essere applicata a
qualunque elemento del modello e non solo sulle classi.
Dependency. Esiste una dipendenza tra due elementi se cambiamenti alla definizione di
uno può causare cambiamenti nell’altro. Tra classi la dipendenza può esistere per vari
motivi, si pensi per esempio ad una classe che abbia un’altra classe come parte dei suoi
dati o ad una classe che ne nomini un’altra come parametro di un’operazione.
Diciamo poi che esiste una dipendenza tra packages se esiste una dipendenza tra due
qualunque classi appartenenti a package distinti. E da osservare che dipendenze e
package sono solo casi particolari di class diagram. La loro utilità sta nel fatto che
permettono di spezzare sistemi di grandi dimensioni in sistemi più piccoli e quindi più
gestibili.
A
B
C
Dominio
D
E
F
G
H
L
(globali)
Comuni
Fig.1.4
Una prima caratteristica dei package è che le dipendenze non sono transitive come ad
esempio la relazione di amicizia tra persone: se A è amico di B e B è amico di C non è
detto che A sia amico di C. Questa proprietà si traduce ad esempio nel caso dei package
di figura 1.4 nel fatto che un cambiamento in una classe del package L non influenza le
classi del package E ma solo quelle del package C. Il difetto di avere una dipendenza per
12
cui vale la proprietà transitiva è che diventa difficile limitare lo scope (il dominio) dei
cambiamenti nella compilazione del codice.
Vediamo ora cosa significa disegnare una dipendenza ad un package che contiene altri
packages: ci sono due convenzioni, la prima afferma che la dipendenza tra il package A
ed il package B con B contenente dei subpackages dà la visibilità di tutti i subpackages e
dei loro contenuti. La seconda convenzione invece dice che sono visibili solo gli elementi
presenti nel package contenente e non anche quelli nel package contenuto. Noi
considereremo la prima convenzione che chiameremo convenzione di trasparenza.
L’utilità dei package non è nel dare direttamente un modo per ridurre le dipendenze nel
modello che si sta progettando ma nel mostrare quante e quali siano che è il punto di
partenza per ridurne il numero.
Torniamo ora all’esempio riportato in figura 1.4. In questo esempio c’è un package che
ho chiamato Dominio che contiene a sua volta altri due packages D ed E. L’utilità di
questo raggruppamento sta evidentemente nel fatto che posso disegnare dipendenze da e
verso Dominio senza dover disegnare ogni singola dipendenza di D ed E. Un’altra
caratteristica di questo esempio è di avere un package segnato come “(globale)”, significa
che tutti i package del sistema hanno una dipendenza da Comune.
Come suggerisce il grafico, le generalizzazioni sono possibili anche con i package e
vanno interpretate in termini di interfaccia; nell’esempio dato il package F è
particolarizzato in G ed H e questo vuole dire che F può usare o l’interfaccia G o
l’interfaccia H.
E’ poi possibile marcare con “{ astratto} ” i package indicando così che quel package
definisce un’interfaccia che è implementata da packages più specifici.
Osserviamo in fine che le generalizzazioni implicano l’esistenza di una dipendenza del
sopratipo dal sottotipo e viceversa.
1.5 State Diagrams.
Gli state diagrams permettono di descrivere tutti i possibili stati che un particolare
oggetto può assumere e come l’oggetto cambia di stato in base al realizzarsi di particolari
eventi.
In figura 1.5 è rappresentato lo state diagram di un ordine di fornitura.
Si inizia dallo start dal quale si ha una transizione nello stato di Checking, questa
transizione è etichettata con la scritta “/get first item”. In generale la le etichette delle
transizioni prevedono tre parti ognuna delle quali è opzionale: Event [Guard] / Action,
nel nostro caso abbiamo solo l’action “get first item”. Una volta eseguita l’azione si entra
nello stato Checking, a tale stato è associata un’attività che è indicata con la seguente
sintassi: do / activity ed in questo caso è chiamata “check item”. Sebbene sia l’activity
che l’action siano dei processi la loro differenza non si limita al fatto che l’una si riferisce
ad uno stato mentre l’altra ad una transizione.
L’action, oltre ad essere associata alle transizioni è considerato un processo molto veloce
e non interrompibile.
E’ da notare che la definizione di “veloce” dipende dal sistema che stiamo descrivendo,
Nel caso di sistemi real-time “veloce” potrebbe essere l’esecuzione di poche istruzioni di
macchina, mentre per sistemi informativi potrebbe essere alcuni secondi.
L’activity invece, oltre ad essere associata agli stati, può richiedere molto tempo per
essere eseguita e può essere interrotta da un evento.
13
Checking
do/check
item
Dispatching
do/initiate
delivery
Waiting Delivered
delivered
/get first item
[All item checked && all
item available]
[Not all items checked
/get next item ]
[All items checked &&
some items not in stock]
Item recived
[some items not in
stock]
Item recieved
[all items available]
state
transition
activity
start
self
transition
Fig1.5
Se una transizione non è etichettata con alcun evento significa che tale transizione
avverrà non appena sarà completata l’attività associata al rispettivo stato. Nel nostro caso
non appena verrà completata l’attività associata allo stato di Checking si passerà o nello
stato di Dispatching o nello stato diWaiting. La scelta tra le tre transizioni verrà effettuata
attraverso le guard indicate tra parentesi quadre.
Una guard è una condizione logica che ritorna un valore di “vero” o “falso”, la rispettiva
transizione occorre solo se la guard ritorna il valore “vero”. Un’altra caratteristica delle
guard è che sono mutuamente esclusive per un dato evento, questo vuole dire che
supponendo di essere in uno stato dal quale si può uscire attraverso più transizioni, solo
una può essere attivata dal realizzarsi di un evento.
Un evento, in generale, è il realizzarsi di condizioni che possono cambiare lo stato
dell’oggetto, possono cioè fare scattare una transizione di stato. Gli eventi possono essere
di diversi tipi, non necessariamente esclusivi tra loro:
• una condizione prestabilita (descritta da un’espressione Booleana) diventa vera,
producendo così un’istanza di cambiamento di stato. L’evento occorre ogniqualvolta il
valore dell’espressione Booleana cambia da falso a vero. E’ da notare che il
comportamento descritto è diverso da quello delle guard. Una guard è valutata solo una
volta per ogni volta che si realizza il corrispondente evento, se è falsa la transizione non
scatta e l’evento è perso;
• la ricezione da parte di un oggetto di un esplicito segnale da un altro oggetto definisce
una istanza detta signal event ed è indicata etichettando l’arco di transizione con il nome
dell’evento;
• la ricezione di una chiamata per un’operazione, implementata come transizione, da
parte di un oggetto rappresenta un’istanza detta call event. E’ da osservare che non c’è
nessuna differenza di notazione tra i signal event e i call event, la differenza è espressa in
modo esplicito solo nella definizione della rispettiva classe;
14
• il passaggio di un determinato intervallo di tempo dopo un evento designato, o
l’occorrenza di un determinato istante è detto time event.
Nel nostro esempio si può uscire dallo stato di Checking solo se è stato verificato che tutti
gli oggetti richiesti sono disponibili ([All items checked && all items available]), in
questo caso si passerà nello stato Dispatching. Se invece si sono controllati tutti gli
oggetti ed è stato verificato che alcuni non sono disponibili allora si passerà nello stato
Waiting. In fine se non tutti gli oggetti sono stati controllati si passa all’oggetto
successivo attraverso l’action /get next item e si rientra nello stato di Checking.
Lo stato Waiting è uno stato in cui si attende che tutti gli oggetti mancanti arrivino, tale
stato non ha un’activity, questo significa che l’ordine semplicemente attende che si
realizzi l’evento che un oggetto richiesto sia disponibile. Ogni volta che si rende
disponibile un oggetto vengono valutate le due guards che permettono l’uscita da tale
stato, se tutti gli oggetti richiesti sono disponibili allora si passa nello stato Dispatching,
se invece ne manca ancora qualcuno si ritorna in Waiting.
Una volta entrati nello stato di Dispatching inizia l’attività do/initiate delivery. Per uscire
da questo stato c’è una sola transizione etichettata dall’evento delivered (che si suppone
essere definito altrove), questo significa che la transizione dell’ordine dallo stato di
Dispatching allo stato di Delivered potrà occorrere solo se si realizza l’evento delivered e
non per il completamento dell’attività initiate delivery.
Supponiamo ora di voler descrivere la possibilità che un ordine sia cancellato trovandosi
in uno stato qualsiasi, potrebbe accadere ad esempio che venga ordinato un oggetto che
non può rendersi disponibile e quindi l’ordine permarrebbe nello stato Waiting
all’infinito, o più semplicemente chi ha fatto l’ordine non può pagare e quindi non si
vuole consegnare la merce, ecc…
Per risolvere il problema o si aggiunge un nuovo stato che chiamiamo Cancelled
raggiungibile da tutti gli altri attraverso transizioni etichettate dall’evento cancelled,
oppure si può costruire un Superstate come indicato in figura 1.6. Gli stati che sono
inclusi in un superstate ereditano qualunque transizione del superstate.
Vi sono poi due eventi che non compaiono nel nostro esempio ma sono di una certa
importanza e precisamente entry ed exit. Qualunque azione che sia etichettata con entry
viene eseguita qualunque sia lo stato in cui si entra attraverso la corrispondente
transizione. L’azione associata all’evento exit viene eseguita qualunque sia la transizione
che permette di cambiare stato.
15
Checking
do/check
item
Dispatching
do/initiate
delivery
Waiting
Delivered
delivered
/get first item
[All item checked && all
item available]
[Not all items checked
/get next item ]
[All items checked &&
some items not in stock]
Item recived
[some items not in
stock]
Item recieved
[all items available]
state
transition
activity
start
self
transition
Fig1.6
Active
Superstate name
Cancelled
cancelled
1.6 Concurrent State Diagrams.
Nell’esempio di figura 1.6 abbiamo analizzato gli stati che un ordine può raggiungere in
base alla disponibilità degli oggetti richiesti, ma vi sono altri punti di vista
complementari. Consideriamo ora lo stesso problema dal punto di vista però
dell’autorizzazione al pagamento, vogliamo descrivere il fatto che se un ordine non
riceve l’autorizzazione al pagamento deve essere cancellato. Lo state diagram
corrispondente all’eventuale autorizzazione, descritto in figura 1.7, è di facile
interpretazione, si inizia con un’attività di controllo nello stato Authorizing, terminata
tale attività se il controllo ha dato esito positivo ([payment OK]) si passa nello stato di
Authorized e quindi in quello di Delivered non appena si realizza l’evento deliver, se
invece c’è stato un esito negativo del controllo ([payment not OK]) si entra nello stato di
Rejected.
L’oggetto Ordine che stiamo descrivendo presenta due comportamenti di cui tenere
conto.