Uno sguardo al modello di design Composite

L'altro giorno stavo ascoltando Car Talk della National Public Radio , una popolare trasmissione settimanale durante la quale i chiamanti fanno domande sui loro veicoli. Prima di ogni pausa del programma, i conduttori dello spettacolo chiedono ai chiamanti di comporre il numero 1-800-CAR-TALK, che corrisponde al numero 1-800-227-8255. Certo, il primo risulta molto più facile da ricordare rispetto al secondo, in parte perché le parole "CAR TALK" sono composte: due parole che rappresentano sette cifre. Gli esseri umani generalmente trovano più facile trattare con i materiali compositi, piuttosto che con i loro singoli componenti. Allo stesso modo, quando si sviluppa un software orientato agli oggetti, è spesso conveniente manipolare i compositi proprio come si manipolano i singoli componenti. Questa premessa rappresenta il principio fondamentale del modello di progettazione Composite,l'argomento di questo Java Design Patterns rata.

Il modello composito

Prima di immergerci nel pattern Composite, devo prima definire oggetti compositi: oggetti che contengono altri oggetti; ad esempio, un disegno può essere composto da primitive grafiche, come linee, cerchi, rettangoli, testo e così via.

Gli sviluppatori Java hanno bisogno del pattern Composite perché spesso dobbiamo manipolare i compositi esattamente nello stesso modo in cui manipoliamo gli oggetti primitivi. Ad esempio, le primitive grafiche come linee o testo devono essere disegnate, spostate e ridimensionate. Ma vogliamo anche eseguire la stessa operazione sui compositi, come i disegni, che sono composti da quelle primitive. Idealmente, vorremmo eseguire operazioni su oggetti primitivi e compositi esattamente nello stesso modo, senza distinguere tra i due. Se dobbiamo distinguere tra oggetti primitivi e composti per eseguire le stesse operazioni su questi due tipi di oggetti, il nostro codice diventerebbe più complesso e più difficile da implementare, mantenere ed estendere.

In Design Patterns , gli autori descrivono il pattern Composite in questo modo:

Componi gli oggetti in strutture ad albero per rappresentare gerarchie parzialmente intere. Composite consente ai clienti di trattare i singoli oggetti e le composizioni di oggetti in modo uniforme.

L'implementazione del pattern Composite è facile. Le classi composite estendono una classe base che rappresenta oggetti primitivi. La Figura 1 mostra un diagramma delle classi che illustra la struttura del pattern Composite.

Nel diagramma delle classi della Figura 1, ho usato i nomi delle classi dalla discussione sui modelli compositi di Design Pattern : Componentrappresenta una classe base (o forse un'interfaccia) per oggetti primitivi e Compositerappresenta una classe composta. Ad esempio, la Componentclasse potrebbe rappresentare una classe base per primitive grafiche, mentre la Compositeclasse potrebbe rappresentare una Drawingclasse. La Leafclasse della figura 1 rappresenta un oggetto primitivo concreto; ad esempio, una Lineclasse o una Textclasse. I metodi Operation1()e Operation2()rappresentano metodi specifici del dominio implementati dalle classi Componente Composite.

La Compositeclasse mantiene una raccolta di componenti. In genere, i Compositemetodi vengono implementati iterando su quella raccolta e richiamando il metodo appropriato per ciascuno Componentnella raccolta. Ad esempio, una Drawingclasse potrebbe implementare il suo draw()metodo in questo modo:

// Questo metodo è un metodo composito public void draw () {// Itera sui componenti per (int i = 0; i <getComponentCount (); ++ i) {// Ottieni un riferimento al componente e invoca il suo disegno metodo Component component = getComponent (i); component.draw (); }}

Per ogni metodo implementato nella Componentclasse, la Compositeclasse implementa un metodo con la stessa firma che itera sui componenti del composto, come illustrato dal draw()metodo sopra elencato.

La Compositeclasse estende la Componentclasse, quindi puoi passare un composto a un metodo che si aspetta un componente; ad esempio, considera il seguente metodo:

// Questo metodo è implementato in una classe non correlata alle // classi Component e Composite public void repaint (Component component) {// Il componente può essere un composto, ma poiché estende // la classe Component, questo metodo non deve // distingue tra componenti e compositi component.draw (); }

Il metodo precedente viene passato a un componente, un componente semplice o un composito, quindi richiama il draw()metodo di quel componente . Poiché la Compositeclasse si estende Component, il repaint()metodo non ha bisogno di distinguere tra componenti e compositi: richiama semplicemente il draw()metodo per il componente (o composito).

Il diagramma di classe del pattern composito della Figura 1 illustra un problema con il pattern: è necessario distinguere tra componenti e compositi quando si fa riferimento a Component, ed è necessario richiamare un metodo specifico del composito, ad esempio addComponent(). In genere si soddisfa tale requisito aggiungendo un metodo, ad esempio isComposite(), alla Componentclasse. Quel metodo restituisce i falsecomponenti e viene sovrascritto nella Compositeclasse da restituire true. Inoltre, devi anche Componenteseguire il cast del riferimento a Compositeun'istanza, in questo modo:

... if (component.isComposite ()) {Composite composite = (Composite) component; composite.addComponent (someComponentThatCouldBeAComposite); } ...

Si noti che al addComponent()metodo viene passato un Componentriferimento, che può essere un componente primitivo o un composto. Poiché quel componente può essere un composto, è possibile comporre i componenti in una struttura ad albero, come indicato dalla citata citazione di Design Patterns .

La Figura 2 mostra un'implementazione alternativa del pattern Composite.

Se si implementa il modello composito della figura 2, non è necessario distinguere tra componenti e compositi e non è necessario eseguire il cast di un Componentriferimento a Compositeun'istanza. Quindi il frammento di codice sopra elencato si riduce a una singola riga:

... component.addComponent (someComponentThatCouldBeAComposite); ...

Ma se il Componentriferimento nel frammento di codice precedente non fa riferimento a a Composite, cosa dovrebbe addComponent()fare? Questo è uno dei principali punti di conflitto con l'implementazione del pattern composito della figura 2. Poiché i componenti primitivi non contengono altri componenti, l'aggiunta di un componente a un altro componente non ha senso, quindi il Component.addComponent()metodo può fallire silenziosamente o generare un'eccezione. In genere, l'aggiunta di un componente a un altro componente primitivo è considerato un errore, quindi il lancio di un'eccezione è forse la migliore linea d'azione.

Quindi quale implementazione del pattern composito, quella nella Figura 1 o quella nella Figura 2, funziona meglio? Questo è sempre un argomento di grande dibattito tra gli implementatori di pattern compositi; Design Patterns preferisce l'implementazione della Figura 2 perché non è mai necessario distinguere tra componenti e contenitori e non è mai necessario eseguire un cast. Personalmente, preferisco l'implementazione della Figura 1, perché ho una forte avversione all'implementazione di metodi in una classe che non hanno senso per quel tipo di oggetto.

Ora che hai compreso il pattern Composite e come puoi implementarlo, esaminiamo un esempio di pattern Composite con il framework Apache Struts JavaServer Pages (JSP).

Il motivo composito e le piastrelle Struts

Il framework Apache Struts include una libreria di tag JSP, nota come Tiles, che consente di comporre una pagina Web da più JSP. Tiles è in realtà un'implementazione del pattern CompositeView J2EE (Java 2 Platform, Enterprise Edition), a sua volta basato sul pattern Composite Design Patterns . Prima di discutere la rilevanza del pattern Composite per la libreria di tag Tiles, esaminiamo prima la logica per Tiles e come lo usi. Se hai già familiarità con le tessere Struts, puoi scorrere le seguenti sezioni e iniziare a leggere "Usa il motivo composito con le tessere Struts".

Nota: è possibile leggere ulteriori informazioni sul pattern J2EE CompositeView nel mio articolo "Componenti dell'applicazione Web semplificati con la visualizzazione composita" ( JavaWorld, dicembre 2001).

I progettisti spesso costruiscono pagine Web con un insieme di aree distinte; ad esempio, la pagina Web della Figura 3 comprende una barra laterale, un'intestazione, un'area del contenuto e un piè di pagina.

I siti Web spesso includono più pagine Web con layout identici, come il layout della barra laterale / intestazione / contenuto / piè di pagina della Figura 3. Struts Tiles ti consente di riutilizzare sia il contenuto che il layout tra più pagine web. Prima di discutere di questo riutilizzo, vediamo come il layout di Figura 3 viene tradizionalmente implementato solo con HTML.

Implementa layout complessi a mano

L'esempio 1 mostra come implementare la pagina Web della figura 3 con HTML:

Esempio 1. Un layout complesso implementato a mano

    Implementazione manuale di layout complessi <% - Una tabella espone tutto il contenuto di questa pagina -%>
   
Collegamenti

Casa

Prodotti

Download

fogli bianchi

Contattaci

Benvenuto in Sabreware, Inc.
Il contenuto specifico della pagina va qui

Grazie per esserti fermato!

Il JSP precedente ha due principali svantaggi: in primo luogo, il contenuto della pagina è incorporato nel JSP, quindi non è possibile riutilizzarne nessuno, anche se è probabile che la barra laterale, l'intestazione e il piè di pagina siano gli stessi in molte pagine Web. In secondo luogo, anche il layout della pagina è incorporato in quel JSP, quindi allo stesso modo non è possibile riutilizzarlo anche se molte altre pagine Web nello stesso sito Web utilizzano lo stesso layout. Possiamo usare l' azione per rimediare al primo inconveniente, come discuterò in seguito.

Implementa layout complessi con JSP include

L'esempio 2 mostra un'implementazione della pagina Web della figura 3 che utilizza :