|
Dr. Carlo Pescio Relazioni tra Oggetti |
Pubblicato su Computer Programming No. 36
Relazioni tra oggetti
Nella precedente puntata abbiamo presentato una tecnica generale
per passare da una descrizione informale di un sistema ad un modello
object oriented, con classi ed attributi ben evidenziati. Piu'
avanti torneremo, quando necessario, su alcuni dei passi descritti;
tuttavia, e' importante ora aggiungere un ingrediente fondamentale
al modello: la struttura.
Il modello che abbiamo ottenuto, infatti, evidenzia gli elementi
attivi del sistema (gli oggetti), la loro struttura interna, e
le operazioni che essi sono in grado di compiere. Tuttavia non
e' presente nel modello alcuna traccia delle relazioni
tra gli oggetti. Ad esempio, possiamo avere la classe Camion
e la classe Ruota ma non avere presente in modo esplicito
il fatto che un Camion ha delle ruote.
Inoltre, il modello potrebbe essere incompleto, a causa di un
documento preliminare di analisi non esaustivo; ad esempio, alcune
classi potrebbero essere non completamente specificate (mancanza
di dettaglio) oppure essere troppo specializzate (mancanza di
flessibilita', eccesso di dettaglio). Potremmo avere una classe
Camion ed una classe Automobile, ma non avere presente
in modo esplicito nel modello il fatto che Automobile e
Camion siano entrambi delle specializzazioni di un concetto
piu' astratto che potremmo definire Automezzo. Vedremo
che, mentre ricerchiamo la struttura nel modello, alcune di queste
lacune vengono automaticamente colmate.
All'interno dei modelli object oriented, due tipi di relazione
rivestono una rilevanza fondamentale: le relazioni di generalizzazione/specializzazione
(che sono modellate con l'ereditarieta', per cui rimando alla
puntata precedente) e le relazioni tra il "tutto" e
le sue "parti". Tali schemi di classificazione permeano
tutto il pensiero umano, e cio' spiega in buona parte la semplicita'
di adozione del modello object oriented, cosi' simile ai nostri
schemi mentali.
Aggiungere la struttura
Normalmente, la struttura non viene ricercata basandosi sull'analisi
logica del testo della descrizione informale: interviene invece
una interpretazione a livello puramente semantico del documento
di specifica dei requisiti. Tuttavia, possiamo nuovamente ricercare
nel testo alcuni spunti per le relazioni di generalizzazione/specializzazione
o tutto/parti: ad esempio, se abbiamo frasi come "un sensore
a perfusione e' un particolare tipo di sensore..." ci troviamo
evidentemente di fronte ad una relazione di generalizzazione/specializzazione.
Frasi del tipo "un dispositivo di input analogico dispone
di un certo numero di canali di ingresso" fanno subito pensare
ad una struttura tutto/parti. Gli elementi sintomatici nelle frasi
precedenti sono i verbi essere ed avere, con i loro
sinonimi, come possedere, disporre, e cosi' via.
Tali verbi indicano spesso una relazione anziche' una azione.
L'analisi del testo non e' pero' sufficiente a rivelare tutte
le relazioni esistenti, e dovremo quindi basarci anche sulla nostra
conoscenza del dominio del problema o sull'ausilio di esperti
del ramo.
Generalizzazione e Specializzazione
Iniziamo con la struttura di generalizzazione/specializzazione,
che verra' modellata attraverso l'ereditarieta'. Un buon metodo
e' esaminare ogni classe individuata ai passi precedenti, e cercare
di trovare la classe (o le classi) di cui costituisce una specializzazione.
Ad esempio, avendo la classe Camion nel nostro modello
object oriented, potremmo concludere che essa costituisce una
specializzazione di Automezzo. Ora ci dobbiamo porre alcune
importanti domande:
Ripetiamo ora lo stesso passaggio, su tutte le classi (incluse
quelle aggiunte al passo precedente) considerando invece le possibili
specializzazioni. Di nuovo, curiamo di evitare i duplicati e
di non introdurre classi non significative ai fini del modello.
Notiamo che ora e' in genere meglio essere restrittivi (ovvero
evitare di introdurre classi molto specializzate) anziche' rilassare
i requisiti.
Vi sono alcune importanti note sulle gerarchie di generalizzazione/specializzazione,
alcune delle quali verranno discusse piu' avanti, dopo aver considerato
le strutture tutto/parti. Vi sono comunque considerazioni di carattere
generale che meritano un commento immediato, in quanto possono
profondamente influire sulla qualita' del modello risultante:
Vale la pena di ritornare sul fatto che, mentre si modella la struttura del problema, si aggiungono talvolta anche nuove classi, piu' astratte di quelle presenti nel documento informale (generalizzazione), o che si trovano ai limiti del dominio del problema e che non erano state considerate prima (specializzazione). Vedremo che qualcosa di simile accade anche con le strutture tutto-parti.
La scelta dei nomi
Alcuni autori suggeriscono di usare i nomi "naturali"
per le classi derivate: nell'esempio precedente, Automezzo
e Camion; altri autori suggeriscono di essere piu' sistematici
ed usare sempre la classe base come prefisso del nome della classe
derivata, ovvero Automezzo-Camion, anziche' Camion.
In gran parte, si tratta di una questione di gusto personale,
posto che si raggiunga la necessaria chiarezza; l'unica raccomandazione
e' di essere sempre consistenti nell'assegnazione dei nomi (questa
raccomandazione si estende anche al design ed alla codifica).
Se usate i prefissi, usateli sempre.
Le relazioni tutto/parti
La migliore strategia per trovare le relazioni tutto/parti consiste
nell'esaminare le diverse classi, alla luce ovviamente della
nostra comprensione del dominio del problema, e considerare per
ognuna di esse le seguenti opzioni:
Puo' essere utile ritornare sulla differenza tra relazioni insieme-parti,
contenitore-contenuto, e collezione-membri:
Una relazione insieme-parti modella un oggetto costituito aggregando
oggetti eterogenei, normalmente in numero fisso. Ad esempio, una
automobile e' costituita da un motore, quattro ruote, e cosi'
via.
Una relazione contenitore-contenuto si distingue dalla precedente
in quanto gli oggetti contenuti non sono parte integrante dell'oggetto
contenitore. Ad esempio, un armadio contiene dei vestiti, ma i
vestiti non sono considerati parte dell'armadio. Spesso nella
relazione contenitore-contenuto non esiste un numero fissato a
priori di oggetti contenuti.
Una relazione collezione-membri si stabilisce quando abbiamo un
insieme di oggetti omogenei che hanno rilevanza non solo come
singoli oggetti, ma anche se intesi nella loro globalita'. Ad
esempio, un insieme di impiegati puo' formare un team di lavoro.
Osserviamo che in genere la collezione e' una entita' astratta,
intesa piu' che altro a rappresentare in modo estensionale (ovvero
per enumerazione) una proprieta' dei suoi membri.
Quando si individua una relazione tutto-parti, puo' essere interessante
estendere il modello per considerare alcune classi piu' marginali,
che sono concettualmente parte di una relazione, anche se assenti
nel documento di specifica. Spesso, in fase di verifica con il
cliente, ci si accorge che tali classi erano "implicite"
nella sua descrizione, o che modellano comunque attivita' che
il sistema potra' essere chiamato a svolgere in futuro.
Nuovamente, attenzione a non elevare a dignita' di classe un singolo
attributo: se non vi sono operazioni significative che una "parte"
deve svolgere, allora puo' semplicemente essere un attributo del
"tutto", anziche' partecipare ad una relazione tutto-parti.
Quando si definisce una relazione tutto/parti, e' sempre importante
chiedersi in che misura la parte contribuisce al tutto, e quantificare
esattamente tale contributo. Ad esempio, una automobile ha esattamente
un motore; viceversa, nel nostro dominio, un motore potrebbe essere
legato al piu' ad una automobile ma esistere anche come entita'
a se' stante (ad esempio, come ricambio). In genere, abbiamo diverse
possibilita', sia per quanto riguarda il "tutto" che
per quanto riguarda le "parti", ed in entrambi i casi
dovremo specificare il corrispondente range. La notazione usuale
consiste in un singolo numero se alla relazione partecipano sempre
un numero noto e prefissato di oggetti, od un range se il numero
di oggetti puo' variare; spesso si utilizzano le variabili n ed
m per intendere numeri interi, maggiori od uguali a zero, non
noti a priori.
Ambiguita'
Talvolta non e' semplice decidere se una relazione e' di tipo
generalizzazione/specializzazione o di tipo tutto/parti. Chi programma
in un linguaggio object oriented si sara' trovato di fronte a
problemi analoghi, dovendo decidere se una classe va derivata
da un'altra (solitamente, per ereditarne l'implementazione e non
l'interfaccia) o se la potenziale classe base va invece inclusa
come membro della potenziale classe derivata. Per portare un esempio
semplice, ci si potrebbe chiedere se ha piu' senso derivare Televisore
da Tubo catodico (quindi, asserire che un televisore e'
un tubo catodico "piu' specializzato") o dire che un
Televisore possiede un Tubo catodico. Stroustrup
[3] suggerisce di usare una tecnica semplice ma effettivamente
molto utile, soprattutto se arricchita da alcune considerazioni.
In caso di ambiguita', chiedetevi "puo' averne piu' di uno?";
se puo', allora usate la struttura tutto-parti. Ovviamente la
tecnica in se' e' inadeguata, ed infatti a meno di ipotizzare
un televisore con piu' schermi (cosa peraltro possibile ma non
molto realistica) porterebbe a derivare Televisore da Tubo
catodico: occorre usare un metodo piu' sottile. Se la tecnica
precedente elimina la derivazione, non e' necessario proseguire
oltre; altrimenti, occorre considerare gli oggetti allo stesso
"livello concettuale" della potenziale classe base:
rimanendo all'esempio citato, potremmo dire che nel sistema in
esame le classi Altoparlante e Sintonizzatore sono
allo stesso livello concettuale della classe Tubo catodico.
A questo punto, se ne abbiamo molte, probabilmente ci troviamo
di fronte ad una struttura insieme-parti, e quindi senza ereditarieta'.
Altrimenti, applichiamo la regola del "puo' averne piu' di
uno?" alle classi di stesso livello; in questo caso, ad esempio,
un televisore puo' avere piu' altoparlanti. Essendo l'altoparlante
allo stesso livello concettuale del tubo catodico, siamo autorizzati
a considerare entrambi come elementi contenuti anziche' classi
base.
Associazione: relazione, classe o metodo?
Chi conosce il modello entita'-relazione (spesso usato come modello
concettuale delle basi di dati, vedi [4] e [5]) sa che le relazioni
esistenti tra le classi non si esauriscono con la generalizzazione/specializzazione
e con il tutto/parti. Esistono altri tipi di relazione, che possiamo
piu' in generale chiamare "di associazione". Ad esempio,
un impiegato "lavora" in un certo ufficio, oppure un
cittadino "vota" per un certo candidato. Come vengono
modellate tali relazioni all'interno del paradigma object oriented?
Esistono in realta' due scuole di pensiero.
Alcuni, come Booch, ritengono che tali relazioni vadano modellate
esattamente come un accoppiamento tra le classi che vi partecipano,
come nel modello entita'-relazione. La relazione ha un nome, e
per ogni classe che partecipa alla relazione occorre specificare
i vincoli di cardinalita'.
Altri, come Coad e Yourdon, ritengono che tali relazioni vadano
ricondotte ad elementi primitivi del paradigma object oriented,
quindi ad una delle relazioni viste in precedenza o ad altri concetti
come le classi o i metodi. Vediamo un esempio: nel caso del cittadino
che "vota" per un certo candidato, "Vota"
potrebbe essere modellato come un metodo della classe cittadino
(magari con alcuni parametri, come la data) che restituisce un
candidato. Oppure come una classe "Voto", contenente
due membri, un cittadino ed un candidato.
Perche' tale distinzione? Al di la' di motivi storici, di spinte
minimaliste o di unificazione dei vari paradigmi, scegliere un
approccio piuttosto che un altro ha una importante conseguenza
sul design e sulla codifica. Ricordiamo infatti che in molti linguaggi
di programmazione orientati agli oggetti non esistono primitive
per esprimere le relazioni di associazione; cio' significa che
in molti casi, in fase di implementazione, un modello contenente
relazioni di associazione dovra' essere rivisto ed adattato al
linguaggio stesso. Se invece si utilizza, ad esempio, un linguaggio
per database orientato agli oggetti, si ha normalmente la possibilita'
di esprimere direttamente le relazioni di associazione.
Questo e' un esempio di quanto sia importante mantenere la consistenza
tra analisi, design e codifica, e di quanto siano fragili le pretese
di eseguire l'analisi (o il design) dimenticando totalmente le
successive fasi. Cercate di avere sempre presenti i passi successivi,
quando dovete operare delle scelte, senza tuttavia lasciarvi influenzare
in modo eccessivo; non e' semplice, ma e' spesso fondamentale
per la buona riuscita dei progetti.
Quando fermarsi
Mentre modelliamo le strutture, aggiungiamo spesso nuove classi,
sia considerando relazioni di generalizzazione/specializzazione
che tutto/parti o di associazione. A loro volta, queste classi
vanno analizzate per trovare altre possibili relazioni e cosi'
via. E' lecito chiedersi quando dobbiamo fermarci, o in altri
termini quando il modello sara' completo. In genere, questo avviene
quando, dopo aver esaminato tutte le classi esistenti secondo
i criteri qui esposti, non e' possibile trovare nuove classi,
nuove relazioni, o nuovi attributi che siano rilevanti ai fini
del dominio del problema. In tal caso, abbiamo raggiunto un punto
fisso del processo di strutturazione, e siamo pronti per le
fasi successive.
Osserviamo che, come gia' detto in precedenza, se il dominio del
problema e' molto vasto conviene avere piu' modelli, a diversi
gradi di astrazione, per partizionare opportunamente il dominio
stesso. In questo caso, prestate sempre attenzione che la "dimensione
concettuale" delle classi presenti in un modello sia molto
vicina.
Considerazioni
Come sempre, anche in questo caso la spiegazione e' stata piuttosto
sequenziale: prima si cercano gli oggetti, poi gli attributi,
poi le azioni, poi le strutture di specializzazione/generalizzazione,
poi le strutture tutto-parti, eccetera. In realta', difficilmente
il lavoro di analisi procede in modo strettamente sequenziale;
non solo avremo da risolvere ambiguita', ridiscutere i dettagli
e cosi' via, ma parte della struttura risultera' evidente sin
dall'inizio, specialmente a mano a mano che l'esperienza aumenta.
I progettisti e gli analisti lavorano e pensano spesso a diversi
livelli di astrazione simultaneamente; tale comportamento e' tipico
degli esseri umani, che possono cogliere il dettaglio senza perdere
di vista l'insieme. Esistono anche interessanti studi in proposito,
dove ad esempio alcuni sviluppatori professionisti sono stati
filmati nel corso del loro lavoro [6]. Sarebbe pertanto futile
pretendere di ignorare i suggerimenti che la nostra conoscenza
del mondo ci offre, ed attendere fino ad un passo successivo prima
di introdurre qualche dettaglio strutturale. Cio' che e' importante
e' di rivedere sempre il risultato ottenuto alla luce delle tecniche
generali qui descritte, per evitare di introdurre accoppiamenti
troppo stretti, di dimenticare alcune relazioni, o di non esplorare
in modo adeguato i confini del dominio del problema.
Infine, considerate sempre l'opportunita' di riutilizzare un modello
esistente, estendendone i confini se necessario, o di aggiungere
qualche classe ai margini del modello affinche' sia piu' facilmente
riutilizzabile. Se pensate di ritrovarvi in futuro a lavorare
nello stesso dominio di analisi, cio' potrebbe essere molto vantaggioso.
Rivedete sempre anche i risultati di progetti precedenti, osservate
se esiste la possibilita' di far confluire piu' classi, o se introdurre
qualche classe base puo' portare alla fusione di piu' modelli.
Mantenere un vostro catalogo di classi, anche a livello di analisi
e di specifica, potra' ridurre in modo significativo lo sforzo
necessario per completare progetti futuri.
Rappresentazioni grafiche
Nella puntata precedente abbiamo introdotto una rappresentazione
a tabella per il modello object oriented. Con l'aggiunta delle
relazioni, una forma tabulare e' in genere troppo pesante e poco
chiara. Nella prossima puntata analizzeremo i piu' noti formalismi
grafici per la rappresentazione di modelli object oriented. Piu'
avanti, completeremo il discorso sull'analisi esponendo alcuni
criteri per la valutazione della qualita' e della completezza
del modello ottenuto.
Bibliografia
[1] P. Wegner: "Dimensions of Object-Based Language Design",
ACM OOPSLA 1987 Proceedings.
[2] P. Wegner: "The Object-Oriented Classification Paradigm",
MIT Press, 1987
[3] B. Stroustrup: "The C++ Programming Language - Second
Edition", Addison-Wesley, 1991; edizione italiana: "Il
Linguaggio C++, Seconda Edizione", Addison-Wesley Masson,
1993.
[4] P. Chen: "The Entity Relationship Model -- Toward a Unified
View of Data", ACM Transactions on Database Systems, Marzo
1976.
[5] Batini, Ceri, Navathe: "Conceptual Database Design: an
Entity-Relationship Approach", Addison-Wesley, 1991.
[6] B. Curtis: "...But You Have to Understand, This Isn't
the Way We Develop Software At Our Company", Microelectronics
and Computer Technology Corporation (Austin), Technical Report
No. STP-203-89, Maggio 1989
Biografia
Carlo Pescio (pescio@acm.org) svolge attività di consulenza in ambito internazionale nel campo
delle tecnologie Object Oriented. Ha svolto la funzione di Software Architect in grandi progetti per
importanti aziende europee e statunitensi. È incaricato della valutazione dei progetti dal
Direttorato Generale della Comunità Europea come Esperto nei settori di Telematica e Biomedicina.
È laureato in Scienze dell'Informazione ed è membro dell'ACM, dell'IEEE e della New York Academy
of Sciences.