cin>>c8 s.r.l 1 A.A. 2004-2005 Generalità Uno dei concetti largamente adottati negli ultimi anni dai professionisti del software in fase di sviluppo è l’uso dei pattern. Queste soluzioni di design standard permettono di velocizzare la produzione di codice flessibile e, quando impiegate con criterio, permettono anche di aprire le porte alle evoluzione del prodotto. In sintesi i pattern sono: soluzioni riusabili per problemi ricorrenti strumenti concettuali che catturano la soluzione di una famiglia di problemi strumenti concettuali utili anche ad esprimere architetture vincenti di software 2 Composite È un pattern basato sul concetto di “rappresentazione parte–tutto“, descrive cioè come utilizzare una composizione ricorsiva in modo tale che i client non siano costretti a distinguere oggetti primitivi da quelli complessi. Come si può vedere dal diagramma in questione, il punto chiave è definire una classe astratta che rappresenti sia gli oggetti primitivi che i contenitori, e la tecnica di codifica per ottenere questo risultato è utilizzare una combinazione di ereditarietà e di 3 contenimento. Composite La classe Component dichiara sia operazioni tipiche delle primitive (operation()) che quelle del contenitore, come ad esempio delle operazioni per accedere e gestire gli oggetti contenuti (children). Per l'oggetto contenitore (Composite), l'operazione non verrà implementata direttamente ma verrà delegata a tutti gli oggetti che lo compongono. Se per esempio il Composite mantenesse un vettore con tutti i children, lo scorrerebbe chiamando su ognuno di essi il metodo operation(). Ovviamente un Composite può essere composto da altri Composite, perchè essi risultano essere comunque dei Component generici. Da qui deriva il nome di "Composizione Ricorsiva", che è un alias per il pattern in questione. 4 Partecipanti Component - Dichiara l’interfaccia per gli oggetti che fanno parte della composizione e per l’accesso e la gestione dei suoi componenti figli Leaf - Rappresenta gli oggetti che non possono avere figli - Definisce il comportamento degli oggetti primitivi della composizione Composite - Definisce il comportamento per i componenti che hanno figli - Memorizza i componenti figli - Implementa le operazioni correlate ai figli definite dall’interfaccia Component Client - Manipola gli oggetti della composizione utilizzando 5 l’interfaccia Component Implementazione Il Riferimento esplicito fra genitori e figli può semplificare molto l'attraversamento e la gestione di una struttura Composite. Di solito si definisce il reference a livello della classe Component, in modo che sia Leaf che Composite possano ereditare la referenza e le operazioni che lo gestiscono. Il modo più facile per assicurarsi la corrispondenza tra padre e figlio è fare in modo che si modifichi il reference ad un genitore solo quando esso viene aggiunto o rimosso da un Composite. Implementando questa funzionalità direttamente nei metodi add() e remove() della classe Component questa regola sarà automaticamente rispettata. 6 Implementazione Condivisione di componenti: è spesso utile condividere i componenti, ad esempio per ridurre l'occupazione di memoria. Mantenere più riferimenti da componenti a genitori può complicare notevolmente il codice. Una possibile soluzione è quella di usare il Flyweight pattern. 7 Implementazione Massimizzazione dell'interfaccia Component: uno degli obiettivi primari è quello di rendere i clients ignari della specifica classe (Leaf o Composite) che stanno usando. Chiaramente la classe Component dovrà implementare tutte le operazioni, sia quelle specifiche dell'una, che quelle specifiche dell'altra. La classe Component di solito provvederà alle implementazioni di default per questi metodi, lasciando il compito a Leaf e Composite di effettuare l'override di quelli a loro specifici. 8 Implementazione Dichiarazione delle operazioni di gestione "figli":è difficile dare un'interpretazione dei metodi add() e remove() per degli oggetti Leaf. L'approccio migliore è quello di cercare di rendere questi metodi meno dannosi possibile quando applicati ad un oggetto Leaf. E' vero che questi potrebbero semplicemente non fare nessuna operazione, ma non si tiene in considerazione il fatto che se nel codice c'è un invocazione del metodo add() o remove() su un oggetto Leaf vuol dire che da qualche parte c'è probabilmente un bug. E' buona pratica dunque fare in modo che add() e remove() falliscano (sollevando ad esempio un'eccezione) quando invocati su un oggetto Leaf. 9 Esempio public abstract class Component { public String name; public Component(String aName) { name = aName; } public abstract void printName(); public void add(Component c) throws LeafException { if (this instanceof LeafInterface) { throw new LeafException("Metodo non supportato"); } … … … … 10 Pro Definisce gerarchie di classi costituite da oggetti primitivi e composti. Gli oggetti primitivi possono essere composti per formare oggetti più complessi che a loro volta possono essere composti ricorsivamente. Semplifica il client. I client possono trattare strutture Composite e singoli oggetti in modo uniforme. I client generalmente non sanno se stanno operando con una foglia o con un componente composto. Rende più semplice l’aggiunta di nuove tipologie di componenti. Nuove sottoclassi potranno essere utilizzate automaticamente nelle strutture esistenti e operare con il codice dei client. 11 Contro Si può rendere il progetto troppo generico. Lo svantaggio di rendere più facile l’aggiunta di nuovi componenti è che ciò rende difficile limitare i componenti che fanno parte di una struttura composta. A volte è necessario che una struttura composta contenga solo determinati componenti. Per rendere ignari i client dal tipo di oggetti che verranno usati, la classe Component deve necessariamente implementare tutti i metodi specifici sia di Leaf che di Composite. Si potrebbe vedere questo fatto come una violazione del principio della coesione, che afferma che bisogna definire in una classe solo i metodi strettamente attinenti alla stessa. Lavorando con un pò di fantasia si può però dare un senso anche a metodi non coesivi: ad esempio si potrebbe definire la foglia come quel Composite composto solo da se stesso, in modo che metodi come getChild() acquistino comunque un senso "logico". In questo caso la classe Component potrebbe fornire una implementazione di default che torna null, coerente 12 con la definizione di Leaf vista sopra. Utilizzi noti Esempi di utilizzo del pattern Composite si trovano in quasi tutti i sistemi ad oggetti. - - - La classe View del framework Model/View/Controller in Smalltalk era un composite. Il framework RTL per la costruzione di compilatori in Smalltalk utilizza a fondo i pattern Composite. Si può immaginare l’uso di questo pattern come “gestione finanziaria”, nel quale un portafoglio è un aggregato di beni singoli. E’ possibile realizzare aggregazioni complesse di beni implementando un portafoglio come un composite conforme all’interfaccia di un bene singolo. 13 Pattern Correlati - - - I riferimenti al componente padre sono spesso usati nel pattern “ Chain of Responsibility ”.ù Il pattern Decorator è spesso usato con Composite. Il pattern Iterator può essere usato per attraversare le strutture Composite. 14 FINE 15