MokaByte Numero  37  - Gennaio 2000
Proiezione statica 
della Design View
II Parte 
di
Luca
Vetti Tagliati
Esempi “celebri” di diagrammi delle classi.
Class diagram da un punto di vista pratico: esempi tratti dai “pattern”


Scopo del presente articolo è proseguire nel percorso di analisi della “Design View” (la seconda vista dello “Unified Modeling Language”), iniziato con l’articolo antecedente ([8]), fornendo, in particolare, una base pratica a quanto introdotto in precedenza. L’idea di fondo è fornire come esempi alcuni “pattern” ritenuti particolarmente utili, con l’intendo di generare un duplice beneficio: illustrazione pratica di “class diagramm” e presentazione di “pattern” di uso comune. Prima di riprendere il cammino si è ritenuto opportuno procedere con una breve ricapitolazione di alcuni dati (sperando che diventino informazioni) relative ai diagrammi delle classi

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é:

  1. il tempo necessario per stabilire nuove connessioni non è trascurabile;
  2. le prestazioni del “DB Server” sono (ovviamente) inversamente proporzionali al numero delle connessioni;
  3. 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)
 


MokaByte rivista web su Java
MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it