Cap 8. Şabloane de proiectare

22
1 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

Transcript of Cap 8. Şabloane de proiectare

Page 1: Cap 8. Şabloane de proiectare

1

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

Page 2: Cap 8. Şabloane de proiectare

2

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

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 Singleton

Singleton 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).

Page 3: Cap 8. Şabloane de proiectare

3

Fig. 8.1 Reprezentarea unei clase singleton

Instanţa unică poate fi creată:

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;

Page 4: Cap 8. Şabloane de proiectare

4

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

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

Page 5: Cap 8. Şabloane de proiectare

5

8.1.2 Metodă factory

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

destinat construirii de obiecte.

Şablonul metodă factory presupune crearea unei clase de bază (Creator) ce conţine

o metodă destinată creării obiectelor de un anumit tip (factoryMethod).

Fig. 8.4 Şablonul metodă factory

Subclase ale clasei factory pot suprascrie metoda factory pentru a se specifica

anumite subtipuri ale obiectelor obţinute.

Ş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).

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

8.1.3 Clasă abstractă factory

Şablonul abstract factory asigură o modalitate de grupare a mai multor metode

factory ce au elemente comune.

Conform acestui şablon se creează o clasă abstractă factory ce va sta la baza mai

multor clase concrete factory. Clasele factory conţin câte o metodă factory pentru fiecare tip

de obiect ce trebuie creat.

Codul client lucrează doar cu tipul abstract de factory, nedepinzând de tipurile

concrete. Conform acestui şablon, sunt create obiect 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.

Page 6: Cap 8. Şabloane de proiectare

6

Fig. 8.4 Şablonul clasă abstractă factory

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.5 Şablonul prototip

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ă

Page 7: Cap 8. Şabloane de proiectare

7

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.6 Ş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ţă

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 clasei cu rolul product este clasa pizza, clasele cu rolurile concretebuilder sunt diferitele

reţete de pizza, iar clasa cu rolul director este clasa.

8.2 Şabloane structurale

8.2.1 Adaptor

Adaptorul (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.

Page 8: Cap 8. Şabloane de proiectare

8

Există două tipuri de adaptoare, în funcţie de modul în care adaptorul reuşeşte să

utilizeze funcţionalităţile clasei de adaptat:

1. adaptoare ce implementează interfaţa dorită de client şi care conţin în plus o instanţă a

clasei de adaptat = object adapter;

Fig. 8.7 Şablonul object adapter

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

public void request () {

furnizor.specificRequest();

}

2. adaptoare care implementează ambele interfeţe şi care în metodele ce implementează

operaţiile interfeţei dorite se face trimitere la operaţiile interfeţei de adaptat = class

adapter. În acest caz se poate vorbi de o adaptare bidirecţională a claselor.

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

Page 9: Cap 8. Şabloane de proiectare

9

Adaptorul descris în figura 8.8 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.8 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ă

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.9 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.9 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 făcând delegând

activitatea de realizat membrului obiect de tipul celuilalt server, mai exact folosind metoda

sendMessage a acestuia.

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

Page 10: Cap 8. Şabloane de proiectare

10

Alte exemple de utilizare a adaptoarelor:

- wrapper-ele din bazele de date federative (DB2) care fac legătură între baze de

date eterogene;

- comenzile de aprovizionare ce pot fi transmise direct furnizorilor într-un mediu

B2B;

- 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

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

componentelor (frunzelor).

Fig. 8.11 Ş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.

Page 11: Cap 8. Şabloane de proiectare

11

Fig. 8.11 Ş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);

Page 12: Cap 8. Şabloane de proiectare

12

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ândui 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.12 Şablonul facade

Page 13: Cap 8. Şabloane de proiectare

13

În exemplul din figura 8.12 cel care crează obiectele de tip tranzacţie (dintr-o

interfaţă utilizator sau dintr-o procedură de import) nu trebuie să cunoască detaliile claselor de

contabilitate şi gestiune, ci doar că o tranzacţie, o dată creată, trebuie transmisă faţadei

ContabilitateSiGestiune pentru înregistrarea acesteia în contabilitate şi afectarea gestiunilor

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

La 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.13 Ş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

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

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)

Page 14: Cap 8. Şabloane de proiectare

14

8.3 Şabloane comportamentale

8.3.1 Mediator

De 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.14 Şablonul mediator

Mediator - defineşte interfaţa de comunicare între obiecte

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

un coleg (binevoitor) poate să transmită „anonime” (avantaj întâlnit şi la proxy);

Page 15: Cap 8. Şabloane de proiectare

15

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.15 Şablonul observator

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ă;

Page 16: Cap 8. Şabloane de proiectare

16

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ţi

privind starea subiectului. Operaţia update( ) poate primi eventual parametri cu informaţii

generale ale evenimentului apărut, informaţii utile observatorului.

Ş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 putrea 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.

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ă;

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

- o cerere poate fi procesată de mai mulţi destinatari, în acelaşi timp, secvenţial

sau respectând chiar anumite fluxuri de cereri;

- clasele intermediare pot realiza log-uri ale cererilor;

- lipsa oricărui potential destinatar poate fi aflată de către expeditor printr-un

mesaj primit de la clasele intermediare.

Page 17: Cap 8. Şabloane de proiectare

17

Fig. 8.16 Şablonul lanţ de responsabilităţi

Un exemplu de astfel de şablon 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ă. Pentru

fiecare rol mentionat mai sus se crează o clasă Functie (cu rolul de ConcreteHandler), în care

există un atribut ce ne indică limita creditului ce poate fi aprobat fără a fi trimis şi la nivelul

ierarhic superior.

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

Fiecare clasă Funcţie va implementa metoda proceseazăDosarCredit, în mod

asemănător cu codul următor:

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);

}

E ca un fel de 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).

Page 18: Cap 8. Şabloane de proiectare

18

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 (createMemento) şi revenire la stare anterioară (setMemento).

Fig. 8.18 Şablonul memento

Clientul care ar folosi un astfel de şablon ar începe prin utilizarea metodei

createMemento din Originator, iar obiectul memento astfel obţinut ar fi folosit ca parametru

de intrare în metoda addMemento din Caretaker:

caretaker.addMemento(originator.createMemento());

Revenirea la o stare anterioară, în condiţiile în care se ştie un anumit număr de

ordine al acesteia în cadrul colecţiei de stări:

originator.setMemento(caretaker.getMemento(7));

8.3.5 Strategie

Şablonul strategie ne ajută să alegem un anumit algoritm de utilizat în funcţie de

un context. Şablonul conţine un grup de algoritmi, fiecare din aceştia fiind încapsulat într-un

obiect. Clienţii ce folosesc algoritmii nu depind de aceştia, variind în mod independent.

Page 19: Cap 8. Şabloane de proiectare

19

Fig. 8.19 Şablonul strategie

Obiectele 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ă.

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.

In orice moment unul din elementele colecţiei este considerat ca fiind elementul

curent.

Fig. 8.20 Şablonul iterator

Page 20: Cap 8. Şabloane de proiectare

20

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

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.20 Şablonul interpreter

Diagrama claselor de mai sus (cea din NetBeans de la acest şablon) nu surprinde

nevoia existenţei unui 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.

Page 21: Cap 8. Şabloane de proiectare

21

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;

ScriptEngineManager mgr = new ScriptEngineManager();

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

try {

Object result = jsEngine.eval("Expresia JavaScript de evaluat!");

} catch (ScriptException ex) {

// tratarea excetiilor

}

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.21 Şablonul command

Clientul instanţiază obiectul de tip comandă şi îl pregăteşte pentru a fi apelat la un

moment ulterior.

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

Page 22: Cap 8. Şabloane de proiectare

22

- 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 invoker, iar acesta îi transmite păpuşii să ţopăie

(metoda action din şablon).