Dr. Carlo Pescio
Relazioni tra Oggetti

Pubblicato su Computer Programming No. 36


Miglioriamo la struttura del modello object oriented considerando le relazioni tra le classi

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.

Reader's Map
Molti visitatori che hanno letto
questo articolo hanno letto anche:

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.