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

Î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.

Figura 1. O diagramă de clasă a modelului 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.

Figura 2. O diagramă alternativă a clasei Composite pattern

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.

Figura 3. Modelul Composite și Struts Tiles. Faceți clic pe miniatură pentru a vizualiza imaginea în mărime naturală.

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:

Exemplu 3. sidebar.jsp

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>

Exemplu 6. footer.jsp

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

Exemplu 9. 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 11. sidebar-layout.jsp

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

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

Figura H1. Internaționalizare cu o acțiune Struts. Faceți clic pe miniatură pentru a vedea imaginea în mărime naturală.

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:

Exemplul H6. sidebar-links.jsp

Exemplu H7. header.jsp

Exemplu H8. content.jsp

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

Email

Î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 un LineNumberReader și apelează readLine(), care nu se află în interfața FileReader; astfel, nu folosiți LineNumberReader î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 lui ScrollDecorator permite altor obiecte să deruleze interfața *dacă* știu că se întâmplă să existe un obiect ScrollDecorator î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

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.