Class
Diagram
I ”class
diagramm” descrivono la vista statica di un sistema (struttura interna)
in termini di classi e relative relazioni. Sebbene presentino diverse analogie
con i diagrammi per l’analisi dei dati (Entità-Relazioni), bisogna
ricordare che le classi non si limitano a visualizzare la struttura delle
informazioni, bensì forniscono anche la descrizione del comportamento.
L’insieme
dei “class diagram” (corredato da ulteriori diagrammi che saranno introdotti
nel prossimo articolo) permette lo sviluppo parallelo dei sistemi in quanto
forniscono (o dovrebbero) tutti i dettagli necessari al processo di sviluppo:
un diagramma delle classi dovrebbe poter essere implementato direttamente
con un qualsiasi linguaggio di programmazione O. O.
A
dir il vero non è completamente corretto asserire che sia sempre
possibile disegnare diagrammi delle classi in modo indipendente dal linguaggio
di programmazione O. O. prescelto: esiste una, seppure minima, dipendenza.
Si è consapevoli che quanto testé affermato ingenererà,
per così dire, l’irrigidimento dei lettori “puristi” della totale
indipendenza dei processi di analisi dai dettagli implementativi, però
le cose stanno così, se vi pare! Per esempio (classico caso del
tutto fortuito), se si decidesse di utilizzare Java come linguaggio di
sviluppo, sarebbe auspicabile evitare di introdurre, nei diagrammi delle
classi, relazioni di ereditarietà multiple: Java semplicemente non
le prevede (eventualmente, dovrebbero essere sostituite con un appropriata
versione del “pattern” delegazione vedi [8]).
I
“class diagram” sono altresì necessari per fornire le basi necessarie
alla progettazione di altri diagrammi che focalizzano aspetti diversi del
sistema (tipicamente dinamici).
Patterns proposti
: Object Pool
La
selezione del pattern denominato “Object Pool” (appartenente alla categoria
denominata “Creational Partner”) non è assolutamente casuale ed
è dedicata a tutta una schiera di presunti programmatori Java (con
cui l’autore si è provato di recente) dediti al decadimento delle
prestazioni dei sistemi ed alla saturazione della memoria.
E’
probabilmente vero che Java non può essere annoverato tra i linguaggi
di programmazione intrinsecamente più efficienti (dal punto di vista
dei tempi di risposta) però è altresì vero che se
lo si utilizza creando oggetti, specie “pesanti”, per deferenziarli e poi
crearli nuovamente subito dopo, le prestazioni del sistema e la relativa
occupazione di memoria sono destinati a risentirne negativamente.
Non
va dimenticato che il famoso “Garbage Collector” è, in ultima analisi,
un “thread” a bassa priorità e come tale, se si “sprecano” cicli
CPU per istanziare oggetti da distruggere e ricreare, difficilmente il
“Garbage Collector” riuscirà a strappare qualche ciclo macchina
per rilasciare blocchi di memoria.
Pertanto,
invece di tentare di agire sulla priorità dei rimanenti “thread”,
forse potrebbe essere opportuno cercare di “riciclare” oggetti più
“pesanti”.
Proprio
questo è l’obiettivo del presente “pattern”: gestire efficientemente
l’utilizzo di oggetti “costosi”; costosi perché richiedono diverso
tempo per essere istanziati o perché danno luogo ad un’occupazione
di memoria non trascurabile.
L’esempio
classico a cui si ricorre per illustrare l’”Object Pool Pattern” è
costituito dalle librerie dedicate all’interconnessione con il “database
server” in un sistema operante in rete.
Tipicamente
il “DB server” è in grado di erogare servizi aventi come dominio
la base dati (reperimento informazioni, aggiornamento tabelle, …). Lo scambio
dei dati con i vari “client” avviene attraverso una connessione di rete,
dalla quale riceve le richieste di fornitura di servizi ed alla quale invia
gli eventuali risultati prodotti.
In
questo contesto un progettista non molto esperto (e non solo!) sarebbe
portato a generare una nuova connessione con il “Database server” in ogni
parte del sistema in cui ve ne sia bisogno.
Sebbene,
a prima vista, potrebbe sembrare la soluzione più semplice, si tratta
di un approccio assolutamente errato poiché:
-
il tempo
necessario per stabilire nuove connessioni non è trascurabile;
-
le prestazioni
del “DB Server” sono (ovviamente) inversamente proporzionali al numero
delle connessioni;
-
determinate
piattaforme limitano il numero massimo di connessioni attive nello stesso
intervallo di tempo.
Una soluzione
accettabile del problema dovrebbe pertanto portare al riutilizzo degli
oggetti al fine sia di limitare le connessioni al database, sia di aumentare
le prestazioni generali del sistema e, soprattutto di evitare il famoso
errore “Out of memory” che sembrava dovesse essere definitivamente confinato
nel dimenticatoio.
Architettura
L’architettura
proposta dall’”Object Pool Pattern” prevede un diagramma delle classi del
tipo di quello riportato in figura 1, che tra l’altro fornisce diversi
spunti per approfondire le regole dell’UML.
|
Figura
1. Pattern Object pool.
In
primo luogo, la classe “Connection” identifica un opportuno database per
mezzo di una stringa (nel caso generale potrebbe indetificare una qualsiasi
risorsa “costosa”), ne contiene le informazioni necessarie, però
non realizza alcuna connessione fisica con il database server. In qualche
modo, banalizzando, si potrebbe dire che ne rappresenta un “proxy”.
Quando
poi si rende necessaria un’intereazione fisica vera e propria, un oggetto
della classe “Connection” viene accoppiato (utilizza) ad un’opportuna istanza
della classe “ConnectionImpl”, come si può ben notare dalla molteplicità.
Il
nome dell’associazione è stato dotato di una freccia al fine di
agevolarne la lettura. La relazione di associazione è stata anch’essa
disegnata per mezzo di una freccia, ma in questo caso, indica che un oggetto
“Connetion” può “navigare” all’interno di un’istanza della classe
“ConnecionImpl” (accedere ai relativi metodi e/o attributi pubblici) mentre
non ha senso il viceversa. In termini implementativi, ciò equivale
a dire che la classe “Connection” possiede un attributo puntatore (pardon
un riferimento, in Java il termine puntatore è entrato in disuso)
ad un oggetto “ConnectionImpl”, mentre non accade il contrario.
Naturalmente,
la classe “ConnectionImpl” incapsula al proprio interno la connessione
fisica con il “server database”: il famoso oggetto pesante. Le relative
istanze vengono create solo quando si genera una reale esigenza: quando
non vi é alcun oggetto di questo “tipo” disponibile ed il numero
massimo di istanze previste non è stato ancora raggiunto.
La
classe “ConnectionPool” recita il ruolo del gestore delle varie connessioni
(così come indicato nel relativo ruolo: “manager” (scrityto in lettre
minuscole)), mentre le classi “Connection”, essendo gestiste, recitano
il ruolo di “client”. Dall’analisi delle molteplicità (0..n) si
può osservare che la classe “ConnectionPool” è in grado di
gestire un “pool” di risorse per ciascuna tipologia di connessione (più
“database server”), mentre ciascuna istanza della classe “Connection” può
essere gestita da una sola classe “ConnectionPool”. Naturalmente, nulla
vieta alla classe “manager” di gestire risorse tra loro eterogenee: connessioni
ai DB, a server remoti, ecc.
Dall’analisi
della struttura interna della classe “ConnectionPool”, si può notare
che si tratta di un “singleton” (anch’esso “pattern” appartenente alla
cosiddetta categoria “creational”). Esso è utilizzato ogniqualvolta
si voglia essere certi che, in ogni istante di tempo, esista una sola istanza
di una determinata classe. Caso tipico delle classi manager (appunto) o
comunque di quelle che si occupano di centralizzare l’accesso ad determinate
risorse. A tal fine i metodi costruttori sono dichiarati privati. La logica
conseguenze è che un oggetto della classe possa essere creato solo
dal suo interno sotto lo stretto controllo di un opportuno metodo; in questo
caso “getIstance()” (i programmatori C++ sono abituati a chiamarlo semplicemente
“istance()”). Si tratta di un metodo statico (e quindi riportato sottolineato),
che restituisce sempre lo stesso riferimento al medesimo oggetto. A tal
fine è necessario disporre anche di un attributo interno statico
che contenga il riferimento a sé stesso. Pertanto a seguito dell’invocazione
del metodo “getIstance()”, non fa altro che restituirne il valore all’esterno.
L’unica eccezione è costituita dalla prima invocazione; in tal caso
deve occuparsi di inizializzarne il valore con quello restituito dal costruttore
privato.
All’interno
dell’elenco dei metodi della classe “ConnectionPool”, sono stati introdotti
due stereotipi: “<<constructor>>” e “<<misc>>” al fine di ordinare
la relativa lista per funzionalità.
Tornando
alla classe “ConnectionPool”, si può notare che essa è costituita
da n classi “ConnectionImpl”, e che anche la relativa relazione di aggregazione
(vedi [8]) prevede una “navigabilità” abbastanza intuitiva. E’ infatti
logico che la prima possa accedere ai metodi e/o agli attributi pubblici
della seconda, come è altresì logico che il contrario non
abbia senso.
Quando
viene richiesta una nuova connessione la classe “ConnectionPool” verifica
la presenza di una già esistente inutilizzata, in caso affermativo
la alloca, in caso negativo verifica la possibilità di istanziarne
un nuovo oggetto.
L’utente
(“client” della libreria) accede unicamente alla classe “Connection” (si
tratta di una classica architettura a due strati), quando richiede l’allocazione
di una risorsa di connessione invoca (implicitamente) un metodo “aquireImpl”
della classe “ConnectionPool”, mentre quando ne ha terminato l’utilizzo
richiama il metodo “releaseImpl”.
Pattern Observer
L’osservatore
appartiene alla categoria dei “pattern” definiti “comportamentali” (“behavioral”).
Si tratta di un’architettura largamente utilizzata, molto famosa in ambiente
Java; basti pensare che è la struttura di riferimento del modello
di gestione degli eventi per delegazione (“Delegation Model Event”).
In
particolare esso consente di memorizzare dinamicamente delle dipendenze
tra oggetti, in modo tale che un cambiamento dello stato dell’oggetto “osservato”
venga opportunamente notificato agli oggetti “osservanti” tempestivamente
registrati.
Un
esempio concreto è ovviamente fornito da i controlli grafici (“widget”)
esistenti per il linguaggio Java a partire dal JDK1.1.x.
Comunque,
si supponga di dover realizzare un “Applet” che tra le sue funzionalità
preveda la visualizzazione dell’andamento temporale di determinati stock
di azioni opportunamente selezionati dall’utente. In particolare si supponga
di utilizzare i dati relativi a ciascuna azione per tracciare una serie
di diagrammi (diagramma cartesiano, istogramma, ecc.).
In
una situazione di questo tipo, risulta fondamentale, per le classi che
si occupano di tracciare i vari diagrammi, essere opportunamente avvisate
(notifica messaggio) al variare del valore di un’azione: ciò consente
di poter intraprendere opportuni provvedimenti (aggiornamento del diagramma).
Architettura
Il
“pattern” dell’osservatore prevede un’architettura come quella riportata
in figura 2. In particolare si ha una classe “osservata” (“OggettoConcreto”)
che si fa carico di notificare il cambiamento del proprio stato. A tal
fine estende una classe “Oggetto” che ha i metodi e gli attributi necessari
per memorizzare e rimuovere una determinata registrazione, e per notificare
l’avvenuto cambiamento di stato.
A
tal fine, mantiene traccia degli oggetti “registrati” per mezzo di un opportuno
vettore (ovviamente privato).
La
classe “oggetto”, per essere in grado di comunicare con le classi “osservatori”,
ne deve conoscere, almeno in parte, la relativa struttura interna, pertanto
tali classi devono possedere una “radice” comune, ossia implementare una
stessa ìnterfaccia o estendere una medesima classe, magari astratta.
Tale
base comune, nel diagramma di figura 2, è rappresentato dalla classe
astratta “Osservatore” che possiede un unico metodo pubblico “aggiorna()”,
invocato appunto dalla classe “Oggetto” per notificare l’avvenuto cambiamento
del suo stato.
Gli
osservatori concreti, oltre a ricevere le varie notifiche, dovrebbero essere
in grado di poter interagire con l’oggetto concreto, a tal fine vi è
un’apposita relazione di associazione tra gli oggetti osservatori e quelli
osservati.
Si
può notare che al fine di chiarire il funzionamento di opportuni
metodi (metodo “notifica()” della classe “Oggetto” e metodo “aggiorna()”
della classe “Osservatore concreto”), sono stati inserite delle apposite
note riportanti dettagli a carattere implementativo.
|
Figura
2 Pattern dell’osservatore.
|
Figura
3 Pattern dell’osservatore con particolare riferimento a Java
In
figura 3, è stata riportata una variante del “pattern” dell’osservatore
con particolare riferimento alle API Java.
In
esso compaiono degli elementi nuovi. Si può osservare infatti che
le interfaccie non sono state disegnate per mezzo dello stereotipo rappresentato
da un cerchio, bensì attraverso la classica nomenclatura corredata
da una relazione di estensione con la linea tratteggiata (in effetti si
tratta di un’implementazione). Spesso si preferisce tale nomenclatura per
poter visualizzazione l’elenco dei relativi metodi. Come si può
ancora notare, la classe dell’osservatore (“Observable”) implementa l’interfaccia
“IObservable”, ma demanda (delega) esternamente l’implementazione dei metodi
“addObserver()” e “removeObserver()”, ad una classe denominata “Multicaster”.
Vi suggerisce nulla?
Conclusioni
Il
presente articolo costituisce una sorta di “bis” nella scaletta inizialmente
pensata per l’illustrazione dell’UML.
In
effetti è stato introdotto al fine di soddisfare le richieste di
tutti coloro che avevano avvertito la necessità di approfondire
ulteriormente i “class diagramm”, soprattutto da un punto di vista più
pratico. Si tratta di una classica modifica in corso d’opera.
Si
è inoltre deciso di ricorrere ai “pattern” sia per presentare dei
diagrammi ben consolidati, eleganti e che pertanto ben si prestavano a
fini “didattici”, sia per non tediare eccessivamente tutti coloro che non
sentivano la necessità di ulteriori chiarimenti a riguardo.
I
vari digrammi sono stati selezionati anche al fine di fornire ulteriori
informazioni relative alle regole UML.
Bibliografia
[1]
"The Unified Modeling Language User Guide" di Grady Booch, James
Rumbaugh, Ivar Jacobson Ed. Addison Wesley
Questo
libro vanta il primato di essere stato scritto dai progettisti originali
del linguaggio, sebbene sia conosciuto come “Grady’s book”, dal nome del
suo primo autore. La mancanza di una sua copia (magari autografata!) può
generare giudizi di scarsa professionalità per coloro che operano
nel settore della progettazione O.O... Ciò però, costituisce
una condizione necessaria, ma non sufficiente, in quanto bisogna, assolutamente,
aggiungere una copia del [3] e una del [4]. Sono soldi spesi bene! Forse,
il limite del libro, se proprio se ne vuole trovare uno, è quello
di essere poco accessibile ad un pubblico non espertissimo di progettazione
O.O. Un altro piccolo inconveniente è che, probabilmente, taluni
esempi possano sembrare di carattere accademico: poco rispondenti alle
problematiche del mondo reale.
[2]
"UML Toolkit" di Hans-Erik Eriksson, Magnus Penker Ed. Wiley
Questo
libro si fa particolarmente apprezzare per via del taglio pratico dello
stesso. Illustra in modo semplice il linguaggio, attraverso numerosi esempi,
limitando le digressioni di carattere teorico. Si suppone infatti, che
coloro che si occupano di progettazione O.O. abbiano una certa familiarità
con i relativi concetti. Chissà perché si ha la sensazione
che non sia sempre così! Naturalmente, studiare libri che illustrano
gli aspetti teorici dell’informatica, è sempre qualcosa più
che auspicabile. È altresì vero però, che coloro che
non hanno tempo da perdere, per la lettura di concetti arcinoti, gradiscono
arrivare rapidamente al nocciolo. Ciò spesso equivale a strappare
alle nottate lavorative ore preziose per il sonno.
[3]
"The Unified Modeling Language Reference Manual" di Grady Booch, James
Rumbaugh, Ivar Jacobson
Ed.
Addison Wesley
Il
commento da riportare per questo libro, noto per come “Rumbaugh’s book”,
è sostanzialmente equivalente a quanto riportato per il primo. Esso
però offre un livello di difficoltà decisamente inferiore,
e pertanto dovrebbe essere più accessibile. Come suggerisce il nome,
si tratta di un manuale, per cui ne rispetta la tipica struttura.
[4]
"Design Patterns: Elements of Reusable Object-Oriented Software" di Erich
Gamma, Richard Helm, Ralph Johnson, John Vlissides, Grady Booch Ed. Addison
Wesley.
Si
tratta di un ottimo libro che bisogna assolutamente avere se si vuole lavorare
nell’ambito della progettazione O.O. . I “pattern” riportati, forniscono
un ottimo ausilio al processo di disegno del software. L’utilizzo del libro
contribuisce ad aumentare la produttività, fornisce soluzioni chiare,
efficienti e molto eleganti. La fase di disegno del software, spesso, si
riduce ad individuare e personalizzare i “pattern” che risolvono la problematica
specifica. Si è di fronte ad una nuova frontiera della progettazione
O.O.: il riutilizzo di parti del progetto. L’unica pecca imputabile, è
che i vari diagrammi non sempre rispettano il formalismo UML.
[5]
www.omg.org il sito ufficiale del Object Managment Group.
[6]
"UML e lo sviluppo del software" - www.mokabyte.it, numero 34 (Ottobre
1999)
[7]
"Use Case: l’analisi dei requisiti secondo l’U.M.L." - www.mokabyte.it,
numero 35 (Novembre 1999)
[8]
"Diagrammi delle classi e degli oggetti: proiezione statica della Design
View." - www.mokabyte.it, numero 36 (Dicembre 1999)
|