Introduzione
al problema
A volte il concetto
di gerarchia di classi sembra qualcosa di puramente teorico, nonostante
sempre utile dal punto di vista pratico, relazionando oggetti che apparentemente
non sono relazionati.
Inoltre spesso,
molti di questi oggetti contengono al loro interno oggetti della stessa
gerarchia (hanno una sorta di struttura ricorsiva dal punto di vista del
tipo di oggetti, anche se a run time si tratta di oggetti di classi derivate).
Ovviamente tali
oggetti devono essere gestiti in modo diverso, ma questo non è un
problema grazie al meccanismo del polimorfismo, tipico della programmazione
ad oggetti e quindi di Java.
Si hanno quindi
in questi casi dei container di altri oggetti che a loro volta possono
essere container, e così via ricorsivamente. Il punto fondamentale
di queste situazioni è NON trattare differentemente container ed
oggetti contenuti, proprio a causa del nesting implicito che si viene a
formarsi, o può venirsi a formare grazie al riuso (che nella programmazione
ad oggetti è un key concept, nonché, spesso, una giustificazione
dell'utilizzo di un tale paradigma). La distinzione fra questi tipi di
oggetti appesantirebbe il codice inutilmente e sarebbe innaturale, in quanto
intuitivamente dovrebbero essere trattati allo stesso modo (o almeno questo
appare agli occhi dell'utente).
Un esempio classico
di questa situazione può essere ritrovato in un editor grafico in
cui si hanno oggetti semplici come linee e punti, ed altri oggetti geometrici
come rettangoli, cerchi, che possono contenere al loro interno (raggruppare)
altri oggetti semplici, o a loro volta altri oggetti più complessi;
questi ultimi possono essere considerati quindi oggetti composti.
Un altro esempio
si può trovare in molti framework per la programmazione a finestre,
in cui si ha il concetto astratto di oggetto grafico (o finestra o vista),
che al suo interno può contenere altri oggetti grafici (caselle
di testo, label, bottoni), ma anche oggetti composti (cornici di raggruppamento);
il nesting, anche se spesso si ferma la primo livello, concettualmente
(e giustamente) deve essere illimitato.
L'esempio classico
di [GOF95] è quello della struttura
di un documento di un word processor: il documento può essere suddiviso
in colonne, suddivise in righe, suddivisi in caratteri, ma si possono anche
avere delle cornici, che a loro volta possono contenere colonne, e così
via; inoltre un word processor che si rispetti deve permettere di includere
oggetti grafici, e quindi il quadro si complica ulteriormente richiedendo
una soluzione apposita, ben studiata, ed ovviamente riutilizzabile e facilmente
estendibile.
Il pattern
Composite
La chiave della
soluzione, per quanto appena detto, sta proprio nel non considerare differenti
oggetti contenitori ed oggetti contenuti: cioè creare una classe
astratta che rappresenti entrambi questi tipi di oggetto. Inoltre l'oggetto
composto dovrà fornire metodi che siano applicabili (ricorsivamente
e trasparentemente) a tutti gli oggetti contenuti; le operazioni saranno
quindi propagate a tutti gli oggetti contenuti in un contenitore.
Vediamo in modo
più formale i partecipanti a questo pattern:
-
Componente:
definisce proprio la classe astratta appena citata ed la chiave della soluzione
che stiamo cercando.
-
foglia: rappresenta
un oggetto semplice (eventualmente contenuto in un oggetto composto) e
che non contiene a sua volta nessun altro oggetto.
-
composite:
è un componente che al proprio interno contiene altri componenti
(che quindi potranno essere o foglie o ricorsivamente oggetti composti).
Ovviamente il tutto
è trasparente al client di questi oggetti che manipolerà
ed utilizzerà tali oggetti come componenti e non, salvo situazioni
particolare, come, distintamente, foglie e oggetti composti. La struttura
è rappresentata come sempre in UML nella seguente figura:
si può
assimilare questa situazione ad una struttura ad albero in cui le foglie
rappresentano appunto il loro omonimo in tale struttura (non sono state
chiamate così per caso), ed i composite rappresentano nodi
con figli (che apparterranno, ricorsivamente alla classe componente).
Conclusioni
Il pattern composite
è senz'altro fra i più utilizzati quando si scrivono dei
framework o comunque si ha a che fare con strutture ricorsive più
complesse di liste o anche alberi.
Ovviamente tale
pattern si può ritrovare più o meno mascherato insieme ad
altri pattern, ma questa si sa è una caratteristica di queste metodologie
di design e sviluppo.
Notare inoltre,
che nonostante si tratti di una soluzione molto potente ed già utilizzata
in passato, la struttura è relativamente semplice, ma questo, come
sempre, non deve spingere ad utilizzare il pattern senza prima avere chiare
le varie caratteristiche che accomunano tutti gli elementi dei vari componenti;
anzi dovrebbe spingere a fare uno studio attento di quelle caratteristiche
che da sole rappresentano tutti i componenti, spostando eventuali altri,
meno caratteristici, sono nelle parti basse della gerarchi, senza che questo
ne limiti l'utilizzo trasparente.
Vi lascio adesso
ad Andrea Trentini, che vi mostrerà qualche esempio concreto di
implementazione di quanto visto finora.
Lorenzo
Bibliografia
[GOF95] E.Gamma,
R.Helm, R.Johnson, J.Vlissides, Design Patterns, Elements of Reusable
Object-Oriented Software, 1995, Addison-Wesley.
|