III. Progetto di un tool per la modellazione UML
In questo capitolo si espone il lavoro fatto per progettare uno strumento per la creazione di modelli espressi nella notazione specificata dallo UML.
Intento
Si è voluto progettare un’architettura di un tool per la modellazione di sistemi software basato sulla notazione dello UML, versione 1.4, e portare a termine la progettazione e la realizzazione del suo modulo principale: in esso si fornisce all’utente uno strumento per la creazione di diagrammi di classe il cui modello è memorizzato e costruito attraverso la libreria CppUML discussa nel precedente capitolo. L’architettura deve essere tale che possa essere sviluppata per incrementi e che abbia come obiettivo finale di divenire un “CASE tool”.
I CASE tool sono quelle applicazioni che hanno lo scopo di assistere il processo di produzione del software, fornendo allo sviluppatore una serie di strumenti, in grado di facilitare lo svolgimento di alcuni lavori. La parola “CASE”
è l'acronimo di “Computer Aided (o Assisted) Software Engineering”. Alcuni
CASE tool supportano tutte le fasi di un processo produttivo, tuttavia la maggior
parte di essi fornisce sostegno solo per alcune fasi, in particolare quelle di analisi,
progettazione ed implementazione. Nel corso degli anni sono state definite alcune
caratteristiche che un CASE tool ideale dovrebbe fornire. Ne riportiamo una breve
descrizione di seguito.
Caratteristiche architetturali
• Utilizzo di interfacce standard: poiché la maggior parte dei CASE tool non supporta tutte le fasi del processo produttivo e anche perché i progettisti che lavorino su diversi tool hanno bisogno di scambiarsi dati, sarebbe auspicabile che applicazioni diverse possano condividere informazioni mediante un formato di scambio comune.
• Espandibilità: un CASE tool dovrebbe basarsi su un'architettura aperta che consenta di aggiungere nuove funzionalità senza dover ricorrere ad una reimplementazione.
• Portabilità: se un CASE tool è in grado di funzionare su piattaforme differenti, è possibile cambiare ambiente di sviluppo ed utilizzare ancora quel tool, evitando di dover imparare ad utilizzare altri strumenti.
Caratteristiche funzionali
• Utilizzo di un repository: uno degli strumenti più importanti di un CASE tool è il repository (o dizionario dei dati). Esso contiene tutti i dati relativi agli elementi che appartengono al sistema che si sta progettando.
• Consistency checking: se esiste un dizionario dei dati, è possibile verificarne la consistenza attraverso l'analisi semantica dei dati contenuti in esso. L'analisi verifica che tutte le dipendenze, le relazioni, i vincoli e le condizioni imposte nel sistema siano rispettati.
• Report generation e screen generation: sono strumenti detti di “rapid
prototyping”, che permettono di generare il codice in maniera automatica per
realizzare alcune porzioni del sistema, come la creazione di report (report generation) o le interfacce per l'inserimento di dati (screen generation).
• Supporto per notazioni grafiche: un CASE tool dovrebbe fornire il supporto per le notazioni più comuni, in particolare UML.
• Supporto per il riuso: un CASE tool dovrebbe mantenere delle librerie di oggetti e componenti che possano essere facilmente integrati col sistema che si sta progettando.
• Controllo delle versioni: durante il ciclo di vita di un prodotto vengono prodotte diverse versioni del sistema. Risulta quindi indispensabile uno strumento per la gestione delle versioni, delle revisioni e delle variazioni.
• Generazione di documentazione: è di fondamentale importanza fornire la possibilità di generare la documentazione del progetto elaborato con un CASE tool. Meglio se lo stesso tool assiste il progettista a creare, organizzare e rintracciare la documentazione.
A questo punto si può introdurre il progetto ad alto livello di uno strumento
visuale per la creazione di diagrammi di classe che crei, per ogni diagramma
disegnato, il corrispondente modello attraverso le istanze delle classi della libreria
CppUML. La progettazione di tale modulo è stata fatta in modo da poterlo
espandere fino ad ottenere uno strumento CASE completo.
3.1 Progetto di un modellatore UML
Nei successivi paragrafi vi è il progetto di uno strumento visuale per la generazione di modelli UML e la realizzazione del modulo che corrisponde ad uno strumento per la produzione di diagrammi di classe e del relativo modello UML attraverso le istanze della classi della libreria CppUML.
Vediamo come si può suddividere la struttura logica di tale strumento attraverso il seguente diagramma:
Figura 3.1 – Struttura logica del tool di modellazione UML.
Si illustrano di seguito i package.
CppUML: è la libreria che ha il compito di fornire gli strumenti per creare il modello utente.
Diagrammi UML: contiene l’interfaccia grafica per l’utente (la finestra principale, le barre dei menù e dei bottoni e l’area di disegno dei diagrammi).
Inoltre astrae il concetto di diagramma UML e lo racchiude in una classe, le cui istanze si occupano di interagire con l’utente per creare un diagramma.
Document: memorizza e ordina tutti i documenti prodotti.
Libreria Qt 2.3.1: è una libreria grafica che contiene la definizione di tutti gli oggetti per creare l’interfaccia con l’utente.
Modellatore UML: contiene lo scheletro di tutta l’applicazione e definisce, oltre alla struttura grafica della finestra principale, anche tutta una serie di funzioni per gestire le varie richieste dell’utente.
Modello utente: contiene le strutture dati che permettono all’utente di creare ed editare un modello UML, indipendentemente da come viene rappresentato graficamente. Per fare questo si utilizzano le librerie CppUML. L’utente non interagisce direttamente con questo package, ma indirettamente: mentre disegna i diagrammi attraverso l’interfaccia grafica, vengono creati gli oggetti che fanno parte del modello utente.
OCppUML: comprende tutte le classi che hanno il compito di visualizzare
all’utente gli oggetti della classe CppUML. Sono classi che ereditano le proprietà
grafiche dalla libreria Qt e la capacità di osservare le classi della libreria CppUML
attraverso l’interfaccia ereditata dalle classi che implementano il pattern Observer, racchiuse nell’omonimo package.
Pattern Observer: definisce le classi che implementano l’interfaccia dell’omonimo pattern che ha lo scopo di disaccoppiare i dati di un’applicazione dalle entità che hanno il compito di presentarli all’utente.
Salvataggio e ripristino modello: gestisce le operazioni di salvataggio e di ripristino del modello costruito dall’utente attraverso un formato proprio dell’applicazione.
Stampa diagrammi: contiene il codice che gestisce la stampa dei vari diagrammi.
Utilità: contiene la definizione di tutta una serie di funzioni globali utili in fase di disegno dei vari diagrammi.
Scelta delle librerie Qt
Per quanto riguarda i particolari delle librerie grafiche Qt si rimanda alla
documentazione ufficiale fornita dal produttore [Tro]. Si citano solo brevemente
gli aspetti principali: Qt è una libreria in C++ multipiattaforma, infatti le librerie
Qt sono presenti per MS/Windows (95, 98, Me, NT, 2000) e Unix/X11 (Linux, Sun
Solaris, HP-UX, Digital Unix, IBM AIX, SGI IRIX), che fornisce agli
sviluppatori di software tutte le funzionalità per costruire una interfaccia grafica
per l’utente completa e coerente con le tipologie delle GUI più frequentemente
usate. Qt è orientata agli oggetti, facilmente personalizzabile e permette di
programmare ogni singolo componente. Vi è la presenza di un potente
meccanismo di comunicazione fra gli oggetti della libreria, o fra quelli definiti dall’utente che ne ereditano l’interfaccia, che viene realizzata tramite due nuove parole chiave: signal e slot. Queste se usate all’interno della dichiarazione delle classi definite dall’utente indicano una parte di codice dove si dichiarano i segnali e le relative istruzioni che hanno il compito di permettere la comunicazione tra gli oggetti; uno slot infatti non è altro che una funzione membro di una classe, che appartiene alla gerarchia di generalizzazione delle classi della libreria Qt, preceduta da tale parola chiave. Un oggetto grafico A, a seguito di un evento generato dall’utente (ad es. click del mouse), lancia un signal (che sarà opportunamente associato all’evento click del mouse). A questo punto andranno in esecuzione tutti gli slot (dell’oggetto stesso o di altri oggetti) che sono associati a quel signal tramite un’istruzione connect; una tale istruzione permette a tempo di esecuzione di associare un signal, emesso da un oggetto A appartenente ad una classe X ove tale signal è dichiarato, ad uno slot di un oggetto B appartenente ad una classe Y ove tale slot è dichiarato; il formato di questa istruzione è:
connect(&A, SIGNAL(signal_of_X()), &B, SLOT(slot_of_Y()));
L’estensione del C++ con queste nuove parole chiave è possibile grazie a un precompilatore fornito dalle librerie Qt che trasforma i nuovi meccanismi in funzioni standard C++.
Sempre a tempo di esecuzione si può disaccoppiare il signal di un oggetto A
dallo slot di un oggetto B con un’istruzione disconnect di simile formato.
Le classi appartenenti a questa libreria sono tutte identificate dal prefisso Q (ad es. la classe che implementa un rettangolo si chiama QRect).
In questa libreria sono predefinite le principali interfacce utente delle applicazioni a finestre e sono personalizzabili sia nello stile di visualizzazione (MS-Windows, OSF Motif, …) sia nella loro struttura: infatti si possono dotare di una serie di oggetti, predefiniti sempre dalla stessa libreria, tipici delle applicazioni WYSIWYG (bottoni, combo box, tool bar, finestre di testo,…).
Sono presenti delle classi QAction e QActionGroup che semplificano la creazione dei controlli delle finestre. Una classe QAction astrae un’azione dell’interfaccia grafica che può apparire sia nei menu che nei tool bar. Una classe QActionGroup raggruppa delle azioni: si semplifica così l’annessione e la rimozione di gruppi di azioni da istanze di menù con una singola istruzione.
Vi sono molte altre caratteristiche che mostrano la completezza di questa libreria, ma non sono oggetto di questa discussione. Quando sarà opportuno si spiegheranno le caratteristiche delle classi della libreria Qt usate.
3.2 Progetto in dettaglio
Si è vista la convenienza di disaccoppiare l’interfaccia grafica dal modulo che
contiene e gestisce i dati usando il pattern MVC ed Observer. Quindi si può
adottare lo stesso pattern anche per il tool di modellazione UML. Nel diagramma
dei package (Figura 3.1) vi è infatti il package Modello utente, che si occupa di
costruire il modello UML dell’utente, e il package Diagrammi UML, che
rappresenta graficamente lo stesso modello utente: la presenza di questa suddivisione dei compiti nasce proprio dall’intenzione di applicare il pattern Observer al tool di modellazione UML. Vediamo ora il diagramma delle classi che specifica la struttura di base del package Diagrammi UML.
Figura 3.2 – Diagramma delle classi principali del modellatore UML.
Si illustrano di seguito le singole classi.
• QmainWindow: appartiene alla libreria Qt e fornisce una tipica finestra di
applicazione con una barra di menù, qualche barra di tool (bottoni, combo box, … , che eseguono controlli) ed una barra di stato.
• Case: fornisce la finestra principale con cui l’utente interagisce. In questa
classe vengono dichiarate tutte le azioni (QAction) delle barre di menù e di
tool che essa stessa fornisce. Contiene tutti i controlli che l’utente usa per
gestire i diagrammi (crea, salva, chiudi, rinomina … diagramma), tutti quelli che operano sui diagrammi stessi (crea una classe, un’associazione, …) e quelli per il modello utente (crea, salva, … , modello). Per potere gestire applicazioni MDI (multi-document interfaces) la classe Case contiene un puntatore ad un oggetto QWorkspace che pone come oggetto grafico principale; in questo modo vi costruisce intorno tutte le barre strumenti, la barra menù, ecc.. Un oggetto QWorkspace può contenere più finestre di documento, che saranno rappresentate dai vari diagrammi. La classe Case ha inoltre il compito di mantenere, con delle liste apposite, i puntatori agli oggetti del modello UML che sono creati dall’utente attraverso i diagrammi; mantiene tutte le informazioni della struttura del modello attraverso istanze della classe Package e le istanze di tutte le classi del package Model Management, come definito nella specifica dello UML.
• DiaDoc: è la classe che ha il compito di creare un documento (nel nostro caso
un diagramma UML) che contiene la rappresentazione di una parte del
modello utente. Contiene un riferimento ad una classe QCanvas che ha il
compito di costruire un’area grafica astratta: il costruttore di DiaDoc crea un
oggetto QCanvas che contiene tutti gli oggetti che rappresentano gli oggetti
della classe CppUML e che vanno così a costituire il diagramma; questo fatto
è chiarito in seguito. Vi è inoltre un puntatore all’oggetto attivo al momento,
ovvero l’ultimo oggetto con cui l’utente ha interagito.
• DiaView: è la classe che ha il compito di osservare un oggetto tipo DiaDoc
tramite il protocollo definito dal pattern Observer e presentarne una vista all’utente; infatti DiaDoc è la classe soggetto della classe DiaView osservatrice. Per rappresentare all’utente i cambiamenti che si avverano nell’oggetto DiaDoc che sta osservando ha un puntatore View ad un’istanza della classe MyView che rappresenta visualmente il diagramma.
• MyView: contiene un riferimento all’oggetto QCanvas creato dal costruttore
di DiaDoc. La classe QCanvas definisce un’area astratta dove vengono creati e distrutti oggetti tipo QCanvasItem o sottoclassi di questa. La classe QCavasItem definisce un oggetto grafico astratto su di un QCanvas, un oggetto QCanvasItem può essere mosso nelle direzioni della larghezza e dell’altezza dell’area e può cambiare profondità, permettendo in questo modo che un oggetto nasconda l’altro se sovrapposto; per un oggetto QCanvasItem vi sono diversi metodi per gestire la sua dimensione, la sua staticità o dinamicità, il rilevamento della sua collisione o sovrapposizione con altri oggetti o con il puntatore del mouse in corrispondenza di determinati eventi (click, doppio click, movimento, ecc.), la sua identificazione, ovvero a quale sottotipo appartiene, attraverso un metodo rtti() e infine un metodo che restituisce un puntatore all’oggetto QCanvas che lo contiene. Un oggetto QCanvas che contiene molti oggetti QCanvasItem ha i seguenti vantaggi rispetto ai tradizionali oggetti grafici forniti dalla libreria:
o gli oggetti rettangolari sono disegnati più velocemente;
o gli oggetti occupano meno memoria;
o si possono fare test molto efficienti per la collisione fra oggetti;
o la ricerca di un oggetto in un’area è efficiente (si possono creare con singoli comandi le liste di riferimenti a quegli oggetti che collidono con una certa coordinata dell’area QCanvas o che collidono con un oggetto, ecc.).
La classe MyView è un sottotipo della classe QCanvasView: pertanto essa stessa è un oggetto grafico che rappresenta all’utente un oggetto QCanvas e permette allo stesso utente di interagire con gli oggetti ivi contenuti attraverso i metodi che gestiscono gli eventi di questa classe (click, doppio click, rilascio tasto sinistro, movimento del mouse, …), opportunamente ridefiniti. Nel suo insieme un oggetto MyView usa l’oggetto QCanvas, corrispondente al diagramma contenuto nel DiaDoc, e lo rappresenta all’utente; in questo modo la classe MyView ha la funzione di rappresentare un diagramma (infatti è l’istanza della classe MyView ad essere contenuta nell’oggetto QWorkspace, che permette l’uso contemporaneo di più documenti grafici) ed inoltre ridefinisce tutti i metodi per il controllo degli eventi mouse per gestire opportunamente gli oggetti ivi creati (questi fanno parte della categoria di oggetti definiti nel package OCppUML e sono sottotipi di una classe della gerarchia con a capo la classe QCavasItem;
questi hanno il compito di rappresentare un oggetto della libreria CppUML). Nella
classe MyView vi è un insieme di variabili di stato booleane che modificano,
attraverso istruzioni condizionali, il comportamento dei metodi che gestiscono gli
eventi (ad esempio la variabile ismoving, se ha valore true, afferma che si sta spostando un oggetto). Nella classe MyView ci sono tutti i metodi che creano e modificano gli oggetti grafici contenuti nell’area rappresentata.
Questa struttura è stata così articolata per permettere la massima flessibilità dell’applicazione e per rendere possibile una realizzazione di successivi prototipi che si avvicinino per incrementi ad una versione dell’applicazione completa di tutte le funzionalità richieste ad un modellatore UML.
Nel complesso, un modellatore UML che si fonda sul package Diagrammi
UML fornisce all’utente un’interfaccia per la creazione e la modifica di diagrammi
UML. L’utente dell’applicazione costruisce il proprio modello UML attraverso la
creazione dei diagrammi. Ogni diagramma è formato da tre oggetti principali di
tipo DiaDoc, DiaView, MyView; il primo oggetto ha il compito di creare oggetti
della classe CppUML (questi sono rappresentati dalle icone grafiche appartenenti
al package OCppUML che l’utente crea nel diagramma) che vanno a formare il
modello UML; non importa costruire liste che mantengano i riferimenti agli
oggetti del modello utente contenuti nel diagramma: infatti un oggetto DiaDoc
contiene un riferimento all’area QCanvas dove vengono piazzate le icone che
rappresentano tali oggetti. Un’area QCanvas può restituire con metodi propri la
lista di tutti gli oggetti ivi contenuti: ovvero quelli che fanno da icona ad un
oggetto del modello e ne sono anche osservatori, quindi a causa del pattern
Observer ne mantengono un riferimento facilmente reperibile estendendo
l’interfaccia di tali osservatori.
Il secondo oggetto (DiaView) osserva i cambiamenti nel primo e li rappresenta attraverso il terzo oggetto (MyView). Quest’ultimo è la visualizzazione del diagramma QCanvas e l’utente interagisce con esso. I simboli bidimensionali, che hanno il compito di rappresentare il modello UML costruito con istanze di classi della libreria CppUML, sono visualizzati da questo oggetto, ma sono sempre contenuti nell’oggetto QCanvas. In sostanza, un utente che aggiunge un nuovo elemento nel modello che sta progettando attraverso un diagramma, attiva un’interazione fra i suddetti tre oggetti e questa interazione ha come frutto la creazione di un oggetto della classe CppUML che definisce il modello UML dell’elemento inserito, del suo osservatore (ovvero il simbolo bidimensionale che rappresenta secondo la notazione UML tale elemento del modello) e la registrazione dell’uno all’altro secondo il protocollo descritto a pagina 97.
Gli osservatori delle classi della libreria CppUML sono definiti nel package OCppUML. Questi sono costituiti da classi sottotipo della classe Observer e sono a loro volta appartenenti a una gerarchia di classi con a capo QCanvasItem, o hanno come propri componenti classi appartenenti ad una tale gerarchia (abbiamo visto in figura 2.8, a pagina 93, un soggetto concreto Class della libreria CppUML e un osservatore concreto OClass appartenente al package “OCppUML” con tre componenti discendenti della classe QCanvasRectangle).
Di seguito si illustra la gerarchia predefinita dalle librerie Qt di classi
discendenti da QCanvasItem.
Figura 3.3 – Gerarchia delle classi derivate da QCanvasItem definite nella libreria Qt.
Da queste classi un utente della libreria Qt può derivare classi di oggetti personalizzati che possono essere inseriti in un oggetto QCanvas.
3.2.1 Package OCppUML
Si illustrano di seguito le classi che hanno il compito di rappresentare gli
oggetti della libreria CppUML del sottopackage Core e di rimanere aggiornate
rispetto a questi attraverso l’applicazione del pattern Observer: tale aggiornamento
istantaneo, come suggerisce il nome del pattern, porta spontaneamente a chiamare
osservatori le istanze delle classi del package OCppUML e a chiamare soggetti le
istanze delle classi di CppUML che sono gli oggetti del modello da osservare e
quindi da rappresentare. Inoltre, si è pensato di dare il nome della corrispondente
classe soggetto preceduta dal prefisso ‘O’ ad ogni classe osservatore del package OCppUML.
La descrizione dettagliata di come rappresentare graficamente queste classi si può trovare nel capitolo 3. di [UMLS] dal titolo: “UML Notation Guide”. I diagrammi che seguono non riportano tutti i dettagli di ogni classe, ma solo quelli che sono stati ritenuti significativi per la comprensione della loro struttura logica.
Per approfondire gli attributi ed i metodi di tali classi si rimanda al codice sorgente.
OClass
La classe OClass ha il compito di osservare un oggetto tipo Class
30della libreria CppUML. Si è gia visto nel paragrafo 2.4.3 come le istanze di Class e di OClass possano rimanere sincronizzate grazie al pattern Observer e al suo protocollo di aggiornamento: in particolare nella figura 2.8 si vede sulla sinistra la gerarchia di classi da Base a Class della libreria CppUML, che eredita l’interfaccia della classe Subject, e sulla destra la classe OClass del package OCppUML, che eredita l’interfaccia della classe Observer. L’applicazione del pattern Observer tra le classi di CppUML e di OCppUML avviene sempre in questo modo, quindi nei prossimi diagrammi saranno lasciati sempre meno dettagli, riguardo questo aspetto, per concentrare l’attenzione sull’aspetto grafico.
30
Class è una specializzazione di Classifier: se interessano le caratteristiche che riguardano
la specifica UML si consultino le figure 1.22 e 1.26.
Non resta che approfondire l’aspetto della grafica di un oggetto OClass: dalle specifiche della notazione UML una classe è disegnata come un rettangolo con all’interno tre compartimenti separati da linee orizzontali. Il compartimento che si trova più in alto contiene il nome della classe ed altre sue proprietà, quello centrale contiene la lista degli attributi, quello inferiore contiene la lista delle operazioni.
Di seguito si mostra un diagramma della classe OClass che illustra più in dettaglio le proprietà grafiche di questa classe.
Figura 3.4 – Le proprietà grafiche della classe OClass.
Le classi che hanno come lettera iniziale ‘Q’ fanno parte delle librerie grafiche Qt: le classi fornite da questa libreria sono state sempre specializzate prima di essere adoperate per personalizzarle ed aggiungere metodi utili alla manipolazione ed al riconoscimento delle loro istanze all’interno di un diagramma. Due sono le modifiche principali che sono state fatte alle classi usate di tale libreria; la prima è la ridefinizione della funzione int rtti() che permette di riconoscere il tipo dell’oggetto, su cui si invoca tale metodo, tramite il numero restituito (ad esempio quando l’utente preme il tasto sinistro su un oggetto del diagramma è possibile, con i metodi offerti dalla classe QCanvas, ottenere un puntatore generico objPtr di tipo QCanvasItem, all’oggetto con cui l’utente ha interagito. Per riconoscere ed adoperare poi i metodi esclusivi del tipo di oggetto puntato da objPtr, tramite un opportuno cast del puntatore, si deve invocare il metodo objPtr->rtti() che restituisce l’intero che identifica univocamente il tipo dell’oggetto); la seconda modifica è la specifica della profondità di ogni elemento grafico. In questo modo, in caso di sovrapposizione di oggetti di diverso tipo, quello più in superficie copre quello più profondo.
Tornando alla descrizione della classe OClass si nota che possiede tre
componenti per la propria visualizzazione: NameComp, AttrComp e OperComp
che corrispondono rispettivamente ai compartimenti del nome, degli attributi e dei
nomi. Tutti e tre i compartimenti sono specializzazioni della classe
QCanvasRectangle: un oggetto di questa classe è un rettangolo di cui si possono
cambiare la posizione, le dimensioni, il colore dell’interno e del bordo, lo spessore
della linea, ecc.. Ogni componente ha un riferimento owner all’oggetto OClass che lo possiede; viceversa l’oggetto OClass ha i riferimenti compname, compattr e compoper ai tre oggetti compartimento che possiede (questi riferimenti sono evidenziati, nel diagramma in figura 3.4, dal nome delle estremità delle associazioni che legano OClass ai suoi componenti NameComp, AttrComp e OperComp. Questo modo di indicare i riferimenti di una classe a quelle classi con cui è associata è una convenzione che da ora in poi si lascia sottintesa):
• compname ha i tre attributi tipo testo
31(name, properties e stereotype) per rappresentare all’utente i corrispondenti valori dell’oggetto Class osservato;
• compattr ha come attributo la struttura tipo Coda<Testo*> di nome textattr che permette di gestire un numero qualsiasi di righe di testo che rappresentano gli attributi dell’oggetto Class osservato;
• compoper ha come attributo la struttura tipo Coda<Testo*> di nome textoper
che permette di gestire un numero qualsiasi di righe di testo che rappresentano i metodi dell’oggetto Class osservato.
Segue la dichiarazione di OClass:
/*Remember: Every change to Metamodel must be done first to classes of CoreElements then The observers will be updataed by the function Notify() that's in every set- Method */
class OClass : public Observer {
public:
31
Testo è derivato dalla classe QCanvasText che permette di inserire del testo su di un
oggetto QCanvas e definirne la posizione, il testo, il carattere ed il colore.
OClass(Class* , QCanvas*);
~OClass();
int rtti() const{ return 40100;};
void Update(Subject*, Aspects needed=Nname);
virtual Base* mysubject() {return ((Base*)MySubject);}
//These are the squares that form the visual widget of a class NameComp* compname;
AttrComp* compattr;
OperComp* compoper;
// parametro* comppar; (not yet implemented)...
// return the rectangle that bounds the Class widget QRect boundingRect();
void ReBorder(int, int);
void fitToText(); // fit classifier width to text inside.
int maxwidth(); // return max width of text inside margin included QString desc;
// to resize the widget properly.
int nattr;
int noper;
void Hide();
void Show();
Coda <OAssociationEnd*> associations;
//the edges of dependencies on this Class as supplier Coda<Arrow*> arrowdependencies;
// all lines of client dependencies that start from this class as client Coda<DipLine*> cliientLines;
Coda<Arrow*> arrowsonNodeCross; //all arrows on top of clientLines void placeAend(); //to place properly squares of AssociationEnd
private:
Class* MySubject;
};
Vediamone i metodi:
• mysubject() restituisce un puntatore generico Base al soggetto osservato
(tramite la funzione getUMLName() è sempre possibile discriminare il tipo del
soggetto);
• boundingRect() restituisce un rettangolo che circonda i tre componenti
dell’istanza di OClass. Viene usato quando si sposta una classe da un punto ad un altro del diagramma per ottenere un riquadro che nasce dal contorno della stessa classe e viene spostato tramite il mouse nel punto ove si vuole posizionare la classe;
• ReBorder (int xspost, int yspost) sposta sul diagramma l’oggetto OClass
insieme ai suoi componenti e a tutti i propri attributi grafici della quantità specificata dalle coordinate relative alla posizione precedente (xspost, yspost);
• fitToText() ridimensiona i componenti dell’oggetto OClass in modo tale che circondino con le giuste distanze tutte le righe di testo che lo specificano;
• maxwidth() restituisce la larghezza massima espressa in numero di pixel del testo incluso nell’oggetto OClass;
• Hide(), Show() servono per nascondere/mostrare l’oggetto OClass.
Vi sono altri attributi e metodi che ora non vengono descritti perché riguardano la rappresentazione delle associazioni e delle dipendenze.
Si era visto il meccanismo di aggiornamento nel paragrafo 2.4.3: per approfondire il suo funzionamento, solo per questa classe, si riporta il codice della funzione Update(…) che è ridefinita per ogni classe del package OCppUML:
void OClass::Update(Subject* changedsub, Aspects needed) { if (changedsub==MySubject)
{ //const QString copyname(MySubject->Getname()) ; QFont und, bold, ibold, uitalic, italic;
ibold.setBold(true);
ibold.setItalic(true);
bold.setBold(true);
und.setUnderline(true);
uitalic.setItalic(true);
uitalic.setUnderline(true);
italic.setItalic(true);
QString appg("");
if( needed==Nname) {
if (MySubject->isAbstract())
compname->textname->setFont(ibold);
else compname->textname->setFont(bold);
compname->textname->setText(MySubject->getName().c_str());
fitToText();
}
if (needed==Nall) {
if (MySubject->isAbstract())
compname->textname->setFont(ibold);
else compname->textname->setFont(bold);
if (MySubject->isLeaf()) appg="{Leaf}";
if (MySubject->isRoot())
{ if (MySubject->isLeaf()) appg="{Leaf, Root}";
else
appg= "{Root}";
}
compname->prop_list[0]->setText(appg);
//Comp Attrib. & Oper.
Coda<Feature*> feat(MySubject->getFeatures());
feat.resetcursor();
Testo* txt;
int i,k,j,nextattribY,nextoperY, X; // k utility index i=0; //row's index of attributes' table)
int maxvettindex;
nextattribY =compattr->areaPoints().point(0).y()+1;
X=compattr->areaPoints().point(0).x()+6;
while(!feat.cursorisnull()) {
if (feat.currentitem()->getUMLClassName()=="Attribute") //Comp Attrib.
{
maxvettindex=compattr->textattr.size()-1;
if (i>maxvettindex) {
txt=new Testo(compattr->canvas());
compattr->textattr.add(txt);
txt->show();
txt->setX(X);
txt->setY(nextattribY) ; }
else
txt=compattr->textattr.get(i);
nextattribY=nextattribY+11;
i++;
appg="";
// ATTR VISIBILITY
switch ( feat.currentitem()->getVisibility()) {
case _public:
appg=appg+"+";
break;
case _private:
appg=appg+"-";
break;
case _protected:
appg=appg+"#";
break;
case package:
appg=appg+"~";
break;
};
//ATTR NAME
appg=appg+feat.currentitem()->getName().c_str() ; //ATTR MULT & ORDERING
if ( (dynamic_cast<Attribute*>(feat.currentitem())-
>getMultiplicity().getMultiplicity()) !="1") {
appg=appg+"[" ;
appg=appg+dynamic_cast<Attribute*>(feat.currentitem())-
>getMultiplicity().getMultiplicity().c_str() ;
if ( (dynamic_cast<Attribute*>(feat.currentitem())-
>getOrdering()==ordered)) appg=appg+" ordered" ; appg=appg+ "]";
} //ATTR TYPE
appg=appg+ " :";
appg=appg+dynamic_cast<Attribute*>(feat.currentitem())-
>getType()->getName().c_str();
//ATTR INIT VALUE
if ( dynamic_cast<Attribute*>(feat.currentitem())-
>getInitialValue().getBody()!="") {
appg=appg+ " =";
appg=appg+ dynamic_cast<Attribute*>(feat.currentitem())-
>getInitialValue().getBody().c_str();
}
// *************************************
//ATTR CHANG.
if (dynamic_cast<StructuralFeature*>(feat.currentitem())-
>getChangeability()!= changeable) {
appg=appg+ " { ";
switch
(dynamic_cast<StructuralFeature*>(feat.currentitem())->getChangeability()) {
case frozen:
appg=appg+"frozen";
break;
case addOnly:
appg=appg+"addOnly";
break;
};
appg=appg+"}";
} // SCOPE switch
(dynamic_cast<StructuralFeature*>(feat.currentitem())->getTargetScope()) {
case instance:
; break;
case classifier:
txt->setFont(und);
break;
};
txt->setText(appg);
} //end if feat.shiftcursor();
} //end while
//now we must delete from compattr->textattr pointers not used maxvettindex=compattr->textattr.size()-1;
k=i;
while( i<=maxvettindex) {
txt=compattr->textattr.get(k); //because I remove every cycle a pointer from the queue, so I must get at the same index!
compattr->textattr.remove(txt);
delete txt;
i++;
}
//adjusting attr heigh
nextattribY =compattr->areaPoints().point(0).y()+1;
compattr->textattr.resetcursor();
while( !compattr->textattr.cursorisnull()) {
nextattribY+= 11;
compattr->textattr.shiftcursor();
}
compattr->setSize(compattr->width(), (nextattribY-(compattr-
>areaPoints().point(0).y())+5 ));
compoper->move(compattr->areaPoints().point(0).x()+1,compattr-
>areaPoints().point(2).y()-1);
//Oper comp
feat.resetcursor();
nextoperY =compoper->areaPoints().point(0).y()+1;
Coda<Parameter*> parameters;
Classifier* clptr;
Operation* optr;
j=0; //indice riga della tabella delle operazioni (row's index of operations' table)
while(!feat.cursorisnull()) {
if (feat.currentitem()->getUMLClassName()=="Operation") //Comp Operation.
{
maxvettindex=compoper->textoper.size()-1;
if (j>maxvettindex) {
txt=new Testo(compoper->canvas());
compoper->textoper.add(txt);
txt->show();
txt->setX(X);
} else
txt=compoper->textoper.get(j);
txt->setY(nextoperY) ; nextoperY=nextoperY+11;
j++;
appg="";
// OPER VISIBILITY
switch ( feat.currentitem()->getVisibility()) {
case _public:
appg=appg+"+";
break;
case _private:
appg=appg+"-";
break;
case _protected:
appg=appg+"#";
break;
case package:
appg=appg+"~";
break;
};
//OPER NAME
appg=appg+feat.currentitem()->getName().c_str() ; //OPER parameterLIst
appg=appg+"(";
parameters=dynamic_cast<Operation*>(feat.currentitem())->
getParameters();
parameters.resetcursor();
parameters.shiftcursor();
while(!parameters.cursorisnull()) {
appg=appg+parameters.currentitem()->
getName().c_str();
appg=appg+" : ";
clptr=parameters.currentitem()->getType();
appg=appg+ clptr->getName().c_str();
if ( !parameters.currentitem()->
getDefaultValue().getBody().empty() ) {
appg=appg+" = ";
appg=appg+parameters.currentitem()->
getDefaultValue().getBody().c_str();
}
parameters.shiftcursor();
if (!parameters.cursorisnull()) appg=appg+", ";
}
optr=dynamic_cast<Operation*>(feat.currentitem());
//OPER RETURNTYPE
appg=appg+") : "+optr->getParameter(0)->getType()->
getName().c_str() ; //PROPERTIES LIST
if ( (optr->isRoot()==true) ||
(optr->isLeaf()==true) ||
(optr->isQuery()==true)||
(optr->getConcurrency()!=sequential) ) {
appg=appg+" {";
if (optr->isRoot()) appg=appg+" Root";
if (optr->isLeaf()) appg=appg+" Leaf";
if (optr->isQuery()) appg=appg+" Query";
if (optr->getConcurrency()!=sequential) switch (optr->getConcurrency()) {
case guarded:
appg=appg+" concurrency=guarded";
break;
case concurrent:
appg=appg+" concurrency=concurrent";
break;
};
appg=appg+"}";
}
// ABSTRACT && SCOPE
if ((optr->isAbstract()) && (optr-
>getOwnerScope()==classifier)) txt->setFont(uitalic);
else{ if (optr->isAbstract()) txt->setFont(italic);
else if (optr-
>getOwnerScope()==classifier) txt->setFont(und);
}
txt->setText(appg);
}//end if
feat.shiftcursor();
} //end while
//now we must delete from compoper->textoper pointers not used
maxvettindex=compoper->textoper.size()-1;
k=j;
while( j<=maxvettindex) {
txt=compoper->textoper.get(k); //because I remove every cycle a pointer from the queue, so I must get at the same index!
compoper->textoper.remove(txt);
delete txt;
j++;
}
//adjusting oper heigh
nextoperY =compoper->areaPoints().point(0).y()+1;
compoper->textoper.resetcursor();
while( !compoper->textoper.cursorisnull()) {
nextoperY+= 11;
compoper->textoper.shiftcursor();
}
compoper->setSize(compoper->width(),
(nextoperY-(compoper->areaPoints().point(0).y())+5 ));
fitToText();
} } }
Come si può notare una funzione di Update(…) è molto lunga e
particolareggiata, perché oltre a dovere aggiornare i dati rappresentati deve
cambiare lo stile della rappresentazione a seconda dei dati stessi (testo in grassetto
per il nome della classe, testo in corsivo se indica un’entità astratta, sottolineato se
lo scope dell’attributo è di classe invece che di istanza, ecc.). Questa funzione di
Update(…) ha un blocco di aggiornamento molto lungo perché è previsto anche il
parametro tipo Aspects Nall che specifica che l’osservatore deve aggiornare tutti i
dati rispetto al soggetto (questo risulta utile perché nell’implementazione del
modellatore la maggior parte delle modifiche ad una classe si fanno attraverso una finestra di dialogo alla cui chiusura aggiorna tutte le sue caratteristiche). Possono essere previsti diversi tipi di implementazione di tale funzione, ma saranno sempre caratterizzati da una certa lunghezza del loro codice; proprio per questa ragione e per il fatto che hanno struttura simile non si riportano in seguito le funzioni di aggiornamento delle altre classi “osservatore”.
OGeneralization e OGeneralizableElement
Questi sono gli osservatori rispettivamente delle classi Generalization e
GeneralizableElement della libreria CppUML. Si riporta il diagramma di classe
che presenta queste due classi soggetto concreto e queste due classi osservatore,
facendo vedere tutta la gerarchia che da Subject va alla classe Base e poi porta alle
due classi soggetto concreto per illustrare l’applicazione del pattern Observer. Per
semplificare la consultazione del diagramma è stata aggiunta una linea tratteggiata
orizzontale, che divide la parte superiore, relativa agli osservatori appartenenti al
package OCppUML, da quella inferiore, relativa ai soggetti della libreria
CppUML (i dettagli di tali classi e delle loro relazioni si possono vedere in figura
1.23). A causa della pesantezza di tale diagramma, nei successivi diagrammi, non
è riportata la gerarchia che porta agli attuali soggetti degli osservatori perché è più
facilmente consultabile nei diagrammi del package Core nella specifica ufficiale
dello UML di OMG (si vedano le figure 1.22 – 1.26), con l’unica accortezza che
nella libreria CppUML sono state aggiunte le classi Subject e Base in testa alla
gerarchia.
Figura 3.5 – Le coppie di osservatore/soggetto: OGeneralization/Generalization;
OGeneralizableElement/GeneralizableElement.
Vediamo gli aspetti grafici di questi due nuovi osservatori.
OGeneralization
OGeneralization deve osservare un oggetto Generalization che indica una
relazione fra un elemento più generale (indicato nel diagramma di figura 1.23
come parent) e uno più specifico (child): l’elemento più specifico eredita tutte le
proprietà, gli attributi, le operazioni e le relazioni da quello più generale e dovrebbe contenere informazioni addizionali. Questo tipo di relazione può avvenire fra classi, package, use case e altri elementi (infatti le precedenti classi sono tutte specializzazioni della classe GeneralizableElement).
OGeneralization è un osservatore che può rappresentare su un diagramma
una tale relazione fra due istanze di classi Class di cui mantiene il riferimento ai
loro osservatori nei propri attributi child e parent di tipo puntatore a OClass. Un
oggetto OGeneralization ha due puntatori g_child e g_parent a istanze
dell’osservatore OGeneralizableElement (tali puntatori sono indicati in figura 3.6
come i ruoli delle relazioni che legano le due classi osservatore): un’istanza di
quest’ultimo osservatore ha una semantica diversa rispetto al proprio soggetto,
infatti non è altro che un secondo osservatore di istanze di Class (in quanto
specializzazioni di GeneralizableElement). Quindi gli oggetti che entrano in gioco
quando si costruisce una generalizzazione fra due classi sono: due oggetti tipo
OClass e ciascuno di essi osserva il corrispondente oggetto tipo Class, un oggetto
OGeneralization, che osserva il corrispondente oggetto tipo Generalization, e
infine due osservatori tipo OGeneralizableElement, che osservano i due
precedenti oggetti tipo Class rappresentando esclusivamente il ruolo di padre o
figlio del proprio soggetto nella relazione di generalizzazione. Quindi nel modello
UML della corrispondente relazione vi sarà un’istanza di Generalization che lega
due istanze di Class in una relazione di generalizzazione; infatti non si possono
avere istanze della classe GeneralizableElement in quanto è astratta, ma si possono avere istanze delle sue specializzazioni.
Per poter rappresentare la relazione tra classe padre e figlio un oggetto OGeneralization possiede un cammino costituito da una o più linee consecutive che partono dall’osservatore riferito da child e arrivano all’osservatore riferito da parent; questo può avverarsi perché un oggetto OGeneralization ha un riferimento path ad uno o più oggetti tipo GenLine
32, che disegnano il cammino, e che possiede esclusivamente (infatti sono suoi componenti). Inoltre possiede zero o più componenti G_Vertex
33che vengono posti sulle giunzioni tra una linea e quella successiva del cammino in modo da offrire all’utente un oggetto da trascinare per ottenere il posizionamento corretto del cammino (infatti la gestione degli eventi della classe MyView si occupa di mantenere un G_Vertex solidale alla giunzione delle due linee consecutive; quindi se se ne sposta uno il cammino viene modificato di conseguenza).
OGeneralizableElement
Come già accennato vi è una differenza semantica tra un OGeneralizableElement e un GeneralizableElement. Infatti il primo è un osservatore della classe Class che partecipa ad una relazione di generalizzazione e rappresenta l’estremo di tale relazione con un vertice speciale di tipo GE_Vertex
32
GenLine deriva dalla classe QCanvasLine che rappresenta una linea su di un QCanvas.
33
G_Vertex non è altro che un vertice rappresentato come un piccolo quadrato.
e, nel caso che la classe Class abbia il ruolo di parent nella relazione, con un triangolo tipo Triangle
34. Tali vertice e triangolo vengono piazzati all’estremo del corrispondente ruolo del cammino della generalizzazione e hanno lo scopo di unire il cammino con l’osservatore tipo OClass della classe Class che partecipa alla generalizzazione. In pratica OGeneralizableElement è un secondo osservatore delle classi Class che hanno il ruolo di padre e figlio nella relazione di generalizzazione. Invece la classe GeneralizableElement è essa stessa la classe padre o figlio di una relazione di generalizzazione: infatti un oggetto Class è una specializzazione di un GeneralizableElement (come si vede da figura 1.23).
Tornando alla notazione grafica bisogna dire che un oggetto OGeneralizableElement possiede zero o un oggetto tipo QPointArray che mantiene le coordinate dei vertici del triangolo riferito da pol ed è usato come parametro del costruttore dello stesso triangolo.
OAssociation e OAssociationEnd
Questi sono gli osservatori delle corrispondenti classi della libreria CppUML Association e AssociationEnd (il loro diagramma si trova in figura 1.23). Nel successivo diagramma di classe si mostrano le proprietà grafiche di queste due classi.
34
Triangle è una specializzazione della classe QCanvasPolygon che permette di disegnare un
poligono su di un QCanvas. Le coordinate dei suoi vertici sono mantenute in un vettore di
coordinate tipo QPointArray.
Figura 3.6 – Gli osservatori OAssociation e OAssociationEnd.
OAssociation
Questa classe ha per componenti uno o più oggetti tipo AssocLine
35di cui mantiene un riferimento di nome path. Come nel caso della generalizzazione gli oggetti riferiti da path corrispondono al cammino di linee consecutive che legano due oggetti OClass (quindi rappresentano un’associazione tra due oggetti tipo Class). Il cammino parte sempre dal centro di una classe per arrivare nel centro dell’altra classe; la parte di cammino che si sovrappone alle due classi, che ne
35
AssocLine deriva dalla classe QCanvasLine che rappresenta una linea su di un QCanvas.
costituiscono le estremità, rimane nascosto, perché un oggetto AssocLine ha profondità più alta rispetto ai componenti di una OClass. Sulle giunzioni di due linee viene posto un oggetto tipo Vertex che è anch’esso componente di OAssociation ed è riferito con una struttura di puntatori di nome verteces.
OAssociation ha per attributo una variabile booleana di nome binary che se ha valore true asserisce che l’associazione è binaria, altrimenti è un’associazione fra più classi e con tale informazione i metodi che gestiscono la classe variano comportamento. Un oggetto OAssociation deve poter mostrare il nome dell’oggetto osservato, quindi possiede fra i suoi attributi un riferimento name che punta ad un oggetto tipo Textonwhite.
Figura 3.7 – TextonWhite è un testo fornito di sfondo bianco.
La classe Textonwhite è stata definita per offrire la possibilità di scrivere del
testo su di un QCanvas con uno sfondo rettangolare bianco della dimensione
esatta dei confini del testo; in questo modo, se tale testo si sovrapponesse a delle
linee di associazioni presenti nel diagramma, il testo rimarrebbe ben leggibile,
perché lo sfondo coprirebbe le linee retrostanti (infatti sfondo e testo hanno una profondità meno elevata rispetto agli altri oggetti grafici). Un oggetto Textonwhite è una specializzazione della classe Testo e possiede come componente un oggetto tipo QCanvasRectangle, che riferisce con il puntatore background, e che funge da sfondo per il testo.
OAssociationEnd
Osserva un oggetto AssociationEnd della libreria CppUML e rappresenta l’estremità di un’associazione che si connette ad un oggetto OClass. Ha un riferimento ad un oggetto tipo AEVertex di nome square; tale oggetto ha il compito di disegnare un piccolo quadrato sulla intersezione del cammino dell’associazione ed il bordo dell’oggetto OClass osservatore dell’oggetto Class che partecipa all’associazione: questo ornamento è stato inserito per dare la possibilità all’utente di interagire direttamente con un ruolo di un’associazione.
Un oggetto OAssociationEnd possiede due attributi tipo puntatore a Testonwhite per rappresentare le proprietà dell’oggetto AssociationEnd che osserva: mul che rappresenta la molteplicità dell’estremità dell’associazione (viene indicata solo se diversa da “1”); rolename indica il nome del ruolo dell’estremità dell’associazione. Per disegnare l’eventuale freccia, che indica la navigabilità della relazione nel solo verso indicato dalla freccia stessa, vi sono due puntatori l1 ed l2 ad ArrowLine
36per disegnare i due tratti della freccia.
36
Arrowline è una specializzazione di QCanvasLine.
Un oggetto OAssociationEnd possiede zero o un componente tipo Lozenge
37; questo componente serve nel caso che l’estremità della relazione corrisponda ad una classe aggregato o composizione, nei quali casi bisogna disegnare una losanga a tale estremità della relazione di colore bianco se un’aggregazione o nero nell’altro caso. Il componente QPointArray serve per costruire un componente Lozenge come nel caso di Triangle visto per le generalizzazioni.
ODependency
ODependency è l’osservatore della classe Dependency (per consultare le relazioni di quest’ultima con le altre classi della libreria CppUML si veda la figura 1.24). Nella pagina seguente si presenta il diagramma che mostra le proprietà grafiche di ODependency.
37
Lozenge è una specializzazione di QCanvasPolygon e serve a disegnare una losanga.
Figura 3.8 – Proprietà grafiche di ODependency.
Una dipendenza può sussistere fra due elementi del modello ed è rappresentata da una freccia tratteggiata che parte dall’elemento client e arriva sull’elemento supplier (ad indicare che il primo dipende dal secondo). Una dipendenza può essere etichettata con uno stereotipo ed è possibile avere un insieme di elementi client che dipendono da uno o più elementi supplier.
Il fatto che una dipendenza può sussistere fra due elementi generici del
modello è mostrato nel diagramma della specifica dello UML in figura 1.24: si
può osservare che il client ed il supplier di una dipendenza sono classi astratte tipo
ModelElement, ad indicare che una dipendenza può legare qualsiasi classe
concreta che discenda da ModelElement. Dato che non esiste un osservatore di una classe ModelElement in quanto è una classe astratta, si è ritenuto vantaggioso porre come client e supplier di un oggetto ODependency un oggetto della classe astratta Observer: in questo modo si può rappresentare una relazione di dipendenza fra qualunque classe che è specializzazione di quest’ultima e quindi fra qualunque osservatore del package OCppUML.
Si può notare che un oggetto ODependency possiede uno o più componenti supplier ed un solo client; si vedrà in seguito come è possibile rappresentare una dipendenza fra un insieme di client ed uno o più supplier.
Per disegnare la freccia, che parte dal client ed arriva sull’elemento supplier, un oggetto ODependancy possiede uno o più componenti tipo DipLine
38riferiti con la struttura di puntatori path.
Se una dipendenza lega un insieme di client con più supplier allora ci sono più frecce che partono dai client ed arrivano su un componente dell’osservatore ODependency di tipo NodeCross
39e da questo partono le frecce che portano ai supplier della dipendenza. In definitiva, un oggetto ODependency ha la responsabilità di mantenere un riferimento al primo osservatore di client e una
38
DipLine è una specializzazione di QCanvasLine, può rappresentare una linea ed ha la proprietà di disegnarla tratteggiata. DipLine serve quindi a disegnare la coda della freccia.
39
NodeCross definisce un vertice particolare del cammino che permette di disegnare
dipendenze fra più di un client e più di un supplier. Se si unisce un client ad un supplier non è
necessaria la sua presenza. In seguito si approfondisce questo aspetto.
struttura di riferimenti a tutti gli osservatori di supplier della dipendenza osservata; inoltre possiede un riferimento ad un oggetto NodeCross che, quando è presente, dà la possibilità di aggiungere altri client alla dipendenza; infatti un NodeCross ha per componenti degli ulteriori oggetti tipo Observer che riferisce con la struttura theClients e che corrispondono agli osservatori dei clienti aggiunti alla dipendenza.
Una freccia che rappresenta una dipendenza è costituita da due elementi: un
oggetto DipLine, con alla sua testa un oggetto tipo Arrow, che disegna con due
linee poste a ‘V’ la punta della freccia. Un oggetto Arrow ha una funzione
importante per capire la struttura di una dipendenza fra molti client e molti
supplier; infatti un oggetto ODependency memorizza in una struttura ordinata di
nome path tutte le linee tipo DipLine che costituiscono il cammino dal primo
client a tutti i supplier. Per capire come si identificano e quindi si collegano le
linee e le frecce tipo Arrow si può osservare la seguente figura che illustra la
dipendenza di alcuni client da più di un supplier. Per comodità è stato aggiunto il
numero d’ordine di inserimento nella struttura path delle linee che costituiscono la
rappresentazione della dipendenza.
Figura 3.9 – Esempio di dipendenza fra tre client e quattro supplier.
Quindi un oggetto ODependency ha per componenti due strutture ordinate:
path, che contiene i riferimenti alle DipLine (le linee tratteggiate che si vedono in
figura), ed arrows, che contiene i riferimenti alle punte di freccia posizionate sui
soli supplier (quindi gli oggetti Arrow in testa alle linee 2, 3, 5 e 7). Ogni oggetto
Arrow ha un riferimento owner alla linea tipo DipLine che lo possiede, ovvero la
linea con cui costituisce una freccia. Se si vuole ricostruire la rappresentazione
della dipendenza si prendono le due strutture path e arrows ivi contenute e si
analizzano in questo modo: se il primo oggetto in path è riferito dal puntatore
owner che si trova nel primo oggetto in arrows allora si ha una dipendenza fra un
client e un supplier con una freccia che li unisce e che è costituita da una linea ed un oggetto Arrow. In questo caso invece si ha la linea 1 come primo elemento di path e la punta di freccia che sta in testa alla linea 2 come primo elemento di arrows; dal fatto che tale oggetto Arrow non è posseduto dalla linea 1 si deduce che ci sono altre linee che costituiscono il path e quindi in testa alla linea 1 ci va il componente node che è lo speciale vertice tipo NodeCross che si mette in testa alla prima linea di un cammino costituito da più di un oggetto DipLine. In seguito si prende il secondo oggetto del path, si verifica che è riferito dal primo oggetto Arrow di arrows quindi tale oggetto Arrow va in testa alla linea 2 e tale linea unisce il vertice speciale node con “Supplier_1”. Quindi si prende il secondo riferimento in arrows e dato che esiste vuol dire che c’è un secondo supplier. Il terzo elemento di path deve partire dal vertice speciale che è la sorgente di tutti i cammini a oggetti supplier aggiunti alla dipendenza. Il secondo oggetto Arrow ha riferimento owner uguale al terzo riferimento di path, quindi tale punta di freccia va posizionata in testa alla linea 3 e deve indicare il secondo supplier. Si passa alla linea 4 di path che deve nascere dal vertice node e si riscontra che tale linea non possiede la terza punta di freccia, quindi tra la linea 4 e la 5 va posto un vertice semplice tipo D_Vertex
40(serve per trascinare le linee 4 e 5). Si passa alla
40
D_Vertex è una specializzazione di un QCanvasRectangle, funge da vertice di due DipLine
consecutive e offre all’utente un piccolo quadrato da trascinare per modificare la posizione della
giunzione con cui è solidale. Un D_vertex contiene un puntatore beforeLine alla DipLine che lo
precede nel cammino che va dal client al supplier.
linea 5 e si vede che il terzo oggetto contenuto in arrows ha puntatore owner che punta alla linea 5, che quindi possiede tale punta di freccia e indica in tal modo il terzo supplier. Altrettanto per le linee 6 e 7.
Il vertice NodeCross è stato concepito per aggiungere qualsivogliano oggetti client alla dipendenza. Infatti l’oggetto NodeCross possiede le due strutture toClients e the Clients: nella prima sono contenuti i riferimenti ordinati alle linee 8 e 9 che uniscono il secondo ed il terzo client con il NodeCross stesso; nella seconda invece ci sono i riferimenti ordinati al secondo ed al terzo client. Per mantenere un riferimento alle punte di freccia in testa alle linee 1, 8 e 9, un NodeCross possiede una struttura sua componente di nome arrowonthis che mantiene i riferimenti alle punte di freccia dei client aggiunti che puntano su se stesso.
Per facilitare la navigazione in questa struttura complessa ogni oggetto Arrow ha due puntatori che indicano quale oggetto è puntato dal vertice della freccia:
node e infronttopArrow. Di questi due puntatori è valido quello con valore diverso
da “NULL”. Il primo indica che la punta di freccia è su di un oggetto NodeCross e
ne fornisce il riferimento, il secondo indica che la punta di freccia è su di un
osservatore di un oggetto del modello e ne fornisce il puntatore generico ad un
Observer.
3.3 Sviluppi futuri
Si può vedere ora un progetto ad alto livello delle espansioni future che si possono aggiungere al modellatore UML per ottenere uno strumento CASE completo.
3.3.1 Analisi e specifica dei requisiti
Tipi di utente
1. Progettista software: usufruisce di tutte le funzionalità del CASE tool, può creare quindi tutti i tipi di diagrammi forniti dallo UML, può presentare i diagrammi dei casi d’uso ed i documenti che specificano i requisiti del problema di design da risolvere sia al committente che ad esperti del dominio del problema e ricevere da questi un feedback.
2. Committente del software: rilascia i requisiti del software che ordina e verifica la parte di progettazione astratta che può comprendere e verificare.
3. Persone esperte dell’oggetto di design: durante l’analisi dei requisiti forniscono il proprio know how e verificano i requisiti che vengono astratti da questo.
Funzionalità
• Disegno di diagrammi UML attraverso una semplice interfaccia grafica e
conseguente creazione del modello UML del progetto rappresentato.
• Possibilità di memorizzare, attraverso un repository, tutti i documenti raccolti e prodotti, in modo da rintracciarli e ordinarli attraverso diversi criteri.
• Supporto del controllo delle versioni.
• Consistency checking.
• Generazione automatica del codice a partire dal progetto fisico.
• Esportazione/importazione del modello software progettato attraverso il formato standard XMI
41.
• Stampa dei diagrammi.
• Salvataggio dei diagrammi UML e del relativo modello.
• Impostazione automatica del layout dei diagrammi.
Requisiti non funzionali ed ambiente di sviluppo
Come piattaforma si è deciso di utilizzare Linux, nella fattispecie sotto forma della distribuzione Mandrake 8.2, sia per motivi di stabilità che per poter sviluppare un software di tipo free. Quest’ultimo aspetto è stato considerato molto importante nella prospettiva di iniziare un progetto con lo scopo di realizzare uno strumento CASE, basato su UML, completo e non commerciale.
41