MokaByte Numero 22 - Settembre 1998
 
 
 
 
 
 
 

 di 
Andrea Trentini

L'UML e la OOP 

 

Strane sigle che celano le metodologie di sviluppo più all'avanguardia 

 


Un "racconto" sul formalismo per la progettazione a oggetti ormai più famoso e (si spera) più usato. Questo articolo vuole fornire una introduzione a UML (Unified Modeling Language) per invogliare gli sviluppatori che ancora non usano nessuno di questi formalismi (come anche il "vecchio" OMT o quello di Booch) a documentarsi e a iniziare il viaggio verso una produzione di software razionalizzata e professionale, corredata della giusta documentazione.



 

Scopo dell'articolo
Intendiamoci, dato che l'intenzione è quella di invogliare qualche programmatore ad avvicinarsi a questo mondo, il tono di questo articolo NON SARA' FORMALE. Nel senso che non tedierò i lettori con tecnicismi (che a volte non so neanch'io, ma non l'ho detto ;-) astrusi e noiosi, ma vorrei trasmettere i concetti base e una vista di insieme in modo da permettere una valutazione semplice: "a me programmatore interessa o no?" (io dico di sì). Parlerò delle caratteristiche fondamentali di un linguaggio come UML, i dettagli potrete trovarli nei vari libri (e siti) elencati in fondo al testo.
E' importante avvisare che per avvicinarsi a questi formalismi sono necessarie almeno delle conoscenze di base sui linguaggi a oggetti. UML, infatti, come gli altri formalismi che lo hanno preceduto, permette di rappresentare un sistema in termini di "oggetti", "relazioni fra oggetti", dinamiche (comportamenti) e stati. Per cui dovreste avere almeno un'idea di cosa siano termini come metodo, oggetto, classe, istanza, interfaccia, etc.
So che chi sta leggendo questo articolo è un javista (neologismo che indica un programmatore allergico al C++... scherzo, intendo un appassionato di Java) e dovrebbe pertanto possedere quei concetti forti della programmazione object oriented. Purtroppo nella mia decennale esperienza con Java (what? un premio a chi mi spiega perchè non è possibile) ho visto usare troppe volte questo linguaggio come se fosse un C semplificato (perchè ad esempio non ci sono i puntatori, anche se per qualcuno è una complicazione!!! pensate che ho conosciuto studenti che mi hanno detto di trovarsi molto più a loro agio col C che non con Java!!!) per non sollevare l'argomento delle basi OO.
Usare un formalismo come UML (o un altro di vostra preferenza) evita la produzione del classico spaguetti-code, che nel caso dei vecchi linguaggi poteva essere l'introduzione dei GOTO, mentre oggi, nei linguaggi a oggetti, vuol dire ad esempio avere "troppi reference" (magari incrociati), vuol dire creare oggetti senza criterio (di solito andrebbe usata, almeno per certe classi di oggettti, una classe istanziatrice centralizzata), vuol dire avere duplicazioni e ridondanze nei metodi e negli attributi e chi più ne ha più ne metta.
 

Perchè usare un formalismo, non posso scrivere direttamente il codice?
Nessuno vi vieta di scrivere direttamente codice. Svariati prodotti "visuali" (per molti ambienti di sviluppo considero questo termine in senso spregiativo) incoraggiano questo comportamento e forniscono spesso un class browser (un modulo che vi dovrebbe dare una visione di insieme, facendovi vedere tutte le classi che avete generato, con i loro metodi e attributi) che ha un difetto fondamentale: NON FA VEDERE TUTTE LE RELAZIONI FRA LE CLASSI. Si vedono (e anche raramente!) solo le relazioni di ereditarietà, cioè forse nemmeno quelle più importanti.
Invece è molto importante sapere quali sono TUTTE le relazioni fra le varie classi, quali sono i messaggi che si scambiano (cioè di fatto i metodi che vengono chiamati vicendevolmente), che transizioni (cambiamenti di stato) avvengono e perchè (in base a quali eventi). Un linguaggio come UML e simili (che in realtà sono solo modi grafici di rappresentare componenti) vi dà questa possibilità, ed è sicuramente più espressivo e potente di ua descrizione testuale ("vale più una bitmap di mille parole", antico proverbio cinese del 95 A.M., Ante Microsoft).
 

L'applicazione come sistema di "oggetti"
Un programma di oggi, a meno che non sia estremamente semplice, è di solito composto da molte parti: una volta (coi linguaggi procedurali) le componenti di un sistema venivano modellate in moduli (procedure/funzioni/algoritmi) e strutture dati, oggi si potrebbe parafrasare il titolo di un libro famoso parlando di "stato + comportamento = oggetto (o classe)". Si è semplicemente pensato di tenere le strutture dati "vicine" alle procedure/funzioni/algoritmi che devono manipolarle.
In Java (come in altri linguaggi OO) una classe (da cui poi si istanzieranno gli oggetti) è un "qualcosa" dotato di comportamento e stato allo stesso tempo, un agglomerato di metodi (procedure/funzioni/algoritmi) che lavorano su un insieme di dati che possono essere interni (e allora parliamo di attributi) o esterni (singoli attributi di altri oggetti o oggetti interi). E' ancora possibile ricondursi alla situazione precedente (di linguaggio procedurale), anche se non è generalmente consigliabile (i casi ci sono, ma sono rari), infatti se costruite una classe di soli attributi avete l'analogo di una struct del C, mentre se costruite una classe di soli metodi statici avete l'analogo di una libreria di procedure/funzioni/algoritmi. Un esempio di "libreria" esiste in Java nella classe java.lang.Math, ha solo metodi statici e non è altro che una libreria matematica.
 

Classi/Oggetti e relazioni (~ER) - i componenti del sistema
Nella progettazione a oggetti (ma in fondo anche nella vita quotidiana), quando si descrive un sistema complesso, spesso lo si fa descrivendo le sue sottoparti. In termini OO le sottoparti sono le classi, il sistema globale è composto dall'insieme delle classi collegate tramite RELAZIONI. Le relazioni sono il collante che permette alle classi (o meglio agli oggetti istanziati dalle classi) di interagire e di far sì che il Sistema (stavolta con la S maiuscola, ad indicare la globalità del nostro prodotto) si comporti come noi vogliamo che faccia (in realtà non succede mai, come direbbe Murphy ;-).
La relazione che tutti nominano (perchè è quella più evidente) parlando di linguaggi a oggetti è quella di ereditarietà: una classe che estende un'altra classe EREDITA una parte dei campi (attributi e metodi) e non ha bisogno di implementarli a sua volta, anche se può ridefinirli, ovviamente.
Nella figura che segue vediamo il primo esempio di diagramma UML per rappresentare la relazione di ereditarietà fra una ClassePadre e una ClasseFiglia, il simbolo usato è una specie di freccia con punta grossa, tra parentesi quadre ho messo la keyword che in Java descrive l'ereditarietà (extends).
 
 



Ogni classe viene disegnata con un rettangolo diviso in tre sezioni: in alto il nome, poi gli attributi (l'icona accanto al nome mostra la sua "accessibilità") e infine i metodi. Nell'esempio riportato abbiamo la ClasseFiglia che ridefinisce (overriding) il metodo_protetto() ereditato da ClassePadre.
Questo per quanto riguarda l'ereditarietà fra classi, ma prima ho affermato che esistono altre relazioni oltre a questa, ne è un esempio la classe Altra, che non eredita da nessuno, nè ha figli, eppure è collegata a ClassePadre da un segmento con degli attributi... Spesso infatti ci si dimentica che quella di ereditarietà non è l'unica relazione possibile fra classi/oggetti, ma prima di vedere che relazione ha Altra con ClassePadre vediamo qualcosa di più semplice.
Cominciamo con una relazione generica (nel senso di non specializzata), cioè quella di "utilizzo" o "conoscenza", si guardi questo frammento di codice:
 
class A{ 
   private B refToB; 

class B{ 
   private A refToA; 
}
Non possiamo forse dire che un'istanza della classe A conosce/usa un'istanza della classe B e viceversa ? (in questo caso volevo anche dare un micro-esempio di spaguetti-code in OO ;-)
Non c'è nessuna altra relazione esplicita tra le due classi (a parte il fatto che derivano entrambe da java.lang.Object se parliamo di Java). Abbiamo la situazione in cui ognuna delle due "possiede" un reference all'altra (e quindi può usarla). In UML si disegna così:


In generale questi diagrammi ci dicono solo che la classe A conosce/usa la classe B, NON specificano (salvo indicazioni esplicite) come verrà implementata effettivamente questa relazione. Normalmente l'mplementazione standard è quella di mettere un reference (dichiarando un attributo, "+refToB" è un esempio) in ognuna delle due classi. Infatti se facciamo generare il codice a Rose (il programma da cui sono tratte queste videate) l'output è proprio come lo avete visto nel tratto di codice poco sopra.
E se volessimo tornare al caso della classe Altra di prima? Vediamo che il segmento che la unisce a ClassePadre ha un nome ("contiene") e due altri attributi (invece delle etichette "+ref...") un numero ("1") e un asterisco ("*"). Che vorranno dire? Stiamo introducendo il concetto di "molteplicità" di una relazione, infatti non sempre è sufficiente dire che una classe è collegata ad un'altra (come nel caso A <-> B), ma si vuole anche specificare QUANTE istanze di una classe sono collegate a QUANTE dell'altra. Nel caso di Altra abbiamo che UNA istanza di ClassePadre "contiene" UN NUMERO A PIACERE di istanze di Altra. In questo caso l'implementazione standard è:
 
public class ClassePadre{ 
   ... 
   public Altra contiene[]; 
   ... 
}
La relazione con molteplicità ARBITRARIA (simboleggiata da "*") viene implementata genericamente con un array, ma nessuno vi vieta di usare un Vector o qualche altra classe magari appositamente costruita.
Esistono anche relazioni (ad esempio quelle di tipo MOLTI A MOLTI) che vengono generalmente implementate attraverso tabelle (nel senso di un qualche tipo di classe Table), come in figura:
 


In questo caso, essendo la classe Table a fare da tramite fra AA e BB, in AA e in BB non troveremo array come nei casi precedenti, ma solo un reference ad un'istanza di Table, cioè una implementazione simile a questa:
 
public class AA{ 
   ... 
   private Table tbl; 
   ... 
}
Un'istanza di AA, per raggiungere un'istanza di BB, dovrà chiamare tbl.getBB(qualcheparametro), solo a quel punto potrà eventualmente chiamare metodi di BB. Quella che abbiamo realizzato è una classe link, una classe che ci serve a specificare quale tipo di relazione intercorre fra AA e BB. Potremmo infatti decidere che l'accesso alla classe AA viene fatto per chiave mentre l'accesso alla BB per indice (a puro titolo di esempio), in questo caso potremmo mettere i metodi "AA getAA(String key)" e "BB getBB(int index)" nella classe Table.
 

Comportamento
Finora abbiamo parlato di classi e istanze in maniera statica, cioè le abbiamo descritte in termini di attributi (le informazioni che contengono), di metodi (cosa sanno fare) e di relazioni ("chi conosce chi"). Non abbiamo ancora detto nulla sul funzionamento del nostro prodotto, non abbiamo ancora introdotto il "fattore tempo" nei nostri disegni. Fino a che ci si limita a disegnare diagrammi di classi infatti, si sta descrivendo COME E' FATTO un sistema, NON COME SI COMPORTA.  Abbiamo detto "chi può chiamare chi", non abbiamo ancora mai detto QUANDO, o in che ordine. A queste esigenze rispondono i diagrammi delle interazioni e degli stati.  In questa figura vedete un micro esempio di diagramma delle interazioni. Il tempo (finalmente introdotto) scorre verso il basso, il diagramma si legge dall'alto verso il basso e da sinistra verso destra.


In questo caso abbiamo un'istanza di ClassePadre che chiama il metodo_pubblico() di un'istanza di ClasseFiglia, che risponde ritornando un qualche valore. La caratteristica importante di uno schema di questo tipo è che mette in SEQUENZA una serie di eventi, cioè mi dice in che ordine avvengono gli eventi. Non che questo sia un esempio completo, i diagrammi reali hanno di solito tante righe verticali (gli attori di una interazione) e parecchi eventi; guardate questo esempio (un classico), la telefonata:



Qui gli attori sono tre: chi chiama, il centralino e chi riceve. Si legge così: il chiamante alza la cornetta, il centralino risponde col tono di centrale, allora il chiamante digita il numero, la chiamata viene instradata, il centralino fa squillare il telefono del ricevente e (contemporaneità) fornisce il segnale di libero al chiamante, quando il chiamato alza a sua volta la cornetta il centralino smette di far squillare (e di dare il tono di libero) e permette la conversazione.
L'altro modo di rappresentare la sequenzialità è attraverso gli schemi a stati, qui abbiamo un esempio di un oggetto che potrebbe assomigliare ad un Thread (non è completo):
 

I vari stati sono rappresentati da rettangoli dai vertici arrotondati, le frecce indicano i passaggi di stato e le etichette delle frecce ci dicono qual'è l'evento scatenante la transizione. Di solito l'evento (almeno in Java) non è altro che la chiamata di un metodo.

Un esempio classico
Quando si introducono i concetti OO, un esempio classico è quello dei veicoli (l'altro è quello delle specie animali). Qui sotto, nella figura, c'è un abbozzo di diagramma delle classi per rappresentare i veicoli a motore. Una classe Veicolo (probabilmente astratta) che si specializza nei vari tipi concreti. Ogni veicolo a motore ha un solo motore (ma va!?), anche se nella realtà potrebbe averne di più (non pensiate di riuscire a rappresentare correttamente un sistema al primo colpo). Inoltre possiede da una ruota a infinite. Così va letto il diagramma sottostante, ammetterete che è una rappresentazione potente, o no?
 
 

Reverse engineering
Direi che merita un piccolo paragrafo anche questa feature di molti prodotti per la progettazione OO (come Rose o WithClass). In questi prodotti, col termine "reverse engineering", si intende indicare la possibilità di analizzare un insieme di sorgenti (in alcuni casi anche di file compilati, direttamente i .class) e di produrre gli schemi UML corrispondenti. E' l'operazione inversa della generazione di codice, è utilissima se si deve manipolare codice esistente accompagnato da una documentazione povera.
 

Prodotti e Riferimenti
Qui di seguito ho messo un po' di riferimenti interessanti, non pretendo di essere stato esaustivo, nè con la descrizione di UML (moooolto introduttiva!), nè con l'elenco dei siti. Se navigate un po' in Internet troverete moltissimo materiale sulla progettazione OO e sui Patterns, il problema sarà fermarsi...