51
8 Şabloane de proiectare (Design patterns) În domeniul proiectării software există soluţii reutilizabile pentru problemele ce apar mai des. Un şablon de proiectare nu este un element de proiectare aflat într-o formă finală, direct transformabilă în cod, ci doar o soluţie pentru o anumită problemă, soluţie care, în timp, s-a dovedit folositoare în situaţii asemănătoare. Şabloanele de proiectare orientate obiect conţin clase, relaţiile şi interacţiunile dintre ele. Clasele conţinute nu sunt într-o formă finală, structurile acestora putându-se extinde cu elementele specifice fiecărui proiect. Algoritmii nu sunt consideraţi ca fiind şabloane de proiectare deoarece ei rezolvă probleme de implementare. Folosirea şabloanelor de proiectare poate duce la creşterea atât a vitezei de dezvoltare a software-ului, cât şi a calităţii acestuia, prin utilizarea unor soluţii testate, care şi-au dovedit eficacitatea. Proiectarea software-ului presupune luarea unor decizii a căror corectitudine se dovedeşte mai târziu, la partea de implementare. Reutilizarea unor şabloane de proiectare ajută la prevenirea unor probleme majore şi îmbunătăţeşte claritatea codului pentru programatorii şi arhitecţii familiari cu aceste şabloane. Reutilizarea şabloanelor de proiectare este diferită de reutilizarea codului. Reutilizarea şabloanelor de proiectare este practic o reutilizare de idei şi nu de componente. În domeniul transformării celor mai utilizate şabloane de proiectare în componente se vorbeşte de o rată de succes de două treimi (Meyer şi Arnout). Nu orice şablon software este un şablon de proiectare. Şabloanele de proiectare privesc doar uşurarea muncii de proiectare a aplicaţiilor software. De exemplu, mai există în domeniul software aşa numitele şabloane arhitecturale (architectural patterns), ce descriu, aşa cum le sugerează şi denumirea, soluţiile unor probleme de arhitectură software. Şabloanele de proiectare pot fi grupate în mai multe categorii: şabloane creaţionale (creational patterns); o singleton; o builder (constructor); o metodă factory; 1

Cap 8 Sabloane de Proiectare

Embed Size (px)

Citation preview

Page 1: Cap 8 Sabloane de Proiectare

8 Şabloane de proiectare (Design patterns)

În domeniul proiectării software există soluţii reutilizabile pentru problemele ce apar mai des. Un şablon de proiectare nu este un element de proiectare aflat într-o formă finală, direct transformabilă în cod, ci doar o soluţie pentru o anumită problemă, soluţie care, în timp, s-a dovedit folositoare în situaţii asemănătoare.

Şabloanele de proiectare orientate obiect conţin clase, relaţiile şi interacţiunile dintre ele. Clasele conţinute nu sunt într-o formă finală, structurile acestora putându-se extinde cu elementele specifice fiecărui proiect.

Algoritmii nu sunt consideraţi ca fiind şabloane de proiectare deoarece ei rezolvă probleme de implementare.

Folosirea şabloanelor de proiectare poate duce la creşterea atât a vitezei de dezvoltare a software-ului, cât şi a calităţii acestuia, prin utilizarea unor soluţii testate, care şi-au dovedit eficacitatea. Proiectarea software-ului presupune luarea unor decizii a căror corectitudine se dovedeşte mai târziu, la partea de implementare. Reutilizarea unor şabloane de proiectare ajută la prevenirea unor probleme majore şi îmbunătăţeşte claritatea codului pentru programatorii şi arhitecţii familiari cu aceste şabloane.

Reutilizarea şabloanelor de proiectare este diferită de reutilizarea codului. Reutilizarea şabloanelor de proiectare este practic o reutilizare de idei şi nu de componente. În domeniul transformării celor mai utilizate şabloane de proiectare în componente se vorbeşte de o rată de succes de două treimi (Meyer şi Arnout).

Nu orice şablon software este un şablon de proiectare. Şabloanele de proiectare privesc doar uşurarea muncii de proiectare a aplicaţiilor software. De exemplu, mai există în domeniul software aşa numitele şabloane arhitecturale (architectural patterns), ce descriu, aşa cum le sugerează şi denumirea, soluţiile unor probleme de arhitectură software.

Şabloanele de proiectare pot fi grupate în mai multe categorii: şabloane creaţionale (creational patterns);

o singleton;o builder (constructor);o metodă factory;o clasă abstractă factory;o prototip;

şabloane structurale (structural patterns);o adaptor;o compozit;o facade;o proxy;

şabloane de comportament (behavioral patterns)o observer;o strategy;o command;o iterator;o memento;o visitor;o mediator;o lanţ de responsabilităţi (chain of responsability)

Pentru exemplificare vom crea în NetBeans două proiecte: un proiect UML Java platform Model şi un proiect Java Desktop Application. Pentru o mai bună organizare a

1

Page 2: Cap 8 Sabloane de Proiectare

claselor vom crea în ambele proiecte pachetele sursă: sabloane_creationale, sabloane_structurale şi sabloane_comportamentale.

În proiectul UML creăm o diagramă de clase şi dăm în spaţiul alb al acesteia click dreapta şi „Apply Design Pattern...”. Dacă un şablon nu presupune prea multe clase şi dorim să introducem şablonul direct în model fără să îl vizualizăm într-o clasă de diagrame putem da click dreapta pe nodul Model şi vom găsi aceeaşi opţiune „Apply Design Pattern...”. Wizard-ul ce se deschide ne dă posibilitatea să introducem din proiectul „GoF Design Patterns” în propriul proiect UML principalele şabloane de proiectare din literatura de specialitate (GoF vine de la „Gang of Four”, autorii „Design Patterns: Elements of Reusable Object-Oriented Software”, o carte de referinţă în literatura de specialitate).

Clasele proiectului UML pot fi generate automat în proiectul Java de la opţiunea „Generate Code” (click dreapta pe clasa UML).

2

Page 3: Cap 8 Sabloane de Proiectare

8.1 Şabloane creaţionale

Şabloanele creaţionale sunt utilizate de obicei pentru a separa crearea obiectelor de utilizarea lor. Scopul principal al unui astfel de demers este de a permite introducerea în sistem a noilor tipuri derivate fără a fi necesare schimbări asupra codului ce foloseşte clasele de bază.

8.1.1 Şablonul SingletonSingleton este un şablon folosit pentru a restricţiona instanţierea unei clase la un

singur obiect. Acest concept este uneori generalizat pentru a restricţiona instanţierea la un anumit număr de obiecte.

Şablonul singleton se implementează prin crearea unei singure clase ce conţine o asociere reflexivă (unul din propriile atribute este tot de tipul clasei). Elementul cheie al acestui şablon este faptul că atributul ce are chiar tipul clasei este de tip static. Modificatorul static folosit la declararea unui atribut sau la declararea unei metode arată apartenenţa membrului respectiv la clasă şi nu la o instanţă a ei (la un obiect). În cazul unui atribut static, ca în exemplul şablonului singleton, se alocă memorie o singură dată, la prima initializare a clasei. La urmatoarele instanţieri ale clasei nu se mai alocă memorie pentru un atribut static, dar toate obiectele din acea clasă pot accesa aceeaşi variabilă statică, aflată în aceeaşi zonă de memorie. Pe lângă cele specificate mai sus, o clasă singleton mai poate conţine atribute şi operaţii specifice domeniului în care este utilizat.

Şablonul singleton prevede şi existenţa unui constructor privat (dar acest constructor nu este definit şi în şablonul prestabilit din NetBeans). Dacă acest constructor nu ar fi privat clasa ar putea fi instanţiată de mai multe ori şi nu ar mai ieşi un singleton.

Neexistând constructorul, instanţa unică se obţine cu o metodă statică denumită în continuare instance( ) (tot un fel de metodă get). Această metodă fiind statică poate fi apelată cu sintaxa:

Obiect_returnat = clasă.metodă_statică();

şi ea ne va returna întotdeuna obiectul unic instanţiat (atributul static de acelaşi tip cu clasa în care este inclus).

Fig. 8.1 Reprezentarea unei clase singleton

Instanţa unică poate fi creată:

3

Page 4: Cap 8 Sabloane de Proiectare

1) implicit la definirea acestui element (o instanţiere nu tocmai leneşă, mai degrabă grăbită şi risipitoare), caz în care metoda instance( ) trebuie doar să returneze o referinţă la acest obiect:

public static Singleton instance() { return uniqueInstance; }

2) explicit în metoda statică instance( ), dacă o astfel de instanţă nu există deja (această modalitate de instanţiere este denumită instanţiere leneşă).

public static Singleton instance () { if(uniqueInstance == null) { uniqueInstance = new Singleton();} return uniqueInstance;}

Putem testa clasa Singleton la crearea a două obiecte: „marie” şi „aceeasiMarie”.

private sabloane_creationale.Singleton marie;private sabloane_creationale.Singleton aceeasiMarie;

marie = sabloane_creationale.Singleton.instance();marie.setSingletonData(3);String s = new Integer(marie.getSingletonData()).toString();System.out.println("Atributul singletonData primeste valoarea " + s);

aceeasiMarie = sabloane_creationale.Singleton.instance();String s = new Integer(aceeasiMarie.getSingletonData()).toString();System.out.println("Atributul singletonData avea deja valoarea " + s);

La lansarea codului de mai sus descoperim că, indiferent de ceea ce se spune „aceeasiMarie” are aceeaşi pălarie (pardon ... aceeaşi valoare a atributului singletonData) şi asta pentru că este aceeaşi „marie” (... acelaşi obiect uniqueInstance).

Exemple de utilizare a şablonului singleton:- clasa user într-o aplicaţie în care se doreşte gestiunea unitară a drepturilor unui

utilizator logat;- clasa destinată conexiunii cu baza de date atunci când se doreşte ca aplicaţia să

folosească la un moment dat o singură bază de date.Pot exista şi versiuni ale şablonului singleton, cele în care se limitează instanţierea

unei clase nu doar la o instanţă, ci la un număr finit de instanţe (de preferat nu număr mic).De exemplu, într-un joc de şah clasa jucător ar trebui să fie instanţiată numai de

două ori. Soluţia în acest caz presupune:- un constructor privat;- două atribute statice de tipul clasei (uniqueWhite şi uniqueBlack);- două metode statice care să returneze cele două instanţe unice

(getUniqueWhite şi getUniqueBlack).

4

Page 5: Cap 8 Sabloane de Proiectare

Fig. 8.2 Clasa singleton Player într-un joc de şah

Metodele statice din acest exemplu ar trebui să se asigure mai întâi că există instanţele statice corespunzătoare şi apoi să le iniţializeze, adică să le dea o nuanţă de alb sau de negru:

public Player getUniqueWhite() { if (uniqueWhite == null) { uniqueWhite = new Player(); color = "White"; } return uniqueWhite; }

Observaţie: în exemplul de mai sus nici nu este nevoie de o operaţie setColor, deoarece culoarea este stabilită doar la instanţierea jucătorului şi nemodificată pe parcursul jocului.

NetBeans ne dă posibiliatea să creăm propriile şabloane de proiectare pe lângă cele prestabilite folosind opţiunea Window\Other\UML Design Center. Aici putem crea un nou proiect cu şabloane, denumit eventual MyPatterns. În acest proiect putem specifica propriul singleton creând următoarea diagramă de clase:

Fig. 8.3 Diagrama de clase necesară creării propriului singleton

8.1.2 Metodă factoryTermenul factory (fabrică) este utilizat în acest context pentru a arăta locul

destinat construirii de obiecte. Fără a utiliza astfel de fabrici dezvoltatorii ar utiliza direct constructorii claselor pentru a obţine instanţe ale acestora.

Cu ajutorul acestor fabrici se separă logica realizării obiectelor de clasele acestora, în locul utilizării constructorilor fiind folosite alte metode, denumite metode factory.

Rolurile întâlnite la şablonul metodă factory sunt: Produs = clasa/clasele de tipul cărora obţinem obiecte

5

Page 6: Cap 8 Sabloane de Proiectare

Creator = clasa/clasele ce conţin metodele FactoryŞablonul metodă factory defineşte o interfaţă (Creator) destinată creării unui

obiect, dar lasă subclasele (ConcreteCreator) să decidă (prin suprascriere) care clasă (ConcreteProduct) va fi efectiv instanţiată.

Fig. 8.4 Şablonul metodă factory

Fig. 8.5 Exemplu de şablon metodă factory

Şablonul este folosit în ierarhiile de clase paralele, când obiectele dintr-o ierarhie creează obiecte corespunzătoare din cealaltă ierarhie. De exemplu, putem dezvolta o ierarhie de păsări (găină, raţă, struţ), ce produc anumite tipuri de ouă. Fiecărui element din ierarhia de păsări îi corespunde un anumit tip de ou (nu punem săraca găină să producă un ou de struţ, spre bucuria ei).

6

Page 7: Cap 8 Sabloane de Proiectare

În plus faţă de codul generat automat pe baza diagramei de mai sus va trebui sa suprascriem metodele creazaOu din clasele Gaina şi Strut, în aşa fel încât să facem legătura logică dintre cele două ierarhii de clase.

public final class gaina extends pasare {public final ou creazaOu(){

return new oudegaina();}

}

Şi

public final class strut extends pasare {public final ou creazaOu(){

return new oudestrut();}

}

În practică are sens să folosim şablonul când metodele de mai sus sunt complexe, presupun accesul la caracteristici ale mediul client şi calculează valori pentru atributele obiectelor obţinute.

Clientul acestui şablon doar crează un obiect de tipul unei anumite păsări (să spunem găină) şi prin utilizarea metodei creazaOu obţine un obiect de tipul Oudegaina, deşi nu a scris explicit în cod numele acestei clase.

public static void main(String[] args) {creationale.factory.gaina gaina;creationale.factory.ou oudeceva;gaina = new creationale.factory.gaina();oudeceva = gaina.creazaOu();System.out.println("A fost creat un " +

oudeceva.getClass().getSimpleName());}

Rezultatul afişat este: A fost creat un oudegainaMetodele factory pot accepta opţional parametri care definesc modul în care

obiectul este creat. Astfel putem crea variante simplificate ale acestui şablon, în care utilizam o singură ierarhie de clase, cea a Produselor şi o singură clasă Creator cu o metodă factory a cărui parametru să ajute la identificarea tipului concret de produs realizat.

Acest şablon poate fi implementat cu dificultate atunci când este folosit pentru clase ce au deja clienţi. Adică dacă începem prin aplicaţie să folosim constructorul implicit de ouă, e puţin cam târziu să mai introducem în ecuaţie şi pasărea cu metoda ei factory de făcut ouă (creazaOu).

8.1.3 Clasă abstractă factoryŞablonul clasă abstractă factory impune existenţa unui nivel suplimentar de

abstractizare faţă de şablonul metodă Factory şi nu exclude acest şablon.Conform acestui şablon se creează o clasă abstractă factory ce va sta la baza mai

multor clase concrete factory, deci nu putem avea versiuni simplificate ca în şablonul anterior în care să existe o singură clasă Creator (factory). Clasele factory conţin câte o metodă factory pentru fiecare tip de obiect ce trebuie creat.

7

Page 8: Cap 8 Sabloane de Proiectare

Şablonul abstract factory asigură o modalitate de grupare a mai multor metode factory ce au elemente (teme) comune.

Codul client lucrează doar cu tipul abstract de factory, nedepinzând de tipurile concrete. Conform acestui şablon, sunt create obiecte concrete, dar codul client le accesează doar prin interfaţa lor abstractă.

Adăugarea de noi tipuri concrete sistemului se face prin modificarea codului client, în aşa fel încât să folosească o nouă clasă concretă factory.

Fig. 8.6 Şablonul clasă abstractă factory

Şablonul clasă abstractă Factory este util atunci când o aplicaţie trebuie să creeze diferite grupuri de obiecte în funcţie de o anumită stare a mediului de execuţie. De exemplu, o aplicaţie poate fi gândită în aşa fel încât să afişeze diferite interfeţe grafice în funcţie de mărimea şi rezoluţia ecranului. Pentru ecrane mici aplicaţia poate apela la interfeţe grafice gândite să încapă într-un spaţiu astfel limitat. De exemplu, textele din interfaţă pot fi scrise în mod abreviat şi/sau cu font-uri mai mici, iar imaginile afişate ar avea un număr mai mic de pixeli şi de culori.

Fiecare din aceste interfeţe grafice urmează să implementeze aceeaşi interfaţă, adică sunt vizibile din cadrul aplicaţiei client în mod identic. În exemplul următor clasa abstractă InterfataGrafica are rolul de Factory şi ajută la crearea de obiecte (imagine, text, liste etc.) de diferite tipuri optimizate pentru diferite ecrane. Pentru fiecare grup de ecrane se crează clase concrete Factory: InterfataGraficaMica, InterfataGraficaMedie,

InterfataGraficaMare. În aplicaţia ce va folosi una din aceste interfeţe putem scrie:

Toolkit toolkit = Toolkit.getDefaultToolkit();Dimension scrnsize = toolkit.getScreenSize();int resolution = toolkit.getScreenResolution();System.out.println ("Screen width: " + scrnsize.getWidth());System.out.println ("Screen height: " + scrnsize.getHeight());System.out.println ("Screen resolution: " + resolution);InterfataGrafica myGUI = null;if (resolution < 40)

{myGUI = new InterfataGraficaMica();}if (resolution >= 40 && resolution<96)

{myGUI = new InterfataGraficaMedie();}if (resolution >= 96)

{myGUI = new InterfataGraficaMare();}System.out.println("Se foloseste o " + myGUI.getClass().getSimpleName());

8

Page 9: Cap 8 Sabloane de Proiectare

8.1.4 PrototipŞablonul constă în folosirea unei instanţe drept prototip pentru crearea (clonarea)

de noi obiecte.Şablonul protoype este folosit pentru:- a evita folosirea subclaselor concrete factory în aplicaţiile client;- a evita crearea de obiecte în mod clasic, cu metode constructor.Pentru implementarea acestui şablon se declară o clasă de bază abstractă ce

specifică o operaţie clonePrototype. Orice clasă ce are nevoie de un constructor polimorfic va deriva din clasa de bază abstractă şi implementează operaţia clonePrototype.

Fig. 8.7 Şablonul prototip

Operaţia clonePrototype poate fi folosită după crearea unei prime instanţe a clasei respective. A clona un obiect înseamnă să se creeze un nou obiect şi să i se modifice acestuia valorile atributelor cu valorile atributelor obiectului clonat.

Avantajele şablonului se văd atunci când realizarea acestei prime instanţe presupune un efort deosebit de timp sau resurse, efort ce nu mai este necesar de la a doua instanţă. Presupunând clonarea oiţei Dolly cel mai mnemonic exemplu despre clonare:

Fig. 8.8 Exemplu de şablon prototip

9

Page 10: Cap 8 Sabloane de Proiectare

Utilizarea şablonului de mai sus presupune implementarea metodei clonePrototype din clasa Sheep:

public Prototype clonePrototype () { Sheep clonedSheep = new Sheep(); clonedSheep.rasa = this.rasa; clonedSheep.culoare = this.culoare; clonedSheep.varsta = this.varsta; return clonedSheep; }

şi apelarea acesteia din programul client:

sabloane_creationale.Sheep dolly = new sabloane_creationale.Sheep();

dolly.setRasa("Chevlot");dolly.setCuloare("alba");dolly.setVarsta(6);

sabloane_creationale.Prototype dollysSyster = dolly.clonePrototype();

System.out.println("Rasa clonei este " + dollysSyster.getRasa());System.out.println("Culoarea clonei este " + dollysSyster.getCuloare());System.out.println("Varsta clonei este " + dollysSyster.getVarsta()+" ani");

8.1.5 BuilderŞablonul builder ajută la abstractizarea paşilor construirii unor obiecte, altfel spus

a reţetelor de fabricaţie. Obiectele de obţinut nu fac parte dintr-o anumită ierarhie de clase, dar există constrângerea ca valorile atributelor acestora să nu aibă sens decât într-o anumită combinaţie. Fiecare combinaţie de valori ale atributelor se obţine cu un anumit builder (cu o anumită reţetă de fabricaţie). Aceşti builder-i sunt organizaţi într-o ierarhie de clase.

Fig. 8.9 Şablonul builder

Clasa director este cea care construieşte efectiv produsul cu ajutorul unui builder (reţete de fabricaţie) pe care îl conţine. Metoda construct din clasa director include o secvenţă

10

Page 11: Cap 8 Sabloane de Proiectare

de apelări a metodelor de construire a părţilor (atributelor) produselor. Cum toate produsele (obiectele) obţinute sunt instanţe ale aceeleiaşi clase înseamnă că toate au aceleaşi părţi şi prin urmare toate se pot obţine prin respectarea aceleiaşi secvenţe de construire.

Pot exista variante ale acestui şablon în care Builder este o clasă abstractă şi nu o interfaţă. Motivul folosirii unei clase abstracte poate fi în acest caz agregarea în Builder a unui obiect produs protejat (protected).

Cel mai reuşit exemplu de utilizare a şablonului builder este cel de pe wikipedia unde clasa cu rolul product este clasa pizza, clasele cu rolurile concretebuilder sunt diferitele reţete de pizza, iar clasa cu rolul director este clasa bucătar (exemplul de pe wikipedia scris in C# este corect, cel în Java are lacune).

Fig. 8.10 Exemplu de şablon builder

În exemplul de mai sus, clasele reţetă şi pizza pot rămâne cu codul generat automat pe baza diagramei, dar:

- clasa bucătar trebuie să ştie ordinea în care se pregătesc diferitele părţi de pizza;

- clasa RetetaPizzaSalami trebuie să ştie cum se pregăteşte fiecare parte din pizza salami.

public class Bucatar {public Pizza gateste (Reteta oRetetaDePizza) {

oRetetaDePizza.pregatesteBlat(); oRetetaDePizza.pregatesteSos(); oRetetaDePizza.pregatesteContinut(); return oRetetaDePizza.getPizza(); }}

11

Page 12: Cap 8 Sabloane de Proiectare

public class RetetaPizzaSalami extends Reteta { public void pregatesteBlat () { pizza.setBlat("subtire"); }

public void pregatesteSos () { pizza.setSos("iute"); }

public void pregatesteContinut () { pizza.setContinut("salam"); }}

În aplicaţia client ajunge să îi apelăm unui bucătar metoda găteşte şi să îi spunem ce fel de pizza dorim, fără să cunoaştem detaliile reţetei de fabricaţie.

sabloane_creationale.Bucatar popescu = new sabloane_creationale.Bucatar();sabloane_creationale.RetetaPizzaSalami retetaPizzaSalami = new sabloane_creationale.RetetaPizzaSalami();sabloane_creationale.Pizza pizza = popescu.gateste(retetaPizzaSalami);

System.out.println("Pizza obtinuta contine "+ pizza.getContinut() + ", are blatul " + pizza.getBlat()+ " si sosul " + pizza.getSos() );

Avantajul acestui şablon constă în separarea reţetei de fabricaţie de beneficiarul produselor obţinute.

12

Page 13: Cap 8 Sabloane de Proiectare

8.2 Şabloane structurale

8.2.1 AdaptorAdaptorul (cunoscut şi sub numele de wrapper) este o clasă ce permite traducerea

unor interfeţe în alte interfeţe. Adaptorul permite unor clase cu interfeţe incompatibile să interacţioneze. Acest şablon este folositor atunci când o clasă deja implementată (clasa de adaptat sau clasa furnizor) asigură funcţionalităţile dorite, dar nu şi interfaţa dorită. Adaptorul ştie să răspundă cel puţin la interfaţa dorită de client, din acest motiv clasa adaptor este utilizată în mod direct de către clasa client. În acelaşi timp adaptorul ştie să utilizeze funcţionalităţile clasei de adaptat.

În acest fel, clasa client este independentă de structura interfeţei de adaptat.Cu un astfel de şablon clasa client poate modifica furnizorii de funcţionalităţi fără

să îşi modifice propria structură, doar utilizând alţi adaptori.Există două tipuri de adaptoare, în funcţie de modul în care adaptorul reuşeşte să

utilizeze funcţionalităţile clasei de adaptat: obiect adaptor şi clasă adaptor.

8.2.1.1 Obiect adaptor

Fig. 8.11 Şablonul obiect adaptor

În diagrama de mai sus o clasă Client ştie să folosească doar clase ce implementează interfaţa Target. Toată logica scrisă în clasa Client depinde de această interfaţă.

Ce ne facem dacă la un moment dat clasa Client trebuie să folosească şi clase care nu au implementată interfaţa Target, dar au aceeaşi funcţionalitate dorită de clasa Client (aşa cum este clasa Furnizor)?

Mai sus am scris îngroşat cuvântul „şi” pentru a arăta că rescrierea clasei Client este exclusă, ea trebuind să poată funcţiona şi cu vechile implementări ale interfeţei Target.

Soluţia propusă de şablonul obiect adaptor este de a crea o clasă Adapter care:- să conţină un atribut de tipul clasei de adaptat, clasa Furnizor;- să implementeze interfaţa Target;

13

Page 14: Cap 8 Sabloane de Proiectare

- să suprascrie metodele interfeţei Target (cum ar fi metoda request), în aşa fel încât la execuţia acestora să se apeleze metodele instanţei de tip Furnizor (de exemplu, metoda specificRequest).

În metoda request a adaptorului se scrie în acest caz:

public void request () { furnizor.specificRequest(); }

Un exemplu de utilizare a şablonului object adaptor este cel în care aplicaţiile client de messenger pot utiliza diferite servere de messenger.

Iniţial un client de messenger poate fi dezvoltat pentru un singur server, ulterior dorindu-se utilizarea şi a altor servere. Figura 8.12 face referire la clasele specifice unei astfel de aplicaţii iniţiale în care clientul, în metoda sendText, utilizează metoda sendText dintr-un anumit server.

Fig. 8.12 Diagrama claselor într-o aplicaţie de messenger

Pentru utilizarea unui alt server de messenger, clasa ServerMessenger se poate specializa într-un adaptor ce conţine ca şi membru un obiect de tipul unui alt server. Metoda sendText a adaptorului suprascrie metoda sendText a serverului iniţial, delegând activitatea de realizat membrului obiect de tipul celuilalt server, mai exact folosind metoda sendMessage a acestuia.

Fig. 8.13 Aplicaţie de messenger cu un object adapter

14

Page 15: Cap 8 Sabloane de Proiectare

Un alt exemplu de utilizare a şablonului obiect adaptor este cel dintr-un sistem B2B în care aplicaţia de achiziţie a unui magazin ştie să folosească iniţial doar o anumită aplicaţie de defacere instalată la un furnizor. În această formă iniţială a sistemului există doar elementele din stânga ale următoarei diagrame:

Fig. 8.14 Aplicaţie B2B cu un object adapter

Clasa Achizitie depinde de interfaţa IDesfacere măcar prin faptul că metoda cumpară apelează metoda vinde.

Dacă la un moment dat apare un Furnizor care are instalată o altă aplicaţie, ce nu implementează interfaţa IDesfacere, dar ajută tot la vânzarea de marfă, putem crea clasa AdapteazaSale.

Conform acestui şablon clasa AdapteazaSale:- conţine un atribut de tipul clasei de adaptat, clasa Sale;- implementează interfaţa IDesfacere;- suprascrie metoda vinde, în aşa fel încât aceasta să apeleze metodele

setCurrentProduct şi sell ale obiectului de tip Sale.La codul generat pe baza diagramei anterioare am adăugat un cod formatat aici cu

litere îngroşate pentru a înţelege ce mai rămâne de scris la mână din prezentul şablon.

public class Achizitie {

private IDesfacere mIDesfacere;

public void cumpara (String codProdus, Double cantitate, String furnizor) {//in functie de furnizor folosim anumite implementari if (furnizor.equals("furnizor1")) mIDesfacere = new Desfacere(); if (furnizor.equals("furnizor2")) mIDesfacere = new AdapteazaSale(); mIDesfacere.vinde(codProdus, cantitate); }}

public interface IDesfacere { public void vinde (String codProdus, Double cantitate);}

15

Page 16: Cap 8 Sabloane de Proiectare

public class Desfacere implements IDesfacere { public void vinde (String codProdus, Double cantitate) { System.out.println("Se foloseste clasa Desfacere pentru a cumpara produsul "+ codProdus); }}

public class AdapteazaSale implements IDesfacere {

private Sale mSale = new Sale();

public void vinde (String codProdus, Double cantitate) { int quantity = cantitate.intValue();//adaptam tipul parametrilor mSale.setCurrentProduct(codProdus);//lansam doua metode in loc de una mSale.sell(quantity); }}

public class Sale { private String currentProduct;

public void sell (int quantity) { System.out.println("Se foloseste clasa Sale pentru a cumpara produsul " + this.currentProduct); }

public String getCurrentProduct () { return currentProduct; }

public void setCurrentProduct (String val) { this.currentProduct = val; }}

Utilizarea şablonului presupune darea în execuţie a următoarelor rânduri de cod:

Achizitie achizitie = new Achizitie();achizitie.cumpara("12345", 3.14, "furnizor1");achizitie.cumpara("56789", 999., "furnizor2");

Rezultatul afişat este:Se foloseste clasa Desfacere pentru a cumpara produsul 12345Se foloseste clasa Sale pentru a cumpara produsul 56789

8.2.1.2 Clasă adaptoare

În şablonul anterior (obiect adaptor) se crează un adaptor pentru fiecare clasă Furnizor. Şablonul clasă adaptor presupune utilizarea unui singur adaptor pentru mai multe clase Furnizor. Acest tip de adaptor implementează toate interfeţele claselor Furnizor.

Cu acest şablon se poate crea şi un adaptor bidirecţional a două clase/interfeţe.

16

Page 17: Cap 8 Sabloane de Proiectare

Fig. 8.15 Şablonul class adapter cu implementare multiplă de interfeţe

Adaptorul descris în figura 8.15 poate fi folosit atât pentru o aplicaţie ce depinde de ClassA, dar care dispune de ClassB, cât şi invers.

Şablonul class adapter presupune folosirea moştenirii multiple, de unde îi vin şi anumite dezavantaje:

- nu toate mediile de programare suportă complet moştenirea multiplă (de aceea figura 8.15 face referire la implementările a două interfeţe şi nu la extinderea a două clase);

- pot apare conflicte între operaţiile celor două interfeţe când aceste operaţii au aceeaşi semnătură dar o semantică diferită.

În figura 8.16 exemplificăm o clasă Adaptor ce implementează trei interfeţe: IDesfacere, ISale, IVente. Practic luăm problema de la ultimul exemplu de object adapter (cea cu achiziţiile într-un sistem B2B) şi o rezolvăm cu un class adapter, pentru a înţelege mai bine diferenţele dintre acestea. Clasa cu rolul de Client (clasa Achizitie) nu mai conţine un membru ce implementează interfaţa IDesfacere, ci conţine un membru de tip Adapter, adică structura acesteia este pregătită din prima să utilizeze diferite sisteme de desfacere.

17

Page 18: Cap 8 Sabloane de Proiectare

Fig. 8.16 Exemplu de clasă adaptor

public class Achizitie { private Adaptor mAdaptor = new Adaptor();

public void cumpara (String codProdus, Double cantitate, String furnizor) {

if (furnizor.equals("furnizor1")) mAdaptor.setDesfacere(new Desfacere()); if (furnizor.equals("furnizor2")) mAdaptor.setSale(new Sale()); if (furnizor.equals("furnizor3")) mAdaptor.setVente(new Vente()); mAdaptor.vinde(codProdus, cantitate);

if (furnizor.equals("furnizor1")) mAdaptor.setDesfacere(null); if (furnizor.equals("furnizor2")) mAdaptor.setSale(null); if (furnizor.equals("furnizor3")) mAdaptor.setVente(null); }

18

Page 19: Cap 8 Sabloane de Proiectare

public Adaptor getAdaptor () { return mAdaptor; }

public void setAdaptor (Adaptor val) { this.mAdaptor = val; }}

public class Adaptor implements IDesfacere, ISale, IVente {

private Vente mVente; private Desfacere mDesfacere; private Sale mSale;

public void vinde (String codProdus, Double cantitate) { if (mDesfacere != null) mDesfacere.vinde(codProdus, cantitate); if (mSale != null) {mSale.setCurrentProduct(codProdus); mSale.sell(cantitate.intValue()); } if (mVente != null) {mVente.setProduit(codProdus); mVente.setQuantite(cantitate); mVente.vendre(); } }

public void sell (int quantity) {// aici putem sa scriem un cod asemanator cu cel din metoda vinde, dar care ar fi folosit atunci când adaptorul este folosit prin interfata ISale }

public void vendre () {// aici putem sa scriem un cod asemanator cu cel din metoda vinde, dar care ar fi folosit atunci când adaptorul este folosit prin interfata IVente }

// si in rest set-uri si get-uri ale clasei Adapter}

public class Desfacere implements IDesfacere {

public void vinde (String codProdus, Double cantitate) { System.out.println("Se foloseste clasa Desfacere pentru a cumpara produsul "+ codProdus); }}

public class Sale implements ISale {

private String currentProduct;

public void sell (int quantity) { System.out.println("Se foloseste clasa Sale pentru a cumpara produsul " + this.currentProduct); }

public String getCurrentProduct () { return currentProduct; }

19

Page 20: Cap 8 Sabloane de Proiectare

public void setCurrentProduct (String val) { this.currentProduct = val; }}

public class Vente implements IVente {

private String produit; private Double quantite;

public void vendre () { System.out.println("Se foloseste clasa Vente pentru a cumpara produsul "+ produit); }

public String getProduit () { return produit; }

public void setProduit (String val) { this.produit = val; }

public Double getQuantite () { return quantite; }

public void setQuantite (Double val) { this.quantite = val; }}

Utilizarea şablonului presupune darea în execuţie a următoarelor rânduri de cod:

Achizitie achizitie = new Achizitie();achizitie.cumpara("12345", 3.14, "furnizor1");achizitie.cumpara("56789", 999., "furnizor2");achizitie.cumpara("24680", 123., "furnizor3");

Rezultatul afişat este:Se foloseste clasa Desfacere pentru a cumpara produsul 12345Se foloseste clasa Sale pentru a cumpara produsul 56789Se foloseste clasa Vente pentru a cumpara produsul 24680

Alte exemple de utilizare a adaptoarelor:- wrapper-ele din bazele de date federative (DB2) care fac legătură între baze de

date eterogene;- coletele de transmis prin intermediul oficiilor poştale diferite.

8.2.2 CompozitŞablonul compozit permite unui grup de obiecte să fie tratat ca un singur obiect.

Cu ajutorul acestui şablon o operaţie specifică elementelor componente poate fi lansată şi pentru un agregat, situaţie în care operaţia se lansează pentru toate elementele agregate.

Una din situaţiile în care şablonul compozit se face util este cea în care se gestionează structuri ierarhice. Diferenţa structurală dintre noduri şi frunze face ca lucrul cu structuri ierarhice să fie complex. Soluţia propusă de şablon este de a obţine elementul

20

Page 21: Cap 8 Sabloane de Proiectare

compozit (nodul în cazul structurilor ierarhice) din specializarea clasei destinată componentelor (frunzelor).

Fig. 8.17 Şablonul compozit

Şablonul nu este util doar în cazul structurilor ierarhice, ci în toate relaţiile de compunere în care operaţiile unui grup de obiecte include operaţiile unui singur obiect.

Clasa destinată componentelor conţine pe lângă implementarea operaţiilor specifice domeniului afacerii şi declararea interfeţei pentru accesarea şi gestiunea componentelor copil (adăugare, ştergere, citire etc.).

Clasa compozită implementează metodele de manipulare a componentelor copil, iar în cazul operaţiilor specifice domeniului afacerii îşi delegă sarcinile operaţiilor corespunzătoare ale obiectelor copil.

În exemplul următor este vorba de ziarişti care fac parte din redacţii, redacţii care aparţin de anumite publicaţii (ziare, reviste etc.), publicaţii ce aparţin unor trusturi de presă. Şablonul compozit ne ajută în acest caz ca o operaţie specifică unui ziarist (cea de a ataca) să fie lansată (de un mogul) pentru un întreg trust, cu un efort minim, fără a apela fiecare ziarist în parte, ci apelând doar operaţia ataca a obiectului trust.

21

Page 22: Cap 8 Sabloane de Proiectare

Fig. 8.18 Şablonul compozit

public interface Componenta { public void ataca ();}

public class Ziarist implements Componenta { public void ataca () { System.out.println("Hau hau!"); }}

public class Grup implements Componenta { private ArrayList<Componenta> mComponenta = new ArrayList<Componenta>();

public void ataca () { for (Componenta componenta : mComponenta) { componenta.ataca(); } }

public void add (Componenta c) { mComponenta.add(c); }

public void remove (Componenta c) { mComponenta.remove(c); }}

Dacă declaraţiile de mai sus ar face parte din pachetul sabloane_structurale, intrând în pielea unui mogul am putea utiliza codul următor:

sabloane_structurale.Grup trust = new sabloane_structurale.Grup();sabloane_structurale.Grup ziar = new sabloane_structurale.Grup();sabloane_structurale.Grup redactie = new sabloane_structurale.Grup();sabloane_structurale.Ziarist popescu = new sabloane_structurale.Ziarist();sabloane_structurale.Ziarist ionescu = new sabloane_structurale.Ziarist();sabloane_structurale.Ziarist altescu = new sabloane_structurale.Ziarist();redactie.add(popescu);redactie.add(ionescu);

22

Page 23: Cap 8 Sabloane de Proiectare

redactie.add(altescu);ziar.add(redactie);trust.add(ziar);trust.ataca();

8.2.3 Facade (faţadă)

O faţadă este un obiect ce asigură o interfaţă simplificată spre un grup de clase. Cu ajutorul acestui şablon o bibliotecă software devine mai uşor de înţeles şi de utilizat, reducându-i dependenţa de restul codului (scade cuplarea).

O faţadă este recomandată şi atunci când trebuie să folosim o colecţie de clase ce folosesc interfeţe prost proiectate sau greu de înţeles, în speranţa că interfaţa faţadei va rezolva aceste probleme.

Fig. 8.19 Şablonul facade

23

Page 24: Cap 8 Sabloane de Proiectare

În exemplul din figura 8.19 cel care crează obiectele de tip tranzacţie (dintr-o interfaţă utilizator sau dintr-o procedură de import) nu trebuie să cunoască detaliile faţadei (claselor de contabilitate şi gestiune), ci doar că o tranzacţie, după ce a fost creată, trebuie transmisă faţadei de tip ContabilitateSiGestiune pentru înregistrarea acesteia în contabilitate şi afectarea gestiunilor corespondente:

//se creaza tranzactia cu datele din GUI/importTranzactie tranzactie = new Tranzactie();tranzactie.setSuma(11.);tranzactie.setTip("BUY");//se creaza si se foloseste fatada fără să se cunoască detaliile eiContabilitateSiGestiune fatada = new ContabilitateSiGestiune();fatada.setTranzactie(tranzactie);fatada.contabilizeaza();

În acest caz metoda contabilizeaza din faţada ContabilitateSiGestiune include toată secvenţa de apelări a claselor: RegistruJurnal, CarteaMare, RegistruDeCasa, RegistruDeBanca, Stocuri.

8.2.4 ProxyLa modul general un proxy este o resursă (clasă, server etc.) care funcţionează ca o

interfaţă pentru altceva. Un proxy nu face decât să delege acţiunile mai departe unei alte clase.

Fig. 8.20 Şablonul proxy

Există patru situaţii în care se recomandă să folosim un astfel de şablon:1) Virtual proxy = o clasă de acces către obiectele unei alte clase ce

presupune utilizarea multor resurse; obiectul real este creat la prima cerere de acces la acesta.

2) Remote proxy = asigură o reprezentare locală pentru un obiect dintr-un spaţiu diferit de memorie; în RPC şi CORBA o astfel de funcţionalitatea o asigură un „stub”.

3) Protective proxy = clasă de control al accesului la obiecte mai sensibile din punct de vedere al securităţii. Obiectul proxy verifică dacă expeditorul are dreptul să transmită mesaje destinatarului.

24

Page 25: Cap 8 Sabloane de Proiectare

4) Smart proxy = clasă care nu doar transmite mai departe o operaţie de realizat, ci realizează în plus o serie de acţiuni, cum ar fi:a. numără referirile la un anumit obiect;b. încarcă obiecte persistente la prima utilizare;c. verifică dacă obiectul adevărat este blocat pentru alte accese (asigură prelucrări

tranzacţionale şi diferite niveluri de izolare)

Exemplificăm un protective proxy:

//atat obiectul proxy cat si realSubject implementeaza aceeasi interfatapublic interface Subject { public void request ();}

public class RealSubject implements Subject { public void request () { System.out.println(this.getClass().getSimpleName() + " executa operatia dorita!"); }}

public class Proxy implements Subject {

private RealSubject mRealSubject = new RealSubject(); public void request () {

//daca numele utilizatorului sistemului de operare = .... if (System.getProperty("user.name").contentEquals("Florin")) { mRealSubject.request(); } else { System.out.println(this.getClass().getSimpleName() + " refuza executia operatiei dorite!"); } }}

8.2.5 Decorator

Şablonul decorator oferă o modalitate de a adăuga comportament unor obiecte individuale fără a fi necesară crearea unei noi clase pentru a realiza acest lucru. Adăugarea de noi funcţionalităţi obiectelor poate fi făcută conform acestui şablon în mod dinamic, în timpul execuţiei programului. Soluţia derivării este şi ea acceptabilă în general, dar este important de ştiut că nu este unica soluţie de adăugare a unor noi comportamente şi că se pot evita ierarhii stufoase. Acest şablon adaugă funcţionalităţi unor instanţe specifice ale unei clase şi nu întregii clase, permiţând adaptarea clasei respective, fără a crea clase derivate care să ducă la ierarhii complexe.

Soluţia şablonului constă în înglobarea uneia sau mai multor componente într-un obiect (cu rolul „decorator”), fiecare componentă conţinând metodele noii funcţionalităţi.

25

Page 26: Cap 8 Sabloane de Proiectare

Fig. 8.21 Şablonul DecoratorInterfaţa Component defineşte interfaţa obiectelor ce adaugă noi funcţionalităţi.Interfaţă Decorator este destinată obiectelor ce vor beneficia la run-time de noi

functionalităţi.Clasa ConcreteDecorator este o implementare a interfeţei Decorator. Metoda

addedBehavior este doar un exemplu de specializare şi nu face parte din specificul şablonului, ci din contra: specificul şablonului este de adăuga funcţionalităţi în componente concrete şi nu direct prin astfel de noi metode.

În exemplul următor vom utiliza clase abstracte în loc de interfeţe pentru a putea include într-un Decorator atribute nonstatice. El face referire la un grup de clase de control menit să gestioneze drepturile accesibile inclusiv prin interfeţe grafice.

Fig. 8.22 Exemplu de şablon decorator

Clasa abstractă GrupDrepturi are rolul de Decorator, deoarece beneficiază de noi funcţionalităţi cu ajutorul colecţiei de drepturi. Un Decorator concret este GrupDrepturiPentruContabili.

Clasa abstractă Drept are rolul de componentă.Clasa DreptModulTranzactie este o componentă concretă, ce include metode

pentru utilizarea tuturor drepturilor specifice unui Modul de Tranzactie.

26

Page 27: Cap 8 Sabloane de Proiectare

8.3 Şabloane comportamentale

8.3.1 MediatorDe obicei o aplicaţie este alcătuită dintr-un număr important de clase. Cu cât sunt

mai multe clase într-o aplicaţie, problema comunicării între obiecte devine mai complexă, ceea ce face programul mai greu de citit şi întreţinut. Modificarea programelor, în astfel de situaţii, devine mai dificilă din moment ce orice modificare poate afecta codul din mai multe clase.

Cu ajutorul şablonului mediator comunicaţia dintre obiecte este încapsulată în obiectul mediator. Obiectele nu mai comunică direct între ele, ci comunică în schimb prin intermediul mediatorului. Acest lucru reduce dependenţa între obiecte, micşorând cuplarea.

În contextul acestei diagrame termenul de coleg este utilizat pentru a desemna un obiect ce doreşte să comunice cu alte obiecte din acelaşi grup, un grup având un singur moderator.

Fig. 8.23 Şablonul mediator

Mediator - defineşte interfaţa de comunicare între obiecteConcreteMediator - implementează interfaţa Mediator şi coordonează comunicarea

între objecte. Ştie care sunt toate obiectele ce doresc să comunice şi scopurile acestor comunicări.

ConcreteColleague – comunică cu alte obiecte “colegi” cu ajutorul mediatorului.

Şablonul mediator se poate combina cu şablonul singleton, dacă o clasă concretă mediator are sens să fie instanţiată o singură data. Într-un astfel de caz, dacă aplicaţia abstractizează mai multe grupuri de colegi, pentru fiecare grup se poate crea câte o clasă concretă mediator care să fie instanţiată o singură dată.

Şablonul mediator nu trebuie confundat cu şablonul proxy. Diferenţa principală este faptul că mediatorii şi colegii nu sunt creaţi pe baza aceleiaşi interfeţe (sau clase abstracte). Un proxy este „imaginea” unui alt obiect, prin urmare structura unui proxy depinde destul de mult de structura obiectului destinaţie. Nu degeaba şablonul proxy este inclus în grupul şabloanelor structurale. Structura unui mediator nu este imaginea în oglindă a unui obiect coleg, ceea ce îi dă posibilitatea să transmită mesajele spre mai multe tipuri de clase concrete de colegi, altfel spus să medieze mesajele între colegi cu structuri diferite.

Există asemănări între şablonul mediator şi alte şabloane la lista de avantaje:

27

Page 28: Cap 8 Sabloane de Proiectare

un coleg (binevoitor) poate să transmită „anonime” (avantaj întâlnit şi la proxy); un coleg (răspândac) poate să transmită acelaşi mesaj mai multor obiecte (avantaj

întâlnit şi la şablonul compozit); un mediator (securist) poate să folosească liste de control al accesului (avantaj

întâlnit şi la proxy); un mediator (mai leneş) poate aştepta mai multe mesaje de la colegi pentru a lansa

o anumită operaţiune (avantaj specific şablonului mediator).Exemplu de folosire a şablonului moderator: organizarea unor vizite (de exemplu

la Moscova) poate fi realizată prin intermediul unui moderator (de exemplu ambasadorul rus la Bucureşti). Utilizând terminologia acestui şablon, colegi sunt persoanele ce urmează să se întâlnească. Din păcate, şablonul mediator nu reflectă şi relaţii de subordonare între „colegi”.

Un alt exemplu (total rupt de cel anterior) este cel al unei reţele de spionaj în care colegi sunt spionii şi persoanele decidente din structurile de informaţii. Mediatorii în acest exemplu sunt persoanele de legătură, care fac ca de cele mai multe ori colegii să nu se cunoască între ei.

8.3.2 Observator

Şablonul observator este un şablon de proiectare în care un obiect gestionează o listă cu proprii dependenţi, pe care îi anunţă automat de eventualele modificări de stare, de obicei prin apelarea anumitor metode.

De multe ori acest şablon este folosit pentru implementarea sistemelor distribuite.

Fig. 8.24 Şablonul observator

28

Page 29: Cap 8 Sabloane de Proiectare

Clasa Subject este o clasă abstractă (sau o interfaţă) ce asigură ataşarea şi scoaterea observatorilor. Clasa conţine pe lângă o listă privată cu observatori şi următoarele metode: attach( ) – adaugă un nou observator în listă; detach( ) – elimină un observator din listă; notifyObserver ( ) – anuntă fiecare observator asupra unei schimbări de stare prin apelarea

metodelor update( ) ale acestora.Clasa ConcreteSubject este elementul de interes al observatorilor. Ea trimite o

notificare tuturor observatorilor prin apelarea metodei notifyObserver( ) din clasa ei părinte. Clasa ConcreteSubject conţine pe lângă interfaţa Subject metoda GetState ce returnează starea subiectului de observat.

Clasa Observer defineşte o interfaţă pentru anunţarea tuturor observatorilor asupra modificărilor survenite în subiect. Interfaţa constă într-o metodă ce va fi suprascrisă de fiecare observator concret.

Clasa ConcreteObserver gestionează o referinţă către clasa ConcreteSubject şi conţine operaţia update( ). Când acestă operaţie este apelată de către subiect, ConcreteObserver apelează operaţia GetState a subiectului pentru a-şi actualiza informaţia privind starea subiectului. Operaţia update( ) poate primi eventual parametri cu informaţii generale ale evenimentului apărut, informaţii utile observatorului.

public class Observer { public void update () { }}

public abstract class Subject {

private ArrayList<Observer> mObserver = new ArrayList<Observer>();

public void attach (Observer o) { mObserver.add(o); }

public void detach (Observer o) { mObserver.remove(o); }

public void notifyObserver () { for (Observer o : mObserver){ o.update(); } }}

public class ConcreteSubject extends Subject {

private String state;

public String getState () { return state; }

public void setState (String val) { this.state = val; System.out.println("Subiectul " + this.toString() + " incepe sa isi anunte toti observatorii de schimbarea starii!"); this.notifyObserver(); }

29

Page 30: Cap 8 Sabloane de Proiectare

}

public class ConcreteObserver extends Observer {

private ConcreteSubject mConcreteSubject = new ConcreteSubject();

public void update () { System.out.println("Observatorul " + this.toString() + " a fost anuntat de schimbarea starii Subiectului " + mConcreteSubject.toString() + "!"); System.out.println("Observatorul " + this.toString() + " citeste noua stare " + mConcreteSubject.getState() + "!"); }

public ConcreteSubject getmConcreateSubject () { return mConcreteSubject; }

public void setmConcreteSubject (ConcreteSubject s) { mConcreteSubject = s; }}

Codul care utilizează şablonul observator:

ConcreteSubject subj = new ConcreteSubject();ConcreteObserver obs1 = new ConcreteObserver();ConcreteObserver obs2 = new ConcreteObserver();ConcreteObserver obs3 = new ConcreteObserver();

subj.attach(obs1);obs1.setmConcreteSubject(subj);subj.attach(obs2);obs2.setmConcreteSubject(subj);subj.attach(obs3);obs3.setmConcreteSubject(subj);subj.setState("DE TEST");

Şablonul observator este utilizat atunci când modificarea stării unui obiect afectează alte obiecte şi nu se ştie la momentul scrierii codului exact ce obiecte vor trebui anunţate.

Exemplu de folosire a şablonului observer: implementrarea modurilor de lucru, când într-o fereastră de dialog utilizarea unui obiect poate presupune disponibilizarea sau indisponibilizarea altor obiecte. În terminologia acestui şablon toate obiectele de pe un formular sunt observatori, iar subiectul concret de observat este chiar formularul. Fiecare obiect în parte nu trebuie să modifice direct celelalte obiecte de pe formular pentru că ar trebui să cunoască mulţimea acestora.

Aceeaşi funcţionalitate a modurilor de lucru ar putea fi implementată şi cu ajutorul şablonului mediator.

8.3.3 Lanţ de responsabilităţi

Şablonul lanţului de responsabilităţi (chain of responsibility) este un şablon comportamental care permite evitarea cuplării directe a expeditorului unei cereri cu un anumit destinatar, folosindu-se în acest sens clase intermediare.

30

Page 31: Cap 8 Sabloane de Proiectare

Fig. 8.25 Şablonul lanţ de responsabilităţiConform acestui şablon un client cere unui obiect ConcreteHandler să fie realizată

o acţiune, dar acesta poate să transmită sarcina unui alt obiect ConcreteHandler în funcţie de un anumit algoritm.

Avantajele unui astfel de şablon:- expeditorul poate să nu cunoască exact care este destinatarul final al cererii sale, pe el

interesându-l doar ca respectiva sarcină să fie îndeplinită (asemănare cu şabloanele proxy şi mediator);

- clasele intermediare pot alege destinatarii, gestionând eventual şi gradul de solicitare al acestora şi gradul de solicitare a sistemelor de calcul pe care rulează aceştia (asemănare cu şablonul proxy, pentru că şi un proxy poate să aleagă RealSubject în funcţie de gradul de solicitare);

- o cerere poate fi procesată de mai mulţi destinatari, în acelaşi timp, secvenţial sau respectând chiar anumite fluxuri de cereri (asemănare cu şablonul mediator);

- clasele intermediare pot realiza log-uri ale cererilor (asemănare cu şabloanele proxy, mediator, observator etc.);

- lipsa oricărui potenţial destinatar poate fi aflată de către expeditor printr-un mesaj primit de la clasele intermediare (asemănare cu şabloanele proxy şi mediator).

Din cele de mai sus reies o serie de asemănări între şablonul mediator şi lanţul de responsabilităţi, mai exact rolul Mediator seamănă cu ConcreateHandler şi rolul Coleg seamănă cu rolul Client.

Deosebirile dintre şablonul mediator şi lanţul de responsabilităţi constau în:- un mediator este de obicei singur pentru un grup de colegi, dar obiectele de tip

ConcreateHandler au sens să fie mai multe (ca să iasă lanţul);- mediatorul nu este un obiect care să şi execute efectiv acţiunea cerută, dar în

lanţul de responsabilităţi, un obiect de tip ConcreateHandler va realiza acţiunea cerută;- în şablonul mediator este vorba de mai mulţi colegi, dar la un lanţ de

responsabilităţi există un singur obiect cu rolul de client.Deosebirea principală dintre şablonul proxy şi lanţul de responsabilităţi este faptul

că un ConcreateHandler din lanţul de responsabilităţi este în acelaşi timp un proxy (poate da mai departe sarcina), dar şi un RealSubject (poate executa sarcina).

Un exemplu de lanţ de responsabilităţi este modul de autorizare a unor tranzacţii de către anumiţi angajaţi în funcţie de mărimea acestor tranzacţii. Aprobarea unui credit poate fi făcută la nivelul unui ofiţer de credit, manager al departamentului de credite, şef de filială sau sucursală şi în rare cazuri un credit are nevoie şi de aprobarea managerilor din centrală.

Clasa Functie are rolul de ConcreteHandler, în ea existând:

31

Page 32: Cap 8 Sabloane de Proiectare

- un atribut cu obiectul ce reprezintă nivelul ierarhic superior (un obiect creat tot pe baza clasei Functie);

- un atribut ce ne indică limita creditului ce poate fi aprobat fără a fi trimis şi la nivelul ierarhic superior.

Fig. 8.26 Exemplu de utilizare a şablonului lanţ de responsabilităţi

Metoda proceseazăDosarCredit poate fi implementată astfel:

public void proceseazaDosarCredit( Credit c ) { if ( c.suma <= limitaCredit ) { //procesează efectiv dosarul în sensul aprobării sau respingerii lui } else if ( superior != null ) superior.proceseazaDosarCredit(c); }

Exemplul de mai sus poate fi extins prin crearea a câte o clasă specializată din clasa Functie pentru fiecare funcţie (ofiţer de credit, manager al departamentului de credite, şef de filială sau sucursală, manager din centrală) şi suprascrierea metodei proceseazăDosarCredit în aşa fel încât algoritmul de decizie privind cui îi cade în sarcină dosarul respectiv să difere la fiecare nivel.

Există variante ale acestui şablon în care obiectele de tip ConcreateHandler:- retransmit cererea mai multor obiecte, formându-se arbori de responsabilităţi;- retransmit recursiv cererea; - rezolvă o parte a problemei şi transmit mai departe doar partea neprocesată din

comandă.

Şablonul seamănă cu aruncarea mâţei moarte în ograda vecinului, în funcţie de starea de putrefacţie a bietului animal. Cu cât starea de putrefacţie este mai mare cu atât pisica va ajunge la un vecin mai îndepărtat (în speranţa că toţi vecinii vor păstra aceeaşi direcţie şi fiecare vecin are propria limită de suportare a mirosului). Biata pisică se opreşte la primul vecin care îi suportă mirosul (dacă nu rămâne în perpetuum mobile).

32

Page 33: Cap 8 Sabloane de Proiectare

8.3.4 Memento (Amintire)

Şablonul amintire (memento) este un şablon comportamental destinat salvării diferitelor stări curente ale unor obiecte şi revenirea la aceste stări.

În acest şablon se foloseşte o clasă de amintire (Memento) ce conţine aceleaşi proprietăţi de stare ca şi clasa obiectelor de salvat (Originator). Obiectele de amintire se pot gestiona eventual într-o colecţie (Caretaker).

Obiectul de salvat trebuie să conţină câte o metodă pentru fiecare din aceste două acţiuni: salvare (saveToMemento) şi revenire la stare anterioară (restoreFromMemento).

Fig. 8.27 Şablonul memento

public class Memento {

private String stare1; private int stare2;

/*plus metodele get si set specifice acestor atribute*/}

public class Caretaker {

private ArrayList<Memento> mMemento = new ArrayList<Memento>();

public void addMemento (Memento m) { mMemento.add(m); }

public Memento getMemento (int index) { return mMemento.get(index); }/*plus metodele get si set specifice acestor atribute*/}

public class Originator { private Caretaker c = new Caretaker(); private String stare1; private int stare2;

public void saveToMemento () { Memento m = new Memento();

33

Page 34: Cap 8 Sabloane de Proiectare

m.setStare1(stare1); m.setStare2(stare2); c.addMemento(m); }

public void restoreFromMemento (int index) { Memento m = c.getMemento(index); this.stare1 = m.getStare1(); this.stare2 = m.getStare2(); }/*plus metodele get si set specifice acestor atribute*/}

Clientul care ar folosi un astfel de şablon ar începe prin a modifica stările obiectului Originator şi ar utiliza din când în când metoda saveToMemento pentru a salva diferitele stări ale acestuia. Revenirea la o stare anterioară se face ştiind un număr de ordine al acesteia în cadrul colecţiei de stări (numărătoarea începe de la 0, cel puţin în exemplul următor).

Originator orig = new Originator();orig.setStare1("ALFA");orig.setStare2(6);orig.saveToMemento();orig.setStare1("BETA");orig.setStare2(7);orig.saveToMemento();orig.setStare1("GAMA");orig.setStare2(8);orig.saveToMemento();orig.restoreFromMemento(1);/*aici se revine la starile BETA & 7*/System.out.println(orig.getStare1());System.out.println(Integer.toString(orig.getStare2()));

8.3.5 Strategie

Şablonul strategie ne ajută să alegem un anumit algoritm de utilizat în funcţie de un context, în momentul rulării. Şablonul conţine un grup de algoritmi, fiecare din aceştia fiind încapsulat într-un obiect (the tip ConcreteStrategy). Clienţii ce folosesc algoritmii (instanţe ale clasei cu rolul Context) nu depind de aceştia, variind în mod independent.

34

Page 35: Cap 8 Sabloane de Proiectare

Fig. 8.28 Şablonul strategieObiectele de tip context conţin câte un obiect strategy, dar alegerea unei strategii

concrete se realizează în funcţie de context.Conform şablonului strategy comportamentul unei clase nu ar trebui să fie

moştenit, ci specific contextului în care rulează şi încapsulat utilizând interfeţe.De exemplu, o aplicaţie de salarizare poate lua în calcul diferitele tipuri de

salarizare: în regie sau în acord. În practică, pentru fiecare dintre aceste tipuri de salarizare există diverse variante, putându-ne aştepta ca la un moment dat să se dorească adăugarea unui nou algoritm la lista iniţială.

Conform şablonului strategie realizăm următoarele clase:

Fig. 8.29 Exemplu de şablon strategie

Dacă ţinem morţiş să nu utilizăm şablonul strategie la realizarea unei aplicaţii care să implementeze diferitele tipuri de salarizare într-o posibilă metodă CalculSalarBrut, atunci:1) în metoda CalculSalarBrut am scrie o structură alternativă, în ramurile căreia am include

algoritmii specifici fiecărui tip de salarizare (metoda capătă volum şi împărţirea sarcinilor la mai mulţi programatori devine ciudată când este vorba de o singură metodă);

sau2) am crea o ierarhie de angajaţi în funcţie de tipul de salarizare (clasa angajat poate face

parte din ierarhii făcute după alte criterii şi folosirea unei ierarhii cu mai multe criterii de specializare ridică probleme de redundanţă a codului).

Cele două soluţii alternative nu separă clientul (şi eventual ierarhia din care face parte clientul) de comportamentul dorit.

Un alt exemplu, pentru iubitorii de jocuri electronice (mai ales de shootere): dacă avem o ierarhie de luptători cu diverse caracteristici şi o metodă atacă, ce face efectiv metoda atacă nu ţine de ierarhia de luptători, ci de arma deţinută la momentul respectiv. În acest exemplu, luptătorii au rolul Context, iar diferitele arme au rolul de ConcreteStrategy.

35

Page 36: Cap 8 Sabloane de Proiectare

8.3.6 Şablonul iterator

Şablonul iterator este folosit pentru a accesa elementele unui agregat (a unei colecţii) în mod secvenţial, fără a ne folosi de caracteristicile acestor elemente.

În orice moment unul din elementele colecţiei este considerat ca fiind elementul curent.

Fig. 8.30 Şablonul iterator

Operaţii ale elementelor colecţiei de parcurs ar putea fi:- currentItem (ghici ce face fiecare operaţie);- first;- last;- next;- previous;- hasNext;- hasPrevious;- isFirst;- isLast.Şablonul iterator are un grad mai mare de importanţă în mediile de dezvoltare în

care nu sunt implementate complet tipuri de date compuse (vectori, liste, stive). Îl întâlniţi implementat în RecordSet-urile ADO, DAO sau RDO specifice mediilor de dezvoltare Microsoft.

36

Page 37: Cap 8 Sabloane de Proiectare

8.3.7 Şablonul interpreter

Şablonul interpreter descrie cum se pot interpreta expresiile într-un anumit limbaj. El îşi găseşte utilitatea în aplicaţiile economice în care se doreşte salvarea unor formule de calcul într-un format accesibil utilizatorilor finali şi folosirea ulterioară de către aplicaţie a acestor formule.

Conform şablonului interpreter atât operanzii, cât şi operatorii dintr-o expresie ar trebui să aibă aceeaşi interfaţă.

Fig. 8.31 Şablonul interpreter

O expresie terminală este o expresie care nu poate fi împărţită în expresii mai mici, ca în cazul unei expresii nonterminale.

O expresie nonterminală se comportă ca un evaluator de expresii. Acest evaluator trebuie să împartă expresia de interpretat în mai multe părţi cu un parser. Tot evaluatorul trebuie să parcurgă fiecare parte a expresiei:

- când întâlneşte un operand (o variabilă pe care are voie utilizatorul să o folosească într-o anumită formulă) îl introduce într-o stivă;

- când întâlneşte un operator extrage din stivă valorile operanzilor afectaţi şi introduce în loc rezultatul aplicării operatorului asupra operanzilor.

Dacă se pleacă de la o expresie corectă la sfărşitul parcurgerii acesteia în stiva utilizată va rămâne o singură valoare, ea fiind chiar rezultatul interpretării.

Exemple de aplicaţii în care ar putea fi utilizat şablonul interpreter:- aplicaţii de salarizare;- aplicaţii de procesare a datelor la importul sau exportul lor din diferite sisteme;- aplicaţii de interpretare a unui dialect SQL sau de traducere dintr-un dialect

SQL în altul.Un interpretor poate fi văzut şi ca o „cutie neagră” căreia i se dă o expresie şi care

returnează un rezultat. Cum nu trebuie să „reinventăm roata” putem folosi interpretore deja existente, cum ar fi cele pentru scripturi:

import javax.script.ScriptEngineManager;import javax.script.ScriptEngine;import javax.script.ScriptException;

ScriptEngineManager mgr = new ScriptEngineManager();ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

37

Page 38: Cap 8 Sabloane de Proiectare

try { jsEngine.eval("a = 1; b = 2; c = a + b; print(c);"); } catch (ScriptException ex) { ex.printStackTrace(); }

Rularea codului de mai sus ar duce la evaluarea expresiei trimisă metodei eval şi anume la calculul şi afişarea variabilei c.

8.3.8 Şablonul command

Conform şablonului command un obiect poate încapsula toate informaţiile necesare pentru apelarea unei metode a altui obiect, cum ar fi: numele metodei de apelat, obiectul ce deţine metoda şi valorile de transmis parametrilor.

Fig. 8.32 Şablonul command

Clientul instanţiază obiectul de tip comandă şi îl pregăteşte pentru a fi apelat la un moment ulterior (transmiţându-l invoker-ului).

Obiectul invoker conţine o listă de comenzi şi decide când va fi apelat obiectul comandă.

Obiectul receiver este cel care va efectua o acţiune ca urmare a lansării în execuţie a comenzii.

Beneficiile acestui şablon ţin de faptul că execuţia unei anumite metode poate fi pregătită din timp, în aşa fel încât aceasta să fie lansată fără să i se ştie numele, obiectul de care aparţine şi momentul exact al execuţiei.

Şablonul command poate fi utilizat pentru a realiza aplicaţii cu:- funcţionalităţi de tip „undo”;- comportament tranzacţional;- progress bar sincronizat cu execuţia unui grup de comenzi;- funcţionalităţi de tip „wizard”;- înregistrări de macro-uri;De exemplu, dacă un păpuşar (client) doreşte să pună o păpuşă (receiver) să se

bucure (concretecommand) foloseşte un şef de campanie (invoker), iar acesta îi transmite păpuşii să ţopăie (metoda action din şablon).

38