Definitie
Este un sablon care realizeaza decuplarea unei abstractiuni de implementarea ei, astfel incat cele 2 pot varia independent.
Context
Cand o anumita abstractiune poate avea mai multe variante de implementare, de obicei se utilizeaza mostenirea: o clasa abstracta defineste interfata abstractiunii, iar subclasele concrete o implementeaza in diverse moduri.
Aceasta abordare nu este intotdeauna suficient de flexibila. Prin mostenire o implementare este legata permanent de abstractiune si acest lucru face foarte dificila modificarea independenta a abstractiunii si a implementarii.
Sa consideram ca abstractiunea in cauza se numeste Window si reprezinta o fereastra in cadrul unui sistem de interfete grafice. Aceasta abstractiune ar trebui sa permita scrierea de aplicatii care sa poata lucra cu diverse platforme grafice, X Window System si Presentation Manager de exemplu.
Utilizand mostenirea, s-ar putea defini 2 subclase, XWindow si PMWindow care sa implementeze interfata Window pentru cele 2 platforme. Apar aici 2 dezavantaje:
Sablonul Bridge isi propune sa solutioneze aceste probleme plasand abstractiunea Window si implementarea ei in ierarhii separate. Astfel, vom avea o ierarhie formata din clasa Window, cu subclasele IconWindow si TransientWindow, respectiv o ierarhie separata, formata din clase care depind de platforma: WindowImpl cu subclasele XWindowImpl si PMWindowImpl:
void XWindowImpl::DevDrawText(){
Motivatii
Sablonul Bridge se aplica in general in situatiile in care:
se doreste evitarea legarii unei abstractiuni de implementarea sa. Acest lucru se intampla de exemplu cand implementarea trebuie aleasa sau modificata pe durata executiei;
atat abstractiunea cat si implementarea trebuie sa poata fi extinse prin adaugarea de subclase. In aces caz sablonul bridge permite combinarea dupa dorinta a diferitelor abstractiuni si implementari.
schimbarile aduse implementarii nu trebuie sa afecteze clientii, adica nu trebuie sa necesite recompilarea codului acestora;
se doreste ascunderea completa a implementarii fata de clienti;
se constata ca extensiile care trebuie aduse unei abstractiuni duc la o crestere prea mare a numarului de subclase (asa cum s-a vazut in exemplul de mai sus). In acest caz se indica "spargerea" obiectelor in 2 parti, creindu-se 2 ierarhii separate. Asemenea ierarhii se mai numesc "generalizari incuibate";
se doreste partajarea unei implementari intre mai multe obiecte si acest fapt trebuie ascuns fata de client.
Solutie
In figura de mai jos este data structura de clase care constituie sablonul Bridge:
Consecinte
Sablonul Bridge prezinta urmatoarele implicatii:
Decuplarea claselor Abstraction si Implementor elimina dependentele la compilare, in sensul ca daca se modifica o clasa de tip Implementor, aceasta nu implica recompilarea claselor Abstraction si a clientilor lor. Aceasta proprietate este esentiala atunci cand se doreste asigurarea compatibilitatii binare intre diferite versiuni ale unei biblioteci de clase.
Mai mult, decuplarea incurajeaza divizarea pe nivele a unei aplicatii, adica o mai buna structurare a lor. Astfel, nivelul cel mai de sus trebuie sa cunoasca doar interfetele Abstraction si Implementor.
Implementare
Crearea obiectului Implementor adecvat. Cum, cand si unde se decide ce obiect concret de tip Implementor sa se instantieze, atunci cand avem mai multe variante?
Implementor unic. In cazul in care avem o singura ramura descendenta din Implementor, practic nu mai este necesara definirea clasei abstracte Implementor. Acesta este un caz degenerat al sablonului Bridge. Totusi, separarea intre Abstraction si Implementor este recomandata, deoarece permite modificarea claselor de implementare fara a afecta clientii.
Daca Abstraction stie care sunt toate clasele ConcreteImplementor, ea poate instantia una dintre ele in constructor, bazandu-se pe parametrii de apel ai constructorului. Spre exemplu, daca o clasa colectie are mai multe implementari, decizia se poate baza pe dimensiunea colectiei: o lista inlantuita este potrivita pentru colectii mici, in timp ce o tabela hashing este buna pentru colectii mari.
O alta posibilitate este aceea de a alege initial o varianta de implementare implicita, urmand ca ea sa fie schimbata pe parcurs, dupa necesitati. De exemplu, daca se constata ca o colectie creste peste o anumita limita, se poate comuta la o implementare mai adecvata.
O alta varianta ar fi de a delega decizia spre un alt obiect. Referindu-ne la exemplul cu Window, s-ar putea introduce un obiect producator abstract (v. Abstract Factory), a carui unica sarcina ar fi aceea de a incapsula informatii specifice platformei. In felul acesta, obiectul producator stie ce obiect WindowImpl trebuie creat pentru platforma curenta. Obiectul Window ar cere doar producatorului sa i se furnizeze un obiect WindowImpl. Un avantaj al acestei variante este acela ca Abstraction nu este legat direct de Implementor.
Definitie
Acest sablon supervizeaza interactiunea intre mai multe obiecte, permitand ca obiectele respective sa nu se refere unul pe altul in mod explicit. In felul acesta, la nevoie se poate modifica mai usor modul in care interactioneaza obiectele.
Context
Proiectarea orientata pe obiecte incurajeaza distribuirea responsabilitatilor intre obiecte. Aceasta poate sa duca uneori la colaborari complexe intre obiecte. In cel mai rau caz se ajunge ca fiecare obiect sa intre in relatie cu toate celelalte.
Desi partitionarea unui sistem in mai multe obiecte este menita sa duca la cresterea reutilizabilitatii, proliferarea conexiunilor intre obiecte tinde sa reduca aceasta caracteristica. Numarul mare de interconexiuni face dificila modificarea comportamentului sistemului, deoarece acest comportament este distribuit pe multe obiecte.
Ca exemplu, sa consideram implementarea unei ferestre de dialog dintr-o interfata grafica. Intr-o asemenea fereastra ar putea sa apara o multitudine de elemnete grafice de control (widgets): butoane, comutatoare, linii de editare, liste derulante etc. intre care pot exista dependente. Astfel, introducerea unei anumite secvente intr-un camp de editare poate determina selectarea/deselectarea unor comutatoare sau selectia unui element dintr-o lista derulanta poate determina afisarea unor anumite valori in diverse linii de editare.
In plus, pentru ferestre de dialog diferite dependentele intre elementele de control vor fi diferite. Ca urmare, chiar daca diversele ferestre de dialog lucreaza cu aceleasi tipuri de componente, ele trebuie particularizate pentru a reflecta dependentele de comunicare intre componente.
Rezolvarea acestei probleme consta in definirea unei clase separate care sa incapsuleze comportamentul colectiv al elementelor de control, jucand rolul unui mediator al acestora. Mediatorul este responsabil pentru coordonarea intarctiunilor unui grup de obiecte. El este un intermediar care permite ca obiectele din grup sa nu se refere unele la altele in mod explicit. Aceste obiecte trebuie doar sa stie cine este mediatorul lor, in felul acesta reducandu-se numarul asocierilor.
Sa presupunem ca in fereastra de dialog se afla, printre altele, o lista derulanta, o linie de editare si o caseta de maracare a optiunilor formata din 3 comutatoare care interactioneaza in felul urmator: initial, pana nu a fost selectata nici o intrare din lista, comutatoarele sunt dezactivate (nu pot fi pozitionate pe on/off); cand utilizatorul selecteaza o intrare din lista, secventa de caractere respectiva apare afisata in campul de editare, iar cele 3 comutatoare sunt activate, astfel incat utilizatorul sa poata sa marcheze o anumita combinatie de optiuni. Admitand ca lista este reprezentata printr-un obiect aListBox, iar campul de editare printr-un obiect anEntryField, aplicarea sablonului mediator in acest caz ar determina urmatoarea inlantuire de operatii dupa selectia unei intrari din lista:
Tema: se propune studentilor sa reprezinte in notatie UML diagrama de secventa care ilustreaza interactiunea de mai sus.
In concluzie, se poate spune ca elementele ferestrei comunica intre ele indirect, prin intermediul mediatorului. Practic, elementele respective nici nu se "cunosc" intre ele. Deoarece comportamentul colectiv este localizat intr-o singura clasa, ea poate fi modificata sau inlocuita cu o subclasa a ei.
In figura de mai jos se poate vedea diagrama care reda structura de clase corespunzatoare exemplului discutat mai sus:
Motivatii
Sablonul Mediator se aplica in situatiile in care:
o multime de obiecte comunica intr-un mod bine definit, dar complex, rezultand o interdependenta nestructurata si dificil de inteles;
reutilizarea unui obiect este dificila din cauza ca el refera si comunica cu multe alte obiecte;
un anumit comportament distribuit pe mai multe clase trebuie sa poata fi adaptat fara a implica o multime de subclase.
Solutie
In figura de mai jos este data structura de clase care constituie sablonul Mediator:
Consecinte
Sablonul Mediator prezinta urmatoarele avantaje si dezavantaje:
Limitarea derivarii. Un mediator localizeaza actiuni care altfel ar trebui distribuite la mai multe obiecte. Schimbarea actiunilor necesita doar schimbarea mediatorului, nu si a obiectelor Colleague.Implementare In cele ce urmeaza vom prezenta o posibila codificare a exemplului discutat in paragraful Context:
Decuplarea obiectelor. Un mediator promoveaza cuplajul slab intre obiectele Colleague. Clasele de tip Mediator si Colleague pot fi schimbate si reutilizate independent unele de altele.
Simplificarea protocolului obiectelor. Un mediator este menit sa inlocuiasca interactiunile de tip many-to-many cu interactiuni de tip one-to-many care sunt mai usor de inteles, implementat si adaptat.
Abstractizarea cooperarii obiectelor. Definind interactiunea intre obiecte ca un concept separat, incapsulat intr-un obiect, se permite proiectantului sa se concentreze (sau sa se focuseze, daca ar fi sa adoptam o expresie la moda) asupra modului in care obiectele coopereaza intre ele, independent de comportamentul lor individual. Cu alte cuvinte, se face o separare intre comportamentul specific unui obiect (care individualizeaza obiectul) si comunicarea obiectului cu alte obiecte.
Centralizarea controlului. Mediatorul introduce un compromis intre complexitatea interactiunilor si complexitatea obiectului mediator, in sensul ca acest obiect, prin faptul ca incapsuleaza protocoale, poate sa devina mai complex decat oricare dintre obiectele Colleague, ajungand un monolit dificil de intretinut.
class Widget {
class ListBox : public Widget{
class EntryField : public Widget {
class FontDialogDirector : public DialogDirector{