/ janturon.cz / Od kodéra k analytikovi / Mediátor a Pozorovatel

Mediátor a Pozorovatel (Observer)

Mediátor

Centralizuje komunikaci mezi objekty. Je to třída, jejíž objekt se zaregistruje v konstruktoru objektů, které mají být ovládány.

Typickým použitím je formulář, kde změna jednoho ovládacího prvku změní ostatní prvky. Umístění těchto změn přímo do kódu ovládacích prvků mezi nimi vytváří závislost a jakákoliv změna (například přesunutí jednoho prvku do dalšího kroku formuláře) vyžaduje refaktoring. Umístěním těchto pravidel do Mediátoru lze provádět tyto změny v jediné třídě a kód se stává znovupoužitelným.

Zaregistrováním Mediátoru cílový objekt dává informaci ostatním objektům, že v něm proběhla změna.

Příklad

Mějme třídu Control obsahující jedinou datovou položku value. Vytvořme tři objekty Control a mějme dále požadavek, že právě jeden z těchto objektů musí být nenulový. Tento požadavek vyřeší Mediator podle následujícího postupu:

Vytvoříme třídu Mediator, do které budeme metodou Register() registrovat Controlobjekty, které mediátoru využívají. Tuto metodu budou volat ony objekty v konstruktoru, kde si onen mediátor také zaregistrují (obousměrná asociace). Při zavolání metody setValue() se při nenulové změně zavolá metoda Update() zaregistrovaného mediátoru zavolá metodu Update všech ostatních zaregistrovaných objektů Control, které provedou jejich vynulování.

Deklarace s obousměrnou závislostí by mohla vypadat takto:

// dopředná (forward) deklarace pro Mediator class Control; class Mediator { vector<Control*> controls; public: void Register(Control* c); void Update(Control* c); void Info(); }; class Control { int value; Mediator* mediator; public: Control(int newValue, Mediator* m); int getValue(); void setValue(int newValue); void Update(); };

Metody můžeme definovat takto:

Control::Control(int newValue, Mediator* m) { value = newValue; mediator = m; m->Register(this); } int Control::getValue() { return value; } void Control::setValue(int newValue) { if(newValue==value) return; // není změna if(newValue==0) return; // nelze nastavit 0 value = newValue; mediator->Update(this); } void Control::Update() { value = 0; } void Mediator::Register(Control* c) { controls.push_back(c); } void Mediator::Update(Control* c) { vector<Control*>::iterator it; for(it=controls.begin(); it<controls.end(); it++) { if(*it==c) continue; (*it)->Update(); } } void Mediator::Info() { vector<Control*>::iterator it; for(it=controls.begin(); it<controls.end(); it++) { cout << (*it)->getValue() << " "; } cout << endl; }

A celou konstrukci demonstruje tento příklad:

int main() { Mediator m; Control c1(1,&m), c2(0,&m), c3(0,&m); // nevolat mediátor, žádná změna c1.setValue(1); m.Info(); // 1 0 0 // nula zakázána: neprovést změnu a nevolat mediátor c1.setValue(0); m.Info(); // 1 0 0 // v pořádku, zavolat mediátor c2.setValue(2); m.Info(); // 0 2 0 c3.setValue(1); m.Info(); // 0 0 1 return 0; }

Pozorovatel (Observer)

Decentralizuje komunikaci mezi objekty. Je to metoda, která zajistí provedení metody jiného objektu, pokud v původním objektu nastala změna.

Pozorovatel je implementace události objektu. Pomocí něj lze například zapsat, že po dokončení stahování souboru se má zavolat nějaká metoda. Koncový uživatel pak tuto metodu zaregistruje a má zaručeno, že se zavolá po dokončení stahování: nemusí se už zaobrat tím, jak je toho docíleno.

Pozorovatel je v mnoha jazycích již implementován jako Posluchač (událostí) - (Event) Listener, viz implementované vzory (zřídka je tak nutné psát vlastní implementaci).

Zaregistrováním posluchače cílový objekt přijímá informaci od ostatních objektů, pokud v nich proběhla změna.

Příklad

Mějme třídu Control obsahující jedinou datovou položku value. Metoda addValueListener přidává do seznamu metod další, které se zavolají (pokud se hodnota změní) při volání metody setValue pomocí metody valueChanged. Volané metody musí být stejného typu (valueEventHandler, které jsou v tomto příkladu (obecně to vzor nevyžaduje) ve třídě Observer.

typedef void(*valueEventHandler)(void*,int); class Control { int value; vector<valueEventHandler> handlers; public: Control() : value(0) { } void addValueListener(valueEventHandler h) { handlers.push_back(h); } void setValue(int newValue) { if(value==newValue) return; int oldValue = value; value = newValue; valueChanged(oldValue); } void valueChanged(int oldVal) { for(int i=0; i<handlers.size(); i++) handlers[i](this, oldVal); } int getValue() { return value; } }; class Observer { public: static void info(void* context, int oldVal) { Control* c = (Control*)context; cout << "old: " << oldVal << ", new: " << c->getValue() << "\n"; } static void change(void* context, int oldVal) { Control* c = (Control*)context; cout << "value changed by: " << c->getValue()-oldVal << "\n"; } }; int main() { Control c; c.addValueListener(Observer::info); c.addValueListener(Observer::change); c.setValue(5); // zavolá info a change c.setValue(3); // zavolá info a change return 0; }

Zřídka je zapotřebí události odhlásit. Je možné napsat metodu Control::removeValueListener, která příslušnou metodu ze seznamu volaných metod vyjme.

void removeValueListener(valueEventHandler h) { vector<valueEventHandler>::iterator it; for(it=handlers.begin(); it<handlers.end(); it++) { if(*it==h) handlers.erase(it); } }

Do metody main lze pak připsat

c.removeValueListener(Observer::change); c.setValue(4); // zavolá se pouze info

Posluchač událostí (Event Listener)

Některé jazyky vzor Pozorovatel implementují přímo jako nativní funkce. Více viz v implementovaných vzorech.