Vandaag luisterde ik naar National Public Radio’s Car Talk, een populaire wekelijkse uitzending waarin bellers vragen stellen over hun auto. Voor elke pauze vragen de presentatoren de bellers om 1-800-CAR-TALK te bellen, wat overeenkomt met 1-800-227-8255. Het eerste nummer is natuurlijk veel gemakkelijker te onthouden dan het tweede, deels omdat de woorden “CAR TALK” een samenstelling zijn: twee woorden die samen zeven cijfers vormen. Mensen vinden het over het algemeen gemakkelijker om met samenstellingen om te gaan, dan met hun afzonderlijke componenten. Evenzo, wanneer je object georiënteerde software ontwikkelt, is het vaak handig om samenstellingen te manipuleren net zoals je individuele componenten manipuleert. Die premisse vertegenwoordigt het fundamentele principe van het Composite ontwerp patroon, het onderwerp van deze Java Design Patterns aflevering.
- Het Composite patroon
- The Composite pattern and Struts Tiles
- Een complexe opmaak met de hand implementeren
- Voorbeeld 1. Een complexe layout met de hand geïmplementeerd
- Emplementeer complexe layouts met JSP includes
- Voorbeeld 2. Een complexe layout geïmplementeerd met JSP bevat
- Voorbeeld 3. sidebar.jsp
- Voorbeeld 4. header.jsp
- Voorbeeld 5. content.jsp
- Voorbeeld 6. footer.jsp
- Complexe lay-outs implementeren met Struts Tiles
- Voorbeeld 7. Gebruik Struts Tiles om lay-out in te kapselen
- Voorbeeld 8. WEB-INF/tiles-defs.xml
- Voorbeeld 9. header-footer-sidebar-layout.jsp
- Het Composite patroon gebruiken met Struts Tiles
- Voorbeeld 10. WEB-INF/tiles-defs.xml: Gebruik het samengestelde patroon
- Voorbeeld 11. sidebar-layout.jsp
- Voorbeeld 12. flags.jsp
- Voorbeeld 13. sidebar-links.jsp
- Het zijn tegenwoordig allemaal composites
- Homework
- Huiswerk van de vorige keer
- Voorbeeld H1. flags.jsp
- Example H2. WEB-INF/web.xml (Excerpt)
- Voorbeeld H3. WEB-INF/struts-config.xml
- Voorbeeld H4. WEB-INF/classes/actions/FlagAction.java
- Voorbeeld H5. WEB-INF/web.xml (Excerpt)
- Voorbeeld H6. sidebar-links.jsp
- Example H7. header.jsp
- Example H8. content.jsp
- Example H9. footer.jsp
- Voorbeeld H10. WEB-INF/classes/resources_en.properties
- Voorbeeld H11. WEB-INF/classes/resources_zh.properties
- Lees meer over dit onderwerp
Het Composite patroon
Voordat we in het Composite patroon duiken, moet ik eerst composite objecten definiëren: objecten die andere objecten bevatten; bijvoorbeeld, een tekening kan worden samengesteld uit grafische primitieven, zoals lijnen, cirkels, rechthoeken, tekst, enzovoort.
Java ontwikkelaars hebben het Composite patroon nodig omdat we composieten vaak op precies dezelfde manier moeten manipuleren als primitieve objecten. Bijvoorbeeld, grafische primitieven zoals lijnen of tekst moeten worden getekend, verplaatst en van grootte veranderd. Maar we willen dezelfde operatie ook uitvoeren op composites, zoals tekeningen, die zijn samengesteld uit die primitieven. In het ideale geval zouden we bewerkingen op zowel primitieve objecten als composities op precies dezelfde manier willen uitvoeren, zonder onderscheid te maken tussen de twee. Als we onderscheid moeten maken tussen primitieve objecten en composieten om dezelfde bewerkingen op die twee soorten objecten uit te voeren, zou onze code complexer worden en moeilijker te implementeren, te onderhouden en uit te breiden.
In Design Patterns beschrijven de auteurs het Composite pattern als volgt:
Composeer objecten in boomstructuren om deel-geheel hiërarchieën weer te geven. Composite laat clients individuele objecten en samenstellingen van objecten uniform behandelen.
Het implementeren van het Composite patroon is eenvoudig. Samengestelde klassen breiden een basisklasse uit die primitieve objecten vertegenwoordigt. Figuur 1 toont een klassendiagram dat de structuur van het Composite-patroon illustreert.
In het klassendiagram van figuur 1 heb ik klassennamen gebruikt uit de bespreking van het Composite-patroon van Design Pattern: Component
staat voor een basisklasse (of mogelijk een interface) voor primitieve objecten, en Composite
staat voor een samengestelde klasse. Bijvoorbeeld, de Component
klasse zou een basisklasse voor grafische primitieven kunnen vertegenwoordigen, terwijl de Composite
klasse een Drawing
klasse zou kunnen vertegenwoordigen. De klasse Leaf
van figuur 1 vertegenwoordigt een concreet primitief object; bijvoorbeeld een Line
klasse of een Text
klasse. De Operation1()
en Operation2()
methoden vertegenwoordigen domeinspecifieke methoden die door zowel de Component
als de Composite
klassen worden geïmplementeerd.
De Composite
klasse onderhoudt een verzameling van componenten. Typisch worden Composite
methoden geïmplementeerd door iteratie over die verzameling en het aanroepen van de juiste methode voor elke Component
in de verzameling. Bijvoorbeeld, een Drawing
klasse zou haar draw()
methode als volgt kunnen implementeren:
Voor elke methode die in de Component
klasse is geïmplementeerd, implementeert de Composite
klasse een methode met dezelfde handtekening die itereert over de componenten van de composiet, zoals geïllustreerd door de draw()
methode die hierboven is vermeld.
De Composite
klasse breidt de Component
klasse uit, zodat u een composiet kunt doorgeven aan een methode die een component verwacht; bijvoorbeeld, overweeg de volgende methode:
// 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();}
De voorgaande methode wordt een component doorgegeven – hetzij een eenvoudige component of een composiet – en roept dan de draw()
methode van die component op. Omdat de klasse Composite
Component
uitbreidt, hoeft de methode repaint()
geen onderscheid te maken tussen componenten en composieten – zij roept eenvoudig de methode draw()
voor de component (of composiet) aan.
Figuur 1’s Composite pattern klassediagram illustreert een probleem met het patroon: u moet onderscheid maken tussen componenten en composieten wanneer u naar een Component
verwijst, en u moet een composiet-specifieke methode aanroepen, zoals addComponent()
. U voldoet meestal aan die eis door een methode, zoals isComposite()
, toe te voegen aan de Component
klasse. Die methode retourneert false
voor componenten en wordt in de klasse Composite
overschreven om true
te retourneren. Bovendien moet u ook de Component
verwijzing casten naar een Composite
instantie, zoals dit:
Merk op dat de addComponent()
methode een Component
verwijzing doorgeeft, die zowel een primitieve component als een composiet kan zijn. Omdat die component een composite kan zijn, kun je componenten samenstellen in een boomstructuur, zoals aangegeven door het eerder genoemde citaat uit Design Patterns.
Figuur 2 toont een alternatieve Composite pattern implementatie.
Als u het Composite pattern van figuur 2 implementeert, hoeft u nooit onderscheid te maken tussen componenten en composites, en hoeft u een Component
-referentie niet te casten naar een Composite
-instantie. Dus het bovenstaande code fragment wordt gereduceerd tot een enkele regel:
...component.addComponent(someComponentThatCouldBeAComposite);...
Maar, als de Component
verwijzing in het voorgaande code fragment niet verwijst naar een Composite
, wat moet de addComponent()
dan doen? Dat is een belangrijk twistpunt met de implementatie van het Composite patroon in figuur 2. Omdat primitieve componenten geen andere componenten bevatten, heeft het toevoegen van een component aan een andere component geen zin, dus de Component.addComponent()
methode kan ofwel stil falen of een exceptie werpen. Typisch wordt het toevoegen van een component aan een andere primitieve component beschouwd als een fout, dus het gooien van een exception is misschien de beste manier van handelen.
Dus welke Composite pattern implementatie-degene in figuur 1 of die in figuur 2 werkt het beste? Dat is altijd een onderwerp van grote discussie tussen Composite pattern implementeerders; Design Patterns geeft de voorkeur aan de Figure 2 implementatie omdat je nooit onderscheid hoeft te maken tussen components en containers, en omdat je nooit een cast hoeft uit te voeren. Persoonlijk geef ik de voorkeur aan de implementatie van figuur 1, omdat ik een sterke afkeer heb van het implementeren van methoden in een klasse die niet zinvol zijn voor dat objecttype.
Nu je het Composite patroon begrijpt en hoe je het kunt implementeren, laten we eens kijken naar een Composite patroon voorbeeld met het Apache Struts JavaServer Pages (JSP) framework.
The Composite pattern and Struts Tiles
Het Apache Struts framework bevat een JSP tag library, bekend als Tiles, waarmee je een webpagina kunt samenstellen uit meerdere JSPs. Tiles is eigenlijk een implementatie van het J2EE (Java 2 Platform, Enterprise Edition) CompositeView patroon, zelf gebaseerd op het Design Patterns Composite patroon. Voor we de relevantie van het Composite patroon voor de Tiles tag bibliotheek bespreken, bekijken we eerst de rationale van Tiles, en hoe je het gebruikt. Als je al bekend bent met Struts Tiles, kun je de volgende secties doornemen en beginnen met lezen bij “Use the Composite Pattern with Struts Tiles.”
Note: Je kunt meer lezen over het J2EE CompositeView patroon in mijn “Web Application Components Made Easy with Composite View” (JavaWorld, december 2001) artikel.
Ontwerpers bouwen webpagina’s vaak met een reeks discrete regio’s; de webpagina van figuur 3 bestaat bijvoorbeeld uit een zijbalk, een koptekst, een inhoudsgebied en een voettekst.
Websites bevatten vaak meerdere webpagina’s met identieke lay-outs, zoals de sidebar/header/content/footer-lay-out in figuur 3. Met Struts Tiles kunt u zowel inhoud als lay-out hergebruiken tussen meerdere webpagina’s. Voordat we dat hergebruik bespreken, laten we eens zien hoe de opmaak van Figuur 3 traditioneel wordt geïmplementeerd met HTML alleen.
Een complexe opmaak met de hand implementeren
Voorbeeld 1 laat zien hoe u de webpagina van Figuur 3 kunt implementeren met HTML:
Voorbeeld 1. Een complexe layout met de hand geïmplementeerd
De voorgaande JSP heeft twee grote nadelen: Ten eerste, de inhoud van de pagina is ingebed in de JSP, dus je kunt er niets van hergebruiken, ook al zijn de sidebar, header, en footer waarschijnlijk hetzelfde over vele Webpages. Ten tweede, de opmaak van de pagina is ook ingebed in die JSP, dus die kunt u ook niet hergebruiken, ook al gebruiken veel andere webpagina’s op dezelfde Website dezelfde opmaak. We kunnen de <jsp:include>
actie gebruiken om het eerste nadeel te verhelpen, zoals ik hierna zal bespreken.
Emplementeer complexe layouts met JSP includes
Voorbeeld 2 toont een implementatie van de webpagina van Figuur 3 die gebruik maakt van <jsp:include>
:
Voorbeeld 2. Een complexe layout geïmplementeerd met JSP bevat
De voorgaande JSP bevat de inhoud van andere JSP’s met <jsp:include>
. Omdat ik die inhoud in aparte JSP’s heb ingekapseld, kunt u die hergebruiken voor andere webpagina’s:
Voor de volledigheid heb ik hieronder de JSP’s opgesomd die door de voorgaande JSP zijn geincludeerd:
Voorbeeld 4. header.jsp
<font size='6'>Welcome to Sabreware, Inc.</font><hr>
Voorbeeld 5. content.jsp
<font size='4'>Page-specific content goes here</font>
<hr>Thanks for stopping by!
Ondanks dat de JSP van Voorbeeld 2 <jsp:include>
gebruikt om inhoud te hergebruiken, kun je de opmaak van de pagina niet hergebruiken omdat die hard gecodeerd is in die JSP. Met Struts Tiles kunt u zowel de inhoud als de lay-out hergebruiken, zoals in de volgende sectie wordt geïllustreerd.
Complexe lay-outs implementeren met Struts Tiles
Voorbeeld 7 toont de webpagina in figuur 3 die is geïmplementeerd met Struts Tiles:
Voorbeeld 7. Gebruik Struts Tiles om lay-out in te kapselen
De voorgaande JSP gebruikt de <tiles:insert>
tag om de JSP van figuur 3 te maken. Die JSP wordt gedefinieerd door een tiles definitie met de naam sidebar-header-footer-definition
. Die definitie bevindt zich in het Tiles configuratie bestand, dat in dit geval WEB-INF/tiles-defs.xml
is, zoals in Voorbeeld 8:
Voorbeeld 8. WEB-INF/tiles-defs.xml
De voorgaande Tiles definitie specificeert de pagina layout, ingekapseld in header-footer-sidebar-layout.jsp
, en de inhoud van de pagina, ingekapseld in sidebar.jsp
, header.jsp
, content.jsp
, en footer.jsp
, zoals opgesomd in Voorbeelden 3-6. Voorbeeld 9 toont de JSP die de layout definieert- header-footer-sidebar-layout.jsp
:
De voorgaande JSP kapselt de layout in en voegt inhoud in volgens de waarden die gespecificeerd zijn voor de sidebar, editor, content, en footer regio’s in het Tiles definitiebestand, waardoor hergebruik voor zowel de inhoud als de layout eenvoudiger wordt. Je zou bijvoorbeeld een andere Tiles definitie kunnen definiëren met dezelfde layout, dezelfde sidebar, editor en footer, maar verschillende inhoud:
Om de JSP te maken die gedefinieerd is door de tiles definitie a-different-sidebar-header-footer-definition
, gebruik je de <tiles:insert>
tag, zoals dit:
Dankzij Struts Tiles, kan je zowel inhoud als layout hergebruiken, wat van onschatbare waarde blijkt te zijn voor websites met veel JSPs die layout en wat inhoud delen. Maar als u goed kijkt naar de code van Voorbeelden 7-9, zult u merken dat de layout voor de sidebar regio hard gecodeerd is in sidebar.jsp
, die vermeld staat in Voorbeeld 3. Dat betekent dat u die opmaak niet kunt hergebruiken. Gelukkig implementeert de Tiles tag bibliotheek het Composite patroon, waarmee we een tiles definitie kunnen specificeren-in plaats van een JSP voor een region. In de volgende sectie leg ik uit hoe je dat Composite patroon kunt gebruiken.
Het Composite patroon gebruiken met Struts Tiles
Struts Tiles implementeert het Composite patroon, waar de Component
klasse wordt gerepresenteerd door JSPs en de Composite
klasse wordt gerepresenteerd door een Tiles definitie. Met deze implementatie kunt u ofwel een JSP (een component) of een Tiles definitie (een composite) specificeren als de inhoud voor een JSP’s region. Voorbeeld 10 illustreert deze mogelijkheid:
Voorbeeld 10. WEB-INF/tiles-defs.xml: Gebruik het samengestelde patroon
Het voorgaande Tiles configuratiebestand definieert twee Tiles definities: sidebar-definition
en sidebar-header-footer-definition
. De sidebar-definition
is gespecificeerd als de waarde voor het zijbalkgebied in de sidebar-header-footer-definition
. U kunt het zo specificeren omdat Tiles het Composite patroon implementeert door Tiles een definitie te laten specificeren (een Composite
die een JSP collectie is) waar u normaal een enkele JSP zou specificeren (die een Component
is).
De layout van de zijbalk is ingekapseld in Voorbeeld 11’s sidebar-layout.jsp
:
Voorbeeld 12 geeft flags.jsp
, gespecificeerd als de inhoud voor de top
regio van de sidebar, en Voorbeeld 13 geeft sidebar-links.jsp
, gespecificeerd als de bottom
regio van de sidebar:
Voorbeeld 12. flags.jsp
Nu kan de sidebar-definition
andere regionen definiëren met een boven- en ondercomponent, hoewel je die definitie waarschijnlijk zou moeten hernoemen naar iets algemeners als top-bottom-definition
.
Het zijn tegenwoordig allemaal composites
Het Composite pattern is populair bij presentatie frameworks, zoals Swing en Struts, omdat het je containers laat nesten door componenten en hun containers precies hetzelfde te behandelen. Struts Tiles gebruikt het Composite patroon om een eenvoudige JSP of een Tiles definitie – wat een verzameling van JSP’s is – te specificeren als de inhoud van een tile. Dat is een krachtige mogelijkheid die het beheer van grote websites met verschillende lay-outs vergemakkelijkt.
De “Huiswerk van de vorige keer” sectie hieronder breidt uit op de discussie van dit artikel door het internationaliseren van de voorgaande toepassing met een Struts actie en de JSP Standard Tag Library (JSTL).
Homework
Ontdek hoe Swing het composiet patroon implementeert met de Component
en Container
klassen.
Huiswerk van de vorige keer
In je laatste opdracht werd je gevraagd om Struts te downloaden van http://jakarta.apache.org/struts/index.html en je eigen Struts action class te implementeren.
Voor deze opdracht heb ik besloten om een Struts action te implementeren in combinatie met de JSP Standard Tag Library (JSTL) om de webapplicatie van Voorbeeld 1 te internationaliseren. Hoewel Struts de nodige infrastructuur levert om je Web toepassingen te internationaliseren, moet je JSTL gebruiken voor die taak, omdat JSTL een standaard is. Op een gegeven moment zullen de Struts internationalisatie mogelijkheden waarschijnlijk worden afgeschreven of geïntegreerd met JSTL.
Nadat ik de Web applicatie van Voorbeeld 1 had geïnternationaliseerd met een Struts actie en JSTL, heb ik die applicatie gelokaliseerd voor Chinees. Figuur H1 illustreert het resultaat.
Note: Ik ken geen woord Chinees, laat staan dat ik weet hoe ik de taal moet schrijven, dus het Chinees van Figuur H1 is gemaakt van willekeurige Unicode strings. Maar hoe dan ook, het ziet er cool uit.
Merk op dat ik de href
-attributen in de flags.jsp
van Voorbeeld 12 als lege strings heb gespecificeerd, dus wanneer u op de vlaggen klikt, laadt de servletcontainer de huidige JSP opnieuw. Daarom is de eerste stap naar het internationaliseren van de Web applicatie het specificeren van een URL voor deze attributen, zoals in Voorbeeld H1:
Voorbeeld H1. flags.jsp
De URL voor elk href
attribuut is hetzelfde: flags.do
. Twee request parameters zijn aan die URL vastgeplakt: een voor de locale die overeenkomt met de vlag en een andere die het pad van de huidige JSP weergeeft. De laatste request parameter verkrijg je door de Http.ServletRequest.getServletPath()
methode aan te roepen.
In de deployment descriptor van de applicatie heb ik alle URL’s die eindigen op .do
toegewezen aan de Struts action servlet, zoals dit:
Example H2. WEB-INF/web.xml (Excerpt)
Note: Zie mijn “Take Command of Your Software” (JavaWorld, juni 2002) artikel voor meer informatie over Struts en de Struts action servlet.
Next, ik heb het pad /flags
toegewezen aan de Struts action actions.FlagAction
in het Struts configuratie bestand, vermeld in Voorbeeld H3:
Voorbeeld H3. WEB-INF/struts-config.xml
Door die toewijzing zorgt de URL flags.do
ervoor dat de Struts action servlet de actions.FlagAction.execute()
methode aanroept; daarom zal het klikken op een vlag de actions.FlagAction.execute()
methode aanroepen. Voorbeeld H4 vermeldt de actions.FlagAction
klasse:
Voorbeeld H4. WEB-INF/classes/actions/FlagAction.java
De methode actions.FlagAction.execute()
verkrijgt een verwijzing naar de locale
verzoekparameter en geeft die waarde door aan de set()
methode van de klasse JSTL Config
. Die methode slaat de string die de locale vertegenwoordigt op in de FMT_LOCALE
configuratie-instelling, die JSTL gebruikt om tekst te lokaliseren en getallen, valuta, procenten en data te formatteren.
Nu ik een locale voor JSTL internationalisatie acties heb gespecificeerd, specificeer ik een resource bundle in de deployment descriptor van de applicatie, waarvan een uittreksel is opgenomen in Voorbeeld H5:
Voorbeeld H5. WEB-INF/web.xml (Excerpt)
De resource bundle basenaam resources
is gespecificeerd voor de javax.servlet.jsp.jstl.fmt.localizationContext
context-initialisatie parameter. Dat betekent dat JSTL zal zoeken naar een bestand met de naam resources_en.properties
wanneer de locale is ingesteld op Brits Engels (en-GB
) en resources_zh.properties
wanneer de locale is ingesteld op Chinees (zh-ZH
).
Nu ik een resource bundle en een locale heb gespecificeerd voor de JSTL internationalisatie acties, pas ik de JSP bestanden aan om deze acties te gebruiken, zoals Voorbeelden H6-H9 laten zien:
Example H7. header.jsp
Example H8. content.jsp
De JSP’s van de voorbeelden H6-H9 gebruiken de JSTL <fmt:message>
actie om Unicode strings te extraheren uit de resource bundel die in de deployment descriptor is gespecificeerd. Voorbeelden H10 en H11 geven de resource bundles voor Engels en Chinees, respectievelijk:
Voorbeeld H10. WEB-INF/classes/resources_en.properties
Voorbeeld H11. WEB-INF/classes/resources_zh.properties
In een e-mail aan mij schreef Joseph Friedman:
In “Decorate Your Java Code” (JavaWorld, december 2001), schrijf je:
“Het omhullende object – bekend als een decorator – conformeert zich aan de interface van het object dat het omhult, waardoor de decorator kan worden gebruikt alsof het een instantie is van het object dat het omhult.”
Het codevoorbeeld decoreert de
FileReader
echter met eenLineNumberReader
en roeptreadLine()
aan, die niet in deFileReader
-interface zit; u gebruikt deLineNumberReader
dus niet transparant.
Zowel FileReader
als LineNumberReader
conformeren zich aan de interface die is gedefinieerd door de Reader
-klasse, die ze beide uitbreiden. Het idee is dat u de decorator kunt doorgeven aan elke methode die een verwijzing naar een Reader
verwacht. Die methoden blijven gelukzalig onwetend van de speciale mogelijkheden van die decorator-in dit geval, de mogelijkheid om bestanden te lezen en regelnummers te produceren. Maar als je die speciale mogelijkheden kent, kun je er je voordeel mee doen.
Het feit dat de decorator (een of meer) methoden bezit die het gedecoreerde object ontbeert, is op geen enkele manier in strijd met de bedoeling van het Decorator pattern; in feite is dat het centrale kenmerk van dat pattern: om functionaliteit in runtime aan een object toe te voegen door objecten recursief te decoreren.
Als je op Design Patterns pagina 173 kijkt, zie je dit:
Decorator subclasses zijn vrij om operaties voor specifieke functionaliteit toe te voegen. Bijvoorbeeld,
ScrollDecorator
’sScrollTo
operatie laat andere objecten door de interface scrollen *als* ze weten dat er toevallig eenScrollDecorator
object in de interface zit.
DavidGeary is de auteur van Core JSTL Mastering the JSP Standard TagLibrary, dat deze herfst zal worden gepubliceerd doorPrentice-Hall en Sun Microsystems Press; Advanced JavaServer Pages (PrenticeHall, 2001; ISBN: 0130307041); en de Graphic Java series (Sun MicrosystemsPress). David ontwikkelt al 18 jaar objectgeoriënteerde software met talrijke objectgeoriënteerde talen. Sinds het GOFDesign Patterns boek werd gepubliceerd in 1994, is David een actieve voorstander van design patterns, en heeft design patterns gebruikt en geïmplementeerd in Smalltalk, C++, en Java. In 1997 begon David full-time te werken als auteur en af en toe als spreker en consultant. David is lid van de expertgroepen die de JSP standaard custom tag library en Java Server Faces definiëren, en is een bijdrager aan het Apache Struts JSP framework.