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

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.

Figuur 1. Een klassendiagram van het Composite-patroon

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.

Figuur 2. Een alternatief Composite pattern klassendiagram

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.

Figuur 3. Het samengestelde patroon en Struts-tegels. Klik op de miniatuur om de afbeelding op ware grootte te bekijken.

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:

Voorbeeld 3. sidebar.jsp

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>

Voorbeeld 6. footer.jsp

<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:

Voorbeeld 9. 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 11. 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

Voorbeeld 13. sidebar-links.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.

Figuur H1. Internationalisatie met een Struts-actie. Klik op de miniatuur om de afbeelding op ware grootte te zien.

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:

Voorbeeld H6. sidebar-links.jsp

Example H7. header.jsp

Example H8. content.jsp

Example H9. footer.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

Email

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 een LineNumberReader en roept readLine() aan, die niet in de FileReader-interface zit; u gebruikt de LineNumberReader 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’s ScrollTo operatie laat andere objecten door de interface scrollen *als* ze weten dat er toevallig een ScrollDecorator 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.

Lees meer over dit onderwerp

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.