Astăzi ascultam emisiunea „Car Talk” de la National Public Radio, o emisiune săptămânală populară în timpul căreia interlocutorii pun întrebări despre vehiculele lor. Înainte de fiecare pauză a programului, gazdele emisiunii îi roagă pe interlocutori să formeze 1-800-CAR-TALK, care corespunde cu 1-800-227-8255. Desigur, primul se dovedește a fi mult mai ușor de reținut decât al doilea, în parte pentru că cuvintele „CAR TALK” sunt un compus: două cuvinte care reprezintă șapte cifre. În general, oamenilor le este mai ușor să se ocupe de compuși, decât de componentele lor individuale. De asemenea, atunci când dezvoltați software orientat pe obiecte, este adesea convenabil să manipulați compuși la fel cum manipulați componentele individuale. Această premisă reprezintă principiul fundamental al modelului de proiectare Composite, subiectul acestui episod din Java Design Patterns.
- Patronul Composite
- Patronul Composite și Struts Tiles
- Implementați manual layout-uri complexe
- Exemplul 1. Un aspect complex implementat manual
- Implementați machete complexe cu incluziuni JSP
- Exemplul 2. Un aspect complex implementat cu JSP include
- Exemplu 3. sidebar.jsp
- Exemplu 4. header.jsp
- Exemplu 5. content.jsp
- Exemplu 6. footer.jsp
- Implementați aspecte complexe cu Struts Tiles
- Exemplul 7. Utilizați Struts Tiles pentru a încapsula layout-ul
- Exemplu 8. WEB-INF/tiles-defs.xml
- Exemplu 9. header-footer-sidebar-layout.jsp
- Utilizați modelul Composite cu Struts Tiles
- Exemplu 10. WEB-INF/tiles-defs.xml: Utilizați modelul Composite
- Exemplul 11. sidebar-layout.jsp
- Exemplul 12. flags.jsp
- Exemplul 13. sidebar-links.jsp
- Toate sunt compuse în zilele noastre
- Treabă de casă
- Terenul de lucru de data trecută
- Exemplu H1. flags.jsp
- Exemplu H2. WEB-INF/web.xml (Excerpt)
- Exemplu H3. WEB-INF/struts-config.xml
- Exemplu H4. WEB-INF/classes/actions/FlagAction.java
- Exemplul H5. WEB-INF/web.xml (Extras)
- Exemplul H6. sidebar-links.jsp
- Exemplu H7. header.jsp
- Exemplu H8. content.jsp
- Exemplu H9. footer.jsp
- Exemplu H10. WEB-INF/classes/resources_en.properties
- Exemplu H11. WEB-INF/classes/resources_zh.properties
- Învățați mai multe despre acest subiect
Patronul Composite
Înainte de a ne scufunda în modelul Composite, trebuie mai întâi să definesc obiectele compozite: obiecte care conțin alte obiecte; de exemplu, un desen poate fi compus din primitive grafice, cum ar fi linii, cercuri, dreptunghiuri, text și așa mai departe.
Dezvoltatorii Java au nevoie de tiparul Composite pentru că deseori trebuie să manipulăm obiectele compuse exact în același mod în care manipulăm obiectele primitive. De exemplu, primitivele grafice, cum ar fi liniile sau textul, trebuie să fie desenate, mutate și redimensionate. Dar dorim, de asemenea, să efectuăm aceeași operațiune pe compozite, cum ar fi desenele, care sunt compuse din aceste primitive. În mod ideal, am dori să efectuăm operații atât pe obiectele primitive, cât și pe cele compuse în exact același mod, fără a face distincție între cele două. Dacă trebuie să facem distincție între obiectele primitive și compozite pentru a efectua aceleași operații pe aceste două tipuri de obiecte, codul nostru ar deveni mai complex și mai dificil de implementat, întreținut și extins.
În Design Patterns, autorii descriu modelul Composite astfel:
Compuneți obiectele în structuri arborescente pentru a reprezenta ierarhii parte-întreg. Composite permite clienților să trateze obiectele individuale și compozițiile de obiecte în mod uniform.
Implementarea modelului Composite este ușoară. Clasele compozite extind o clasă de bază care reprezintă obiecte primitive. Figura 1 prezintă o diagramă de clasă care ilustrează structura tiparului Composite.
În diagrama de clasă din figura 1, am folosit nume de clase din discuția despre modelul Composite din Design Pattern: Component
reprezintă o clasă de bază (sau, eventual, o interfață) pentru obiecte primitive, iar Composite
reprezintă o clasă compozită. De exemplu, clasa Component
ar putea reprezenta o clasă de bază pentru primitive grafice, în timp ce clasa Composite
ar putea reprezenta o clasă Drawing
. Clasa Leaf
din figura 1 reprezintă un obiect primitiv concret; de exemplu, o clasă Line
sau o clasă Text
. Metodele Operation1()
și Operation2()
reprezintă metode specifice domeniului implementate atât de clasa Component
, cât și de clasa Composite
.
Clasa Composite
menține o colecție de componente. În mod obișnuit, metodele Composite
sunt implementate prin iterația peste această colecție și invocarea metodei corespunzătoare pentru fiecare Component
din colecție. De exemplu, o clasă Drawing
ar putea să implementeze metoda draw()
astfel:
Pentru fiecare metodă implementată în clasa Component
, clasa Composite
implementează o metodă cu aceeași semnătură care itera peste componentele compusului, așa cum este ilustrat de metoda draw()
menționată mai sus.
Clasa Composite
extinde clasa Component
, astfel încât puteți transmite un compozit unei metode care așteaptă o componentă; de exemplu, luați în considerare următoarea metodă:
// 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();}
Metodei precedente i se transmite o componentă – fie o componentă simplă, fie un compozit – apoi invocă metoda draw()
a acelei componente. Deoarece clasa Composite
extinde Component
, metoda repaint()
nu trebuie să facă distincție între componente și compozite – ea invocă pur și simplu metoda draw()
pentru componentă (sau compozit).
Diagrama clasei modelului Composite din figura 1 ilustrează o problemă a modelului: trebuie să faceți distincția între componente și compozite atunci când faceți referire la o Component
și trebuie să invocați o metodă specifică compozitului, cum ar fi addComponent()
. De obicei, îndepliniți această cerință prin adăugarea unei metode, cum ar fi isComposite()
, la clasa Component
. Această metodă returnează false
pentru componente și este suprascrisă în clasa Composite
pentru a returna true
. În plus, trebuie, de asemenea, să transformați referința Component
într-o instanță Composite
, după cum urmează:
Rețineți că metodei addComponent()
i se transmite o referință Component
, care poate fi fie o componentă primitivă, fie o componentă compozită. Deoarece acea componentă poate fi un compozit, puteți compune componentele într-o structură arborescentă, așa cum indică citatul menționat mai sus din Design Patterns.
Figura 2 prezintă o implementare alternativă a modelului Composite.
Dacă implementați modelul Composite din figura 2, nu trebuie să faceți niciodată distincția între componente și compuși și nu trebuie să transformați o referință Component
într-o instanță Composite
. Astfel, fragmentul de cod enumerat mai sus se reduce la o singură linie:
...component.addComponent(someComponentThatCouldBeAComposite);...
Dar, dacă referința Component
din fragmentul de cod precedent nu se referă la un Composite
, ce ar trebui să facă addComponent()
? Acesta este un punct major de dispută cu implementarea modelului Composite din figura 2. Deoarece componentele primitive nu conțin alte componente, adăugarea unei componente la o altă componentă nu are sens, astfel încât metoda Component.addComponent()
poate eșua în mod silențios sau poate arunca o excepție. În mod obișnuit, adăugarea unei componente la o altă componentă primitivă este considerată o eroare, astfel încât aruncarea unei excepții este poate cel mai bun mod de acțiune.
Atunci care implementare a modelului Composite – cea din figura 1 sau cea din figura 2 – funcționează cel mai bine? Acesta este întotdeauna un subiect de mare dezbatere între cei care implementează modelul Composite; Design Patterns preferă implementarea din Figura 2 deoarece nu trebuie să faceți niciodată distincția între componente și containere și nu trebuie să efectuați niciodată un cast. Personal, prefer implementarea din Figura 1, deoarece am o aversiune puternică față de implementarea unor metode într-o clasă care nu au sens pentru acel tip de obiect.
Acum că ați înțeles modelul Composite și cum îl puteți implementa, să examinăm un exemplu de model Composite cu cadrul Apache Struts JavaServer Pages (JSP).
Patronul Composite și Struts Tiles
Cadrul Apache Struts include o bibliotecă de etichete JSP, cunoscută sub numele de Tiles, care vă permite să compuneți o pagină Web din mai multe JSP-uri. Tiles este de fapt o implementare a modelului J2EE (Java 2 Platform, Enterprise Edition) CompositeView, la rândul său bazat pe modelul Design Patterns Composite. Înainte de a discuta despre relevanța tiparului Composite pentru biblioteca de etichete Tiles, să trecem în revistă mai întâi raționamentul pentru Tiles și modul în care îl folosiți. Dacă sunteți deja familiarizat cu Struts Tiles, puteți să parcurgeți următoarele secțiuni și să începeți lectura la „Use the Composite Pattern with Struts Tiles.”
Nota: Puteți citi mai multe despre modelul J2EE CompositeView în articolul meu „Web Application Components Made Easy with Composite View” (JavaWorld, decembrie 2001).
Designerii construiesc adesea pagini Web cu un set de regiuni discrete; de exemplu, pagina Web din figura 3 cuprinde o bară laterală, un antet, o regiune de conținut și un subsol.
Site-urile web includ adesea mai multe pagini web cu layout-uri identice, cum ar fi layout-ul bară laterală/în antet/contenit/footer din Figura 3. Struts Tiles vă permite să reutilizați atât conținutul, cât și aspectul între mai multe pagini Web. Înainte de a discuta despre această reutilizare, să vedem cum se implementează în mod tradițional layout-ul din Figura 3 doar cu HTML.
Implementați manual layout-uri complexe
Exemplul 1 arată cum puteți implementa pagina Web din Figura 3 cu HTML:
Exemplul 1. Un aspect complex implementat manual
JSP-ul precedent are două dezavantaje majore: În primul rând, conținutul paginii este încorporat în JSP, astfel încât nu puteți refolosi nimic din el, chiar dacă bara laterală, antetul și subsolul sunt susceptibile de a fi aceleași în mai multe pagini Web. În al doilea rând, aspectul paginii este, de asemenea, încorporat în acel JSP, deci, de asemenea, nu îl puteți reutiliza, chiar dacă multe alte pagini web din același site web folosesc același aspect. Putem folosi acțiunea <jsp:include>
pentru a remedia primul inconvenient, după cum voi discuta în continuare.
Implementați machete complexe cu incluziuni JSP
Exemplul 2 prezintă o implementare a paginii Web din figura 3 care folosește <jsp:include>
:
Exemplul 2. Un aspect complex implementat cu JSP include
JSP-ul precedent include conținutul altor JSP-uri cu <jsp:include>
. Deoarece am încapsulat acel conținut în JSP-uri separate, îl puteți refolosi pentru alte pagini Web:
Pentru a fi complet, am enumerat mai jos JSP-urile incluse de JSP-ul precedent:
Exemplu 4. header.jsp
<font size='6'>Welcome to Sabreware, Inc.</font><hr>
Exemplu 5. content.jsp
<font size='4'>Page-specific content goes here</font>
<hr>Thanks for stopping by!
Chiar dacă JSP din Exemplul 2 folosește <jsp:include>
pentru a refolosi conținutul, nu puteți refolosi aspectul paginii, deoarece acesta este codat în acel JSP. Struts Tiles vă permite să reutilizați atât conținutul, cât și aspectul, așa cum este ilustrat în secțiunea următoare.
Implementați aspecte complexe cu Struts Tiles
Exemplul 7 prezintă pagina web din figura 3 implementată cu Struts Tiles:
Exemplul 7. Utilizați Struts Tiles pentru a încapsula layout-ul
JSP-ul precedent utilizează tag-ul <tiles:insert>
pentru a crea JSP-ul din Figura 3. Acel JSP este definit de o definiție tiles numită sidebar-header-footer-definition
. Această definiție se află în fișierul de configurare Tiles, care în acest caz este WEB-INF/tiles-defs.xml
, listat în Exemplul 8:
Exemplu 8. WEB-INF/tiles-defs.xml
Definiția Tiles precedentă specifică aspectul paginii, încapsulat în header-footer-sidebar-layout.jsp
, și conținutul paginii, încapsulat în sidebar.jsp
, header.jsp
, content.jsp
și footer.jsp
, așa cum sunt enumerate în exemplele 3-6. Exemplul 9 enumeră JSP-ul care definește aspectul-header-footer-sidebar-layout.jsp
:
JSP-ul precedent încapsulează aspectul și inserează conținutul în conformitate cu valorile specificate pentru regiunile sidebar, editor, conținut și subsol în fișierul de definiție Tiles, facilitând astfel reutilizarea atât pentru conținut, cât și pentru aspect. De exemplu, ați putea defini o altă definiție Tiles cu același aspect, aceeași bară laterală, editor și subsol, dar cu conținut diferit:
Pentru a crea JSP-ul definit de definiția tiles a-different-sidebar-header-footer-definition
, utilizați tag-ul <tiles:insert>
, astfel:
Grație Struts Tiles, puteți reutiliza atât conținutul, cât și aspectul, ceea ce se dovedește de neprețuit pentru site-urile web cu multe JSP-uri care împart aspectul și o parte din conținut. Dar dacă vă uitați cu atenție la codul din exemplele 7-9, veți observa că aspectul pentru regiunea sidebar este codificat în sidebar.jsp
, care este listat în exemplul 3. Aceasta înseamnă că nu puteți reutiliza acel layout. Din fericire, biblioteca de etichete Tiles implementează modelul Composite, care ne permite să specificăm o definiție de tip tiles – în loc de un JSP – pentru o regiune. În secțiunea următoare, explic cum să utilizăm această implementare a modelului Composite.
Utilizați modelul Composite cu Struts Tiles
Struts Tiles implementează modelul Composite, unde clasa Component
este reprezentată de JSP-uri și clasa Composite
este reprezentată de o definiție Tiles. Această implementare vă permite să specificați fie un JSP (o componentă), fie o definiție Tiles (un compozit) ca și conținut pentru regiunea unui JSP. Exemplul 10 ilustrează această caracteristică:
Exemplu 10. WEB-INF/tiles-defs.xml: Utilizați modelul Composite
Fisierul de configurare Tiles precedent definește două definiții Tiles: sidebar-definition
și sidebar-header-footer-definition
. sidebar-definition
este specificată ca valoare pentru regiunea barei laterale din sidebar-header-footer-definition
. O puteți specifica astfel deoarece Tiles implementează modelul Composite, permițându-i lui Tiles să specifice o definiție (un Composite
care este o colecție JSP) acolo unde, în mod normal, ați specifica un singur JSP (care este un Component
).
Dispoziția barei laterale este încapsulată în sidebar-layout.jsp
din Exemplul 11:
Exemplul 12 listează flags.jsp
, specificat ca fiind conținutul pentru regiunea top
a barei laterale, iar exemplul 13 listează sidebar-links.jsp
, specificat ca fiind regiunea bottom
a barei laterale:
Exemplul 12. flags.jsp
Acum, sidebar-definition
poate defini alte regiuni cu o componentă de sus și una de jos, deși probabil ar trebui să redenumiți această definiție în ceva mai generic, cum ar fi top-bottom-definition
.
Toate sunt compuse în zilele noastre
Patronul Composite este popular în cadrele de prezentare, cum ar fi Swing și Struts, deoarece vă permite să aninați containere prin tratarea componentelor și a containerelor lor exact la fel. Struts Tiles utilizează modelul Composite pentru a specifica un JSP simplu sau o definiție Tiles – care este o colecție de JSP-uri – ca și conținut al unui tile. Aceasta este o capacitate puternică care ușurează gestionarea site-urilor web mari cu layout-uri diferite.
Secțiunea „Tema de acasă de data trecută” de mai jos extinde discuția din acest articol prin internaționalizarea aplicației precedente cu o acțiune Struts și JSP Standard Tag Library (JSTL).
Treabă de casă
Discuțiți modul în care Swing implementează modelul compozit cu clasele Component
și Container
.
Terenul de lucru de data trecută
În ultima temă vi s-a cerut să descărcați Struts de la http://jakarta.apache.org/struts/index.html și să implementați propria clasă de acțiune Struts.
Pentru această temă, am decis să implementez o acțiune Struts împreună cu JSP Standard Tag Library (JSTL) pentru a internaționaliza aplicația Web din Exemplul 1. Deși Struts oferă infrastructura necesară pentru a internaționaliza aplicațiile Web, ar trebui să folosiți JSTL pentru această sarcină, deoarece JSTL este un standard. La un moment dat, capacitățile de internaționalizare Struts vor fi probabil depreciate sau integrate cu JSTL.
După ce am internaționalizat aplicația Web din Exemplul 1 cu o acțiune Struts și JSTL, am localizat acea aplicație pentru chineză. Figura H1 ilustrează rezultatul.
Nota: Nu știu nici măcar un cuvânt din chineză, cu atât mai puțin cum să scriu această limbă, așa că chineza din Figura H1 este fabricată din șiruri Unicode arbitrare. Dar arată bine, oricum.
Atenție, am specificat atributele href
în flags.jsp
din Exemplul 12 ca șiruri goale, astfel încât atunci când faceți clic pe steaguri, containerul servlet reîncarcă JSP-ul curent. Prin urmare, primul pas în vederea internaționalizării aplicației Web este specificarea unui URL pentru aceste atribute, așa cum este enumerat în exemplul H1:
Exemplu H1. flags.jsp
Url-ul pentru fiecare atribut href
este același: flags.do
. La această adresă URL sunt adăugați doi parametri de cerere: unul pentru locația corespunzătoare steagului și altul care reprezintă calea JSP-ului curent. Acest din urmă parametru de cerere se obține prin invocarea metodei Http.ServletRequest.getServletPath()
.
În descriptorul de implementare al aplicației, am mapat toate URL-urile care se termină în .do
către servlet-ul de acțiune Struts, astfel:
Exemplu H2. WEB-INF/web.xml (Excerpt)
Nota: Consultați articolul meu „Take Command of Your Software” (JavaWorld, iunie 2002) pentru mai multe informații despre Struts și servlet-ul de acțiune Struts.
În continuare, am mapat calea /flags
la acțiunea Struts actions.FlagAction
în fișierul de configurare Struts, listat în exemplul H3:
Exemplu H3. WEB-INF/struts-config.xml
Din cauza acestei corespondențe, URL-ul flags.do
face ca servlet-ul de acțiune Struts să invoce metoda actions.FlagAction.execute()
; prin urmare, dacă se face clic pe un steag, se va invoca metoda actions.FlagAction.execute()
. Exemplul H4 listează clasa actions.FlagAction
:
Exemplu H4. WEB-INF/classes/actions/FlagAction.java
Metoda actions.FlagAction.execute()
obține o referință la parametrul de cerere locale
și transmite această valoare metodei Config
a clasei set()
JSTL Config
. Această metodă stochează șirul de caractere care reprezintă locația în setarea de configurare FMT_LOCALE
, pe care JSTL o folosește pentru a localiza textul și a formata numerele, monedele, procentele și datele.
Acum că am specificat o locație pentru acțiunile de internaționalizare JSTL, voi specifica în continuare un pachet de resurse în descriptorul de implementare al aplicației, al cărui extras este listat în Exemplul H5:
Exemplul H5. WEB-INF/web.xml (Extras)
Numele de bază al pachetului de resurse resources
este specificat pentru parametrul de inițializare a contextului javax.servlet.jsp.jstl.fmt.localizationContext
. Aceasta înseamnă că JSTL va căuta un fișier numit resources_en.properties
atunci când locale-ul este setat la British English (en-GB
) și resources_zh.properties
atunci când locale-ul este setat la Chinese (zh-ZH
).
Acum că am specificat un pachet de resurse și un locale pentru acțiunile de internaționalizare JSTL, voi modifica în continuare fișierele JSP pentru a utiliza aceste acțiuni, așa cum demonstrează exemplele H6-H9:
Exemplu H7. header.jsp
Exemplu H8. content.jsp
Exemplele JSP H6-H9 utilizează acțiunea JSTL <fmt:message>
pentru a extrage șirurile Unicode din pachetul de resurse specificat în descriptorul de implementare. Exemplele H10 și H11 enumeră pachetele de resurse pentru engleză și, respectiv, chineză:
Exemplu H10. WEB-INF/classes/resources_en.properties
Exemplu H11. WEB-INF/classes/resources_zh.properties
Într-un e-mail adresat mie, Joseph Friedman mi-a scris:
În „Decorate Your Java Code” (JavaWorld, decembrie 2001), scrieți:
„Obiectul care înconjoară – cunoscut sub numele de decorator – se conformează interfeței obiectului pe care îl înconjoară, permițând decoratorului să fie folosit ca și cum ar fi o instanță a obiectului pe care îl înconjoară.”
Cu toate acestea, exemplul de cod decorează
FileReader
cu unLineNumberReader
și apeleazăreadLine()
, care nu se află în interfațaFileReader
; astfel, nu folosițiLineNumberReader
în mod transparent.
Atât FileReader
cât și LineNumberReader
sunt conforme cu interfața definită de clasa Reader
, pe care ambele o extind. Ideea este că puteți trece decoratorul la orice metodă care se așteaptă la o referință la un Reader
. Aceste metode ignoră cu desăvârșire capacitățile speciale ale decoratorului – în acest caz, capacitatea de a citi fișiere și de a produce numere de linie. Cu toate acestea, dacă știți despre aceste capacități speciale, puteți profita de ele.
Faptul că decoratorul posedă (una sau mai multe) metode care lipsesc obiectului decorat nu încalcă în nici un fel intenția modelului Decorator; de fapt, aceasta este caracteristica centrală a acestui model: adăugarea de funcționalitate în timpul execuției la un obiect prin decorarea recursivă a obiectelor.
Dacă vă uitați la Design Patterns pagina 173, veți vedea acest lucru:
Subclasele Decorator sunt libere să adauge operații pentru funcționalități specifice. De exemplu, operația
ScrollTo
a luiScrollDecorator
permite altor obiecte să deruleze interfața *dacă* știu că se întâmplă să existe un obiectScrollDecorator
în interfață.
DavidGeary este autorul cărții Core JSTL Mastering the JSP Standard TagLibrary, care va fi publicată în această toamnă dePrentice-Hall și Sun Microsystems Press; Advanced JavaServer Pages (PrenticeHall, 2001; ISBN: 0130307041); și al seriei Graphic Java (Sun MicrosystemsPress). David a dezvoltat timp de 18 ani software orientat pe obiecte cunumeroase limbaje orientate pe obiecte. De la publicarea cărții GOFDesign Patterns în 1994, David a fost un susținător activ al modelelor de proiectare și a folosit și implementat modele de proiectare în Smalltalk, C++ și Java. În 1997, David a început să lucreze cu normă întreagă ca autor și vorbitor și consultant ocazional. David este membru al grupurilor de experți care definesc biblioteca standard de etichete personalizate JSP și JavaServer Faces, și este uncontribuitor la cadrul Apache Struts JSP.
Învățați mai multe despre acest subiect
.