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 show chiedono ai chiamanti di comporre 1-800-CAR-TALK, che corrisponde a 1-800-227-8255. Naturalmente, il primo si dimostra molto più facile da ricordare del secondo, in parte perché le parole “CAR TALK” sono un composto: due parole che rappresentano sette cifre. Gli esseri umani generalmente trovano più facile trattare con i composti, piuttosto che con i loro singoli componenti. Allo stesso modo, quando si sviluppa software orientato agli oggetti, è spesso conveniente manipolare i compositi proprio come si manipolano i singoli componenti. Questa premessa rappresenta il principio fondamentale del design pattern Composite, l’argomento di questa puntata di Java Design Patterns.
- Il pattern Composite
- Il pattern Composite e Struts Tiles
- Implementare a mano layout complessi
- Esempio 1. Un layout complesso implementato a mano
- Implementare layout complessi con JSP include
- Esempio 2. Un layout complesso implementato con JSP include
- Esempio 3. sidebar.jsp
- Esempio 4. header.jsp
- Esempio 5. content.jsp
- Esempio 6. footer.jsp
- Implementare layout complessi con Struts Tiles
- Esempio 7. Usare Struts Tiles per incapsulare il layout
- Esempio 8. WEB-INF/tiles-defs.xml
- Esempio 9. header-footer-sidebar-layout.jsp
- Utilizzare il pattern Composite con Struts Tiles
- Esempio 10. WEB-INF/tiles-defs.xml: Usa il modello Composite
- Esempio 11. sidebar-layout.jsp
- Esempio 12. flags.jsp
- Esempio 13. sidebar-links.jsp
- E’ tutto composito in questi giorni
- Homework
- Ricompito dell’ultima volta
- Esempio H1. flags.jsp
- Esempio H2. WEB-INF/web.xml (Estratto)
- Esempio H3. WEB-INF/struts-config.xml
- Esempio H4. WEB-INF/classes/actions/FlagAction.java
- Esempio H5. WEB-INF/web.xml (Estratto)
- Esempio H6. sidebar-links.jsp
- Esempio H7. header.jsp
- Esempio H8. content.jsp
- Esempio H9. footer.jsp
- Esempio H10. WEB-INF/classes/resources_en.properties
- Esempio H11. WEB-INF/classes/resources_zh.properties
- Per saperne di più su questo argomento
Il pattern Composite
Prima di immergerci nel pattern Composite, devo prima definire gli oggetti compositi: oggetti che contengono altri oggetti; per esempio, un disegno può essere composto da primitive grafiche, come linee, cerchi, rettangoli, testo e così via.
Gli sviluppatori Java hanno bisogno del modello Composite perché spesso dobbiamo manipolare i compositi esattamente nello stesso modo in cui manipoliamo gli oggetti primitivi. Per esempio, primitivi grafici come linee o testo devono essere disegnati, spostati e ridimensionati. Ma vogliamo anche eseguire la stessa operazione sui compositi, come i disegni, che sono composti da quelle primitive. Idealmente, vorremmo eseguire operazioni su entrambi gli oggetti primitivi e compositi esattamente nello stesso modo, senza distinguere tra i due. Se dobbiamo distinguere tra oggetti primitivi e compositi 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:
Comporre oggetti in strutture ad albero per rappresentare gerarchie parte-intera. Composite permette ai client di trattare oggetti individuali e composizioni di oggetti in modo uniforme.
Implementare il pattern Composite è facile. Le classi composite estendono una classe base che rappresenta oggetti primitivi. La Figura 1 mostra un diagramma di classe che illustra la struttura del pattern Composite.
Nel diagramma di classe della Figura 1, ho usato i nomi delle classi dalla discussione sul pattern Composite di Design Pattern: Component
rappresenta una classe base (o forse un’interfaccia) per oggetti primitivi, e Composite
rappresenta una classe composita. Per esempio, la classe Component
potrebbe rappresentare una classe base per le primitive grafiche, mentre la classe Composite
potrebbe rappresentare una classe Drawing
. La classe Leaf
della figura 1 rappresenta un oggetto primitivo concreto; per esempio, una classe Line
o una classe Text
. I metodi Operation1()
e Operation2()
rappresentano metodi specifici del dominio implementati da entrambe le classi Component
e Composite
.
La classe Composite
mantiene una collezione di componenti. Tipicamente, i metodi Composite
sono implementati iterando su quella collezione e invocando il metodo appropriato per ogni Component
nella collezione. Per esempio, una classe Drawing
potrebbe implementare il suo metodo draw()
in questo modo:
Per ogni metodo implementato nella classe Component
, la classe Composite
implementa un metodo con la stessa firma che itera sui componenti del composto, come illustrato dal metodo draw()
elencato sopra.
La classe Composite
estende la classe Component
, così potete passare un composito a un metodo che si aspetta un componente; per esempio, considerate il seguente metodo:
// This method is implemented in a class that's unrelated to the// Component and Composite classespublic void repaint(Component component) { // The component can be a composite, but since it extends // the Component class, this method need not // distinguish between components and composites component.draw();}
Al metodo precedente viene passato un componente – sia un componente semplice che un composito – quindi invoca il metodo draw()
di quel componente. Poiché la classe Composite
estende Component
, il metodo repaint()
non ha bisogno di distinguere tra componenti e compositi – semplicemente invoca il metodo draw()
per il componente (o composito).
Il diagramma di classe del pattern Composite della Figura 1 illustra un problema con il pattern: dovete distinguere tra componenti e compositi quando fate riferimento a Component
, e dovete invocare un metodo specifico per il composito, come addComponent()
. In genere si soddisfa questo requisito aggiungendo un metodo, come isComposite()
, alla classe Component
. Questo metodo restituisce false
per i componenti ed è sovrascritto nella classe Composite
per restituire true
. Inoltre, dovete anche lanciare il riferimento Component
a un’istanza Composite
, come questo:
Nota che al metodo addComponent()
viene passato un riferimento Component
, che può essere sia un componente primitivo che un composito. Poiché quel componente può essere un composito, è possibile comporre i componenti in una struttura ad albero, come indicato dalla suddetta citazione da Design Patterns.
La figura 2 mostra un’implementazione alternativa del pattern Composite.
Se si implementa il pattern Composite della Figura 2, non si deve mai distinguere tra componenti e compositi, e non si deve fare il cast di un riferimento Component
a un’istanza Composite
. Così il frammento di codice elencato sopra si riduce a una sola linea:
...component.addComponent(someComponentThatCouldBeAComposite);...
Ma, se il riferimento Component
nel frammento di codice precedente non si riferisce a un Composite
, cosa dovrebbe fare il addComponent()
? Questo è un grande punto di contrasto con l’implementazione del pattern Composite della Figura 2. Poiché i componenti primitivi non contengono altri componenti, aggiungere un componente ad un altro componente non ha senso, quindi il metodo Component.addComponent()
può fallire silenziosamente o lanciare un’eccezione. Tipicamente, aggiungere un componente ad un altro componente primitivo è considerato un errore, quindi lanciare un’eccezione è forse la migliore linea d’azione.
Quindi quale implementazione del pattern Composite – quella della Figura 1 o quella della Figura 2 – funziona meglio? Questo è sempre un argomento di grande dibattito tra gli implementatori del pattern Composite; 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 per l’implementazione di metodi in una classe che non hanno senso per quel tipo di oggetto.
Ora che avete capito il pattern Composite e come potete implementarlo, esaminiamo un esempio di pattern Composite con il framework Apache Struts JavaServer Pages (JSP).
Il pattern Composite e Struts Tiles
Il framework Apache Struts include una libreria di tag JSP, conosciuta come Tiles, che permette di comporre una pagina web da più JSP. Tiles è in realtà un’implementazione del pattern J2EE (Java 2 Platform, Enterprise Edition) CompositeView, a sua volta basato sul pattern Design Patterns Composite. Prima di discutere la rilevanza del pattern Composite per la libreria di tag Tiles, rivediamo prima la logica di Tiles e come si usa. Se avete già familiarità con Struts Tiles, potete scorrere le sezioni seguenti e iniziare a leggere da “Use the Composite Pattern with Struts Tiles.”
Nota: Potete leggere di più sul pattern J2EE CompositeView nel mio articolo “Web Application Components Made Easy with Composite View” (JavaWorld, Dicembre 2001).
I progettisti spesso costruiscono pagine web con un insieme di regioni discrete; per esempio, la pagina web della Figura 3 comprende una barra laterale, un’intestazione, una regione di contenuto e un piè di pagina.
I siti web spesso includono più pagine web con layout identici, come il layout della figura 3 tra barra laterale/intestazione/contenuto/piè di pagina. Struts Tiles permette di riutilizzare sia il contenuto che il layout tra più pagine web. Prima di discutere del riutilizzo, vediamo come il layout della Figura 3 è tradizionalmente implementato con il solo HTML.
Implementare a mano layout complessi
L’esempio 1 mostra come puoi implementare la pagina web della Figura 3 con HTML:
Esempio 1. Un layout complesso implementato a mano
Il precedente JSP ha due grandi svantaggi: Primo, il contenuto della pagina è incorporato nel JSP, quindi non puoi riutilizzare nulla di esso, anche se la barra laterale, l’intestazione e il piè di pagina sono probabilmente gli stessi in molte pagine web. In secondo luogo, anche il layout della pagina è incorporato in quel JSP, quindi non è possibile riutilizzarlo anche se molte altre pagine web nello stesso sito usano lo stesso layout. Possiamo usare l’azione <jsp:include>
per rimediare al primo inconveniente, come discuterò in seguito.
Implementare layout complessi con JSP include
L’esempio 2 mostra un’implementazione della pagina web della Figura 3 che usa <jsp:include>
:
Esempio 2. Un layout complesso implementato con JSP include
La JSP precedente include il contenuto di altre JSP con <jsp:include>
. Poiché ho incapsulato quel contenuto in JSP separate, puoi riutilizzarlo per altre pagine web:
Per completezza, ho elencato sotto le JSP incluse dalla JSP precedente:
Esempio 4. header.jsp
<font size='6'>Welcome to Sabreware, Inc.</font><hr>
Esempio 5. content.jsp
<font size='4'>Page-specific content goes here</font>
<hr>Thanks for stopping by!
Anche se la JSP dell’esempio 2 usa <jsp:include>
per riutilizzare il contenuto, non puoi riutilizzare il layout della pagina perché è hardcoded in quella JSP. Struts Tiles ti permette di riutilizzare sia il contenuto che il layout, come illustrato nella prossima sezione.
Implementare layout complessi con Struts Tiles
L’esempio 7 mostra la pagina web di Figura 3 implementata con Struts Tiles:
Esempio 7. Usare Struts Tiles per incapsulare il layout
La JSP precedente usa il tag <tiles:insert>
per creare la JSP della Figura 3. Questa JSP è definita da una definizione di tiles chiamata sidebar-header-footer-definition
. Questa definizione risiede nel file di configurazione Tiles, che in questo caso è WEB-INF/tiles-defs.xml
, elencato nell’esempio 8:
Esempio 8. WEB-INF/tiles-defs.xml
La definizione Tiles precedente specifica il layout della pagina, incapsulato in header-footer-sidebar-layout.jsp
, e il contenuto della pagina, incapsulato in sidebar.jsp
, header.jsp
, content.jsp
, e footer.jsp
, come elencato negli esempi 3-6. L’esempio 9 elenca il JSP che definisce il layoutheader-footer-sidebar-layout.jsp
:
Il precedente JSP incapsula il layout e inserisce il contenuto secondo i valori specificati per le regioni sidebar, editor, content e footer nel file di definizione Tiles, facilitando così il riutilizzo sia del contenuto che del layout. Per esempio, si potrebbe definire un’altra definizione Tiles con lo stesso layout, la stessa barra laterale, lo stesso editor e lo stesso footer, ma con un contenuto diverso:
Per creare la JSP definita dalla definizione tiles a-different-sidebar-header-footer-definition
, si usa il tag <tiles:insert>
, come questo:
Grazie a Struts Tiles, è possibile riutilizzare sia il contenuto che il layout, il che si rivela prezioso per siti web con molte JSP che condividono il layout e alcuni contenuti. Ma se guardate attentamente il codice degli esempi 7-9, noterete che il layout per la regione della barra laterale è hardcoded in sidebar.jsp
, che è elencato nell’esempio 3. Questo significa che non puoi riutilizzare quel layout. Fortunatamente, la libreria di tag Tiles implementa il modello Composite, che ci permette di specificare una definizione di tiles – invece di un JSP – per una regione. Nella prossima sezione, spiego come usare l’implementazione del pattern Composite.
Utilizzare il pattern Composite con Struts Tiles
Struts Tiles implementa il pattern Composite, dove la classe Component
è rappresentata da JSP e la classe Composite
è rappresentata da una definizione Tiles. Questa implementazione permette di specificare o una JSP (un componente) o una definizione Tiles (un composito) come contenuto della regione di una JSP. L’esempio 10 illustra questa caratteristica:
Esempio 10. WEB-INF/tiles-defs.xml: Usa il modello Composite
Il precedente file di configurazione Tiles definisce due definizioni Tiles: sidebar-definition
e sidebar-header-footer-definition
. Il sidebar-definition
è specificato come valore per la regione della barra laterale nel sidebar-header-footer-definition
. Puoi specificarlo come tale perché Tiles implementa il modello Composite permettendo a Tiles di specificare una definizione (un Composite
che è una collezione JSP) dove normalmente specificheresti un singolo JSP (che è un Component
).
Il layout della sidebar è incapsulato nel sidebar-layout.jsp
dell’esempio 11:
L’esempio 12 elenca flags.jsp
, specificato come contenuto della regione top
della sidebar, e l’esempio 13 elenca sidebar-links.jsp
, specificato come regione bottom
della sidebar:
Esempio 12. flags.jsp
Ora il sidebar-definition
può definire altre regioni con un componente superiore e inferiore, anche se probabilmente dovresti rinominare questa definizione in qualcosa di più generico come top-bottom-definition
.
E’ tutto composito in questi giorni
Il pattern Composite è popolare nei framework di presentazione, come Swing e Struts, perché ti permette di annidare i contenitori trattando i componenti e i loro contenitori esattamente allo stesso modo. Struts Tiles usa il pattern Composite per specificare un semplice JSP o una definizione Tiles – che è una collezione di JSP – come contenuto di un tile. Questa è una capacità potente che facilita la gestione di grandi siti web con diversi layout.
La sezione “Homework from Last Time” qui sotto espande la discussione di questo articolo internazionalizzando l’applicazione precedente con un’azione Struts e la JSP Standard Tag Library (JSTL).
Homework
Discutere come Swing implementa il pattern composito con le classi Component
e Container
.
Ricompito dell’ultima volta
L’ultimo compito ti ha chiesto di scaricare Struts da http://jakarta.apache.org/struts/index.html e di implementare la tua classe di azione Struts.
Per questo compito, ho deciso di implementare un’azione Struts insieme alla JSP Standard Tag Library (JSTL) per internazionalizzare l’applicazione web dell’Esempio 1. Anche se Struts fornisce l’infrastruttura necessaria per internazionalizzare le vostre applicazioni web, dovreste usare JSTL per questo compito, perché JSTL è uno standard. Ad un certo punto, le capacità di internazionalizzazione di Struts saranno probabilmente deprecate o integrate con JSTL.
Dopo aver internazionalizzato l’applicazione web dell’esempio 1 con un’azione Struts e JSTL, ho localizzato l’applicazione per il cinese. La figura H1 illustra il risultato.
Nota: non conosco una sola parola di cinese, tanto meno come scrivere la lingua, quindi il cinese della figura H1 è fabbricato da stringhe Unicode arbitrarie. Ma sembra figo, nonostante tutto.
Nota che ho specificato gli attributi href
nell’esempio 12 flags.jsp
come stringhe vuote, così quando si fa clic sulle bandiere, il servlet container ricarica la JSP corrente. Pertanto, il primo passo verso l’internazionalizzazione dell’applicazione Web è specificare un URL per questi attributi, come elencato nell’esempio H1:
Esempio H1. flags.jsp
L’URL per ogni attributo href
è lo stesso: flags.do
. Due parametri di richiesta sono aggiunti a questo URL: uno per il locale corrispondente alla bandiera e un altro che rappresenta il percorso della JSP corrente. Si ottiene quest’ultimo parametro di richiesta invocando il metodo Http.ServletRequest.getServletPath()
.
Nel deployment descriptor dell’applicazione, ho mappato tutti gli URL che terminano con .do
al servlet dell’azione Struts, come questo:
Esempio H2. WEB-INF/web.xml (Estratto)
Nota: Vedi il mio articolo “Take Command of Your Software” (JavaWorld, giugno 2002) per maggiori informazioni su Struts e l’action servlet di Struts.
In seguito, ho mappato il percorso /flags
all’azione Struts actions.FlagAction
nel file di configurazione di Struts, elencato nell’esempio H3:
Esempio H3. WEB-INF/struts-config.xml
A causa di questa mappatura, l’URL flags.do
fa sì che il servlet dell’azione Struts invochi il metodo actions.FlagAction.execute()
; quindi, cliccando su una bandiera si invocherà il metodo actions.FlagAction.execute()
. L’esempio H4 elenca la classe actions.FlagAction
:
Esempio H4. WEB-INF/classes/actions/FlagAction.java
Il metodo actions.FlagAction.execute()
ottiene un riferimento al parametro della richiesta locale
e passa quel valore al metodo set()
della classe JSTL Config
. Questo metodo memorizza la stringa che rappresenta il locale nell’impostazione di configurazione FMT_LOCALE
, che JSTL usa per localizzare il testo e formattare numeri, valute, percentuali e date.
Ora che ho specificato un locale per le azioni di internazionalizzazione di JSTL, ho specificato un bundle di risorse nel deployment descriptor dell’applicazione, un estratto del quale è elencato nell’esempio H5:
Esempio H5. WEB-INF/web.xml (Estratto)
Il nome base del bundle di risorse resources
è specificato per il parametro javax.servlet.jsp.jstl.fmt.localizationContext
di inizializzazione del contesto. Ciò significa che JSTL cercherà un file chiamato resources_en.properties
quando il locale è impostato su British English (en-GB
) e resources_zh.properties
quando il locale è impostato su Chinese (zh-ZH
).
Ora che ho specificato un bundle di risorse e un locale per le azioni di internazionalizzazione JSTL, modifico i file JSP per usare queste azioni, come dimostrano gli esempi H6-H9:
Esempio H7. header.jsp
Esempio H8. content.jsp
Le JSP degli esempi H6-H9 usano l’azione JSTL <fmt:message>
per estrarre le stringhe Unicode dal pacchetto di risorse specificato nel descrittore di distribuzione. Gli esempi H10 e H11 elencano i bundle di risorse per l’inglese e il cinese, rispettivamente:
Esempio H10. WEB-INF/classes/resources_en.properties
Esempio H11. WEB-INF/classes/resources_zh.properties
In una e-mail a me, Joseph Friedman ha scritto:
In “Decorate Your Java Code” (JavaWorld, Dicembre 2001), tu scrivi:
“L’oggetto che racchiude, noto come decoratore, è conforme all’interfaccia dell’oggetto che racchiude, permettendo al decoratore di essere usato come se fosse un’istanza dell’oggetto che racchiude.”
Tuttavia, l’esempio di codice decora il
FileReader
con unLineNumberReader
e chiamareadLine()
, che non è nell’interfacciaFileReader
; quindi non state usando ilLineNumberReader
in modo trasparente.
Sia FileReader
che LineNumberReader
sono conformi all’interfaccia definita dalla classe Reader
, che entrambi estendono. L’idea è che potete passare il decoratore a qualsiasi metodo che si aspetta un riferimento a un Reader
. Questi metodi rimangono beatamente ignari delle capacità speciali di quel decoratore – in questo caso, la capacità di leggere file e produrre numeri di linea. Tuttavia, se siete a conoscenza di queste capacità speciali, potete approfittarne.
Il fatto che il decoratore possieda (uno o più) metodi che mancano all’oggetto decorato non viola in alcun modo l’intento del pattern Decorator; infatti, questa è la caratteristica centrale di quel pattern: aggiungere funzionalità a runtime ad un oggetto decorando oggetti in modo ricorsivo.
Se guardate Design Patterns a pagina 173, vedrete questo:
Le sottoclassi di Decorator sono libere di aggiungere operazioni per funzionalità specifiche. Per esempio, l’operazione
ScrollTo
diScrollDecorator
permette ad altri oggetti di scorrere l’interfaccia *se* sanno che c’è un oggettoScrollDecorator
nell’interfaccia.
DavidGeary è l’autore di Core JSTL Mastering the JSP Standard TagLibrary, che sarà pubblicato questo autunno daPrentice-Hall e Sun Microsystems Press; Advanced JavaServer Pages (PrenticeHall, 2001; ISBN: 0130307041); e la serie Graphic Java (Sun MicrosystemsPress). David ha sviluppato software orientato agli oggetti con numerosi linguaggi orientati agli oggetti per 18 anni. Da quando il libro GOFDesign Patterns è stato pubblicato nel 1994, David è stato un attivo sostenitore dei design pattern, e ha usato e implementato design pattern in Smalltalk, C++ e Java. Nel 1997, David ha iniziato a lavorare a tempo pieno come autore e consulente occasionale. David è un membro dei gruppi di esperti che definiscono la libreria di tag personalizzati standardJSP e JavaServer Faces, ed è un contributore del framework Apache Struts JSP.