Richiami di ingegneria del software: la modellazione ad oggetti e UML
L’UML è un linguaggio standard utilizzabile per progettare, rappresentare o documentare qualunque tipo di sistema (software, hardware, organizzativo). NON è un linguaggio di programmazione. E’ uno standard OMG (Object Management Group) dal novembre del 1997; i suoi autori sono:
Ogni diagramma di UML é progettato per fornire a sviluppatori e clienti la vista di un sistema software da diverse prospettive e a vari gradi di astrazione. Diagrammi “logici”
Diagrammi di “implementazione”
Rappresentano le modalità di utilizzo del sistema da parte di uno o più utilizzatori (attori). Descrivono l’interazione tra attori e sistema, senza rivelare l’organizzazione interna del sistema. Ciascun caso d’uso può essere descritto in forma testuale, comprensibile anche ai “non addetti ai lavori”. Possono essere definiti a livelli diversi (sistema o parti del sistema). Il ragionare sui casi d’uso aiuta a esplicitare i requisiti del sistema.
Lo scopo di un diagramma delle classi è di rappresentare le classi e le relazioni tra esse all’interno di un modello (di analisi o di progettazione). Le relazioni tra classi definiscono un così detto modello statico del progetto. L’elemento fondamentale del diagramma delle classi é un’icona che rappresenta una classe, come indicato in figura.
Gli oggetti che costituiscono un’applicazione sono tipicamente istanze di classi che in generale non sono isolate, ma in relazione con altre classi mediante legami di varia natura, detti “relazioni“. Le principali relazioni tra le classi sono:
Una relazione gen-spec rappresenta un legame di generalizzazione-specializzazionetra classi, che si ha quando una classe è un caso particolare di una o più altre classi, ovvero quando una classe è una generalizzazione di un o più altre classi. Un legame gen-spec è tipicamente espresso nei requisiti in linguaggio naturale da frasi del tipo:
Le relazioni gen-spec danno luogo a strutture di ereditarietà. Il legame gen-spec è espresso in UML mediante una freccia dalla classe particolare (sottoclasse) a quella generale (superclasse).
L’ereditarietà puo’ essere singola o multipla; si ha ereditarietà multipla quando una classe è una specializzazione di (deriva da) due o più classi. Si distingue a volte tra ereditarietà di interfaccia ed ereditarietà di implementazione: nella prima si eredita solo la specifica e non l’implementazione. UML non ha una notazione specifica per questo, ma utilizza una linea tratteggiata che termina con una freccia per indicare l’ereditarietà di interfaccia.
La relazione di contenimento o aggregazione (o legame tutto-parti) tra classi esprime un legame di tipo contenuto-contenitore, ovvero un legame tra un insieme e le sue parti. Un legame di contenumento è tipicamente espresso nei requisiti in linguaggio naturale da frasi del tipo:
Si distingue tra contenimento forte (o stretto) e contenimento debole (o lasco). Si ha contenimento forte quando la parte non perde la sua identità e riconoscibilità quando entra a far parte del tutto.
Si ha contenimento debole quando la parte perde la sua identità e riconoscibilità quando entra a far parte del tutto.
Il contenimento forte è rappresentato in UML con un rombo pieno. quello debole con un rombo vuoto.
La relazione di contenimento debole indica l’indipendenza del ciclo di vita dell’oggetto contenuto dall’oggetto contenitore. L’oggetto contenuto potrà quindi esistere anche indipendentemente dal contenitore. Questo comporta la non responsabilità da parte del contenitore per la creazione e distruzione dell’oggetto contenuto.
Il contenimento debole si realizza introducendo nella classe contenitore C un puntatore all’oggetto x contenuto. Il costruttore di C riceve in ingresso il puntatore all’oggetto contenuto.
L’utente della classe C ha la responsabilità di: a) creare l’oggetto contenuto; b) definire ed inizializzare un puntatore ad esso; c) costruire l’oggetto contenitore passando il puntatore al contenuto. Il contenimento debole si puo’ realizzare in due modi: a) il contenitore (C) ha una variabile membro privata di tipo puntatore ad un oggetto di tipo contenuto (X); b) il contenitore (C) ha una variabile membro privata di tipo riferimento ad un oggetto di tipo contenuto (X). In entrambi i casi il contenitore dovrà definire un costruttore che riceva in input il puntatore (o il riferimento) all’oggetto contenuto.
La relazione di contenimento forte indica che l’oggetto contenuto non ha una vita propria ma esiste in quanto parte dell’oggetto contenitore. Pertanto l’oggetto contenitore é anche responsabile della costruzione e distruzione dell’oggetto contenuto. Il contenimento forte si realizza introducendo nella classe contenitore C una variabile membro x della classe contenuto X.
Attraverso il costruttore di C viene costruito un oggetto c e viene richiamato (anche implicitamente, a cura del compilatore) il costruttore della classe X per l’inizializzazione dell’oggetto x contenuto in c.
L’utente della classe C ha la responsabilità di istanziare un oggetto di tipo contenitore, richiamando il suo costruttore con tutti i valori di inizializzazione necessari sia all’inizializzazione del contenitore che del contenuto. Il contenimento forte si puo’ realizzare nel modo seguente: a) aggiungere una variabile membro alla classe contenitore del tipo della classe contenuto; b) implementare il costruttore del contenuto in modo da richiamare il costruttore del contenitore.
Consideriamo l’esempio riportato in figura. Esso esprime il concetto che l’Automobile (ogni istanza di Automobile) ha una Carrozzeria ed un Motore. La specifica prodotta indica un contenimento in senso lasco. Per quanto detto scegliamo la traduzione di tale relazione tramite l’uso dei puntatori per cui avremo:
I legami tra classi che non siano di tipo gen-spec nè di contenimento sono detti associazioni. Un’associazione esprime un legame semantico tra le classi. Essa è caratterizzata da:
NB: una associazione è intrinsecamente adirezionale (se A è associato a B, B è associato ad A; es.: legame matrimoniale tra uomo e donna. Pertanto la direzionalità indica solo una navigabilità nel diagramma (se da A si vuole risalire a B o viceversa, o entrambi i casi), ed attribuisce alla classe origine del percorso la responsabilità di tenere traccia dell’associazione.
Un’associazione può essere dotata di una classe associativa. Una classe associativa è caratterizzata da attributi e operazioni che non sono in sè connessi ad una specifica classe tra quelle poste in relazione, ma sono connessi all’associazione in quanto tale.
Le relazioni di associazione si traducono in C++ con una o più variabili membro di tipo puntatore alla classe associata (secondo la molteplicità dell’associazione).
Requisiti informali di utenteSi vuole progettare un sistema informativo per la gestione dei dipendenti di una azienda. L’automazione si riferisce esclusivamente alla:
Descrizione dei dipendenti dell’aziendaTutti i dipendenti dell’azienda sono caratterizzati all’interno di essa dal Nome e dal Cognome. I dipendenti dell’azienda sono di 4 “categorie”:
Lo stipendio dei dipendenti dipende dal ruolo che essi giocano nell’azienda.
Caso d’uso: registrare un nuovo dipendente.
Caso d’uso: stampare stipendio mensile (per tutti i dipendenti)
Da un’analisi testuale dei requisiti informali e dei casi d’uso presentati si individuano le classi di oggetti di dominio:
Tutte queste classi condividono delle caratteristiche (attributi) comuni: il nome, il cognome e lo stipendio mensile (attributi).
Altre caratteristiche invece sono proprie di ogni classe.
Se si raggruppano le caratteristiche comuni in una classe astratta “Dipendente” e si derivano da essa tutte le classi, il risultato è una gerarchia di classi. Ciò che specializza una qualsiasi classe è il ruolo giocato nell’azienda, mentre le caratteristiche proprie di ogni classe sono invece inserite nelle classi a cui appartengono.
Da un’analisi iniziale ci si accorge che il valore dell’attributo Stipendio Mensile può essere derivato (calcolato) a partire dal valore di altri attributi. Senza pensare all’algoritmo si intuisce che lo StipendioMensile dell’Agente di Vendita è derivabile dal NumeroDiContrattiEffettuati e dalla PagaPerContratto. Per rappresentare ciò, l’attributo StipendioMensile è stato specificato come “derivabile” (simbolo”/”). Tutti gli attributi sono stati specificati come “pubblici” (segno “+”). Per un qualsiasi oggetto sarà possibile leggere e scrivere il valore di tutti gli attributi escluso lo StipendioMensile che potrà essere solo letto.
Nel diagramma precedente non è stato rispettato il principio dell’information hiding che consiste nel nascondere le strutture dati e nel dotare le classi di metodiper l’accesso. Rivisitando il precedente diagramma delle classi in questi termini si otterebbe un nuovo diagramma le cui modifiche indicano che:
Quest’ultimo diagramma (o quello precedente a seconda dei casi) potrebbe essere sufficiente per quel che riguarda la modellazione statica del dominio considerato. A questo punto occorre entrare nel dominio della soluzione. Le problematiche da considerare sono: a) Come condurre le procedure descritte nei casi d’uso? b) Come interfacciarsi con l’utente? c) Come realizzare la persistenza degli oggetti? La soluzione a tali problemi comporterà l’aggiunta di nuove classi e la ridefinizione di quelle trovate. Pertanto sia il diagramma delle classi che i diagrammi di interazione prodotti devono essere rivisitati. A valle di ciò si potrebbe dare una architettura al sistema in termini di package. Ovviamente per sistemi poco complessi questa ultima fase può essere superflua.
a) Come condurre le procedure descritte nei casi d’uso?
In OO è molto utilizzata la metafora di Oggetto Coordinatore delle Procedure. Un oggetto Coordinatore si occupa del coordinamento dello scambio di messaggi tra gli oggetti di dominio e non (interfacce, servizi di persistenza…) per il compimento delle procedure richieste. Nel nostro caso si può pensare di racchiudere tutta la logica di controllo necessaria al compimento della procedura di “Registrazione di un nuovo dipendente” nel comportamento di un apposito oggetto “CoordinatoreRegistrazione”. Esso coordinerà tutte le operazioni necessarie (ad esempio):
b) Come interfacciarsi con l’utente.
L’interfacciamento con l’utente può sicuramente essere realizzata tramite l’uso di oggetti grafici messi a disposizione dai vari ambienti di sviluppo. E’ possibile usare ad esempio oggetti grafici offerti dalle Classi di Microsoft Foundation Class o similari. Per questo esempio l’interfaccia può essere realizzata tramite semplici oggetti, costruiti ad hoc, che richiedano all’utente l’inserimento delle informazioni. Ad esempio possiamo avere una classe MainWindow che ha la responsabilità di acquisire una tra le tre scelte procedurali messi a disposizione dall’utente (una sorta di menù). Ed ancora una finestra InsertionWindow per l’inserimento delle informazioni necessarie alla creazione di nuovi impiegati e così via.
2. La modellazione a oggetti e il linguaggio UML (richiami)
3. Generalità su Java e la programmazione ad oggetti
6. Regole di traduzione da UML a Java/C++
7. Programmazione multi-thread
8. Sincronizzazione tra thread
9. Programmazione client-server con socket TCP/IP (Java networkin...
10. Programmazione di applicazioni client-server: il Pattern Proxy...
12. Design Patterns
13. Pattern architetturali - Esempi
14. Design pattern creazionali. Esempi
15. Design pattern strutturali. Esempi
16. Introduzione alle tecnologie middleware
17. Modelli di middleware: RPC, MOM, TP, TS
Testo: Da C++ a UML:
Capitolo 32
Capitolo 33 §1 , §2.1 , §2.2
Capitolo 34 §§ 1, 2, 3, 4, 5
Capitolo 35