Lekce 18 - Mediator
V minulé lekci, Interpreter, jsme si ukázali návrhový vzor chování Interpreter, který definuje, jakým způsobem implementovat interpretaci nějakého jazyka pomocí objektově orientovaného programování.
V dnešním tutoriálu Návrhové vzory GoF si představíme návrhový vzor Mediator, který zavádí prostředníka mezi přímou komunikaci několika objektů.
Motivace použití vzoru Mediator
Vzor Mediator snižuje počet vazeb mezi objekty a reguluje jejich odpovědnost.
Vzor pracuje na populárním principu, který využívají i vzory Controller a Indirection z návrhových vzorů GRASP.
Ačkoli to zní možná paradoxně, přidáním umělé třídy můžeme často naši aplikaci zjednodušit. Pokud spolu nějaká množina objektů komunikuje přímo, vzniká mezi nimi zbytečné množství složitých vazeb. Pokud bude množina objektů komunikovat pouze s mediátorem a on s nimi, sníží se počet těchto vazeb. Ukažme si, jak by mohla vypadat situace, kdyby spolu nějaké komponenty komunikovaly přímo:

Množství vazeb mezi komponentami je vysoké. Porušujeme vzor low coupling z návrhových vzorů GRASP, protože komponenty na sobě navzájem závisí. Nyní zkusme zavést mediátora, který bude veškerou komunikaci zprostředkovávat:

Mediátor sníží potřebu přesné znalosti objektů mezi sebou a kód se zjednoduší. Stačí, když objekt umí komunikovat jen s mediátorem. O nic dalšího se nemusí starat. Komunikační mechanismus mezi objekty poté zůstane zapouzdřený na jednom místě: v mediátoru. Objekty lze takto samozřejmě i jednoduše měnit bez nutnosti úpravy všech dalších účastníků interakce.
V praxi se mediátor často používá například pro komunikaci mezi formulářovými prvky. Zabráníme tím, aby se na sebe musely formulářové prvky navzájem odkazovat. Budou komunikovat s mediátorem, samostatným objektem, který bude podle aktuálních akcí upravovat stav formuláře.
Definice vzoru Mediator
Vzor Mediator zavádí pojmy jako Mediator
,
Colleague
, ConcreteMediator
a
ConcreteColleague
. Pojďme si je vysvětlit.
Mediator
Mediator
definuje pro mediátor abstraktní
třídu. Konkrétních mediátorů může být poté více.
Colleague
Colleague
definuje abstraktní třídu pro
kolegy, objekty v interakci. Díky abstraktní třídě mohou
konkrétní kolegové rovnou jednoduše zdědit vazbu na
mediátor.
ConcreteMediator
ConcreteMediator
jsou konkrétními mediátory
udržujícími vazbu na všechny kolegy za účelem
zprostředkování komunikace.
ConcreteColleague
ConcreteColleague
představuje konkrétní objekty
implementující libovolné operace s vazbou na mediátor.
UML diagram
Podívejme se, jak návrhový vzor Mediator popisuje UML diagram:

Příklad implementace vzoru Mediator
Poměrně klasickým příkladem použití vzoru je vytvoření mediátoru pro různé typy loggerů. Aplikace potom nekomunikuje s několika loggery, ale s jedním mediátorem. Ten podle metod a parametrů zprostředkovává komunikaci loggerům. Toto použití mediátoru je velmi podobné návrhovému vzoru Facade (fasáda). Rozdíl nastává v tom, že fasáda často pouze deleguje na rozhraní nějaké již existující implementace. Mediátor již obsahuje logiku, tedy kód potřebný pro zprostředkování komunikace. Na příkladu loggerů si ukážeme rozdíl implementace pomocí vzoru Mediator a Facade.
Implementace vzorem Mediator
Implementace vzoru Mediator pro příklad logování by mohla vypadat například takto:
-
public class LoggerMediator { private TypLogovani typLogovani; private FileLogger fileLogger = new FileLogger(); public void SetTypLogovani(TypLogovani typLogovani) { this.typLogovani = typLogovani; } public void Loguj(string zprava, Level level) { if (typLogovani == TypLogovani.Konzole) { if (level == Level.Error) Console.Error.WriteLine(zprava); else Console.WriteLine(zprava); } else if (typLogovani == TypLogovani.Soubor) { if (level == Level.Error) fileLogger.Log(zprava, FileLogger.ERROR); else fileLogger.Log(zprava, FileLogger.DEBUG); } } }
-
public class LoggerMediator { private TypLogovani typLogovani; private FileLogger fileLogger = new FileLogger(); public void setTypLogovani(TypLogovani typLogovani) { this.typLogovani = typLogovani; } public void loguj(String zprava, Level level) { if (typLogovani == TypLogovani.Konzole) { if (level == Level.Error) System.err.println(zprava); else System.out.println(zprava); } else if (typLogovani == TypLogovani.Soubor) { if (level == Level.Error) fileLogger.log(zprava, FileLogger.ERROR); else fileLogger.log(zprava, FileLogger.DEBUG); } } }
-
class LoggerMediator { private $typLogovani; private $fileLogger; public function setTypLogovani($typLogovani) { $this->typLogovani = $typLogovani; } public function loguj($zprava, $level) { if ($this->typLogovani === TypLogovani::Konzole) { if ($level === Level::Error) fwrite(STDERR, $zprava); else echo $zprava; } elseif ($this->typLogovani === TypLogovani::Soubor) { if ($level === Level::Error) $this->fileLogger->log($zprava, FileLogger::ERROR); else $this->fileLogger->log($zprava, FileLogger::DEBUG); } } }
-
class LoggerMediator { constructor() { this.typLogovani = null; this.fileLogger = new FileLogger(); } setTypLogovani(typLogovani) { this.typLogovani = typLogovani; } loguj(zprava, level) { if (this.typLogovani === TypLogovani.Konzole) { if (level === Level.Error) console.error(zprava); else console.log(zprava); } else if (this.typLogovani === TypLogovani.Soubor) { if (level === Level.Error) this.fileLogger.log(zprava, FileLogger.ERROR); else this.fileLogger.log(zprava, FileLogger.DEBUG); } } }
-
class LoggerMediator: def __init__(self): self.typLogovani = None self.fileLogger = FileLogger() def setTypLogovani(self, typLogovani): self.typLogovani = typLogovani def loguj(self, zprava, level): if self.typLogovani == TypLogovani.Konzole: if level == Level.Error: print(zprava, file=sys.stderr) else: print(zprava) elif self.typLogovani == TypLogovani.Soubor: if level == Level.Error: self.fileLogger.log(zprava, FileLogger.ERROR) else: self.fileLogger.log(zprava, FileLogger.DEBUG)
Implementace vzorem Facade
Pro ten samý příklad si ukažme jeho implementaci pomocí vzoru Facade, u kterého mediátor pouze deleguje logování na různé loggery pomocí nějaké vnitřní logiky:
-
public class LoggerFacade { public void LogInfo(string zprava) { Console.WriteLine(zprava); } public void LogError(string zprava) { Console.Error.WriteLine(zprava); } }
-
public class LoggerFacade { public void logInfo(final String zprava) { System.out.println(zprava); } public void logError(final String zprava) { System.err.println(zprava); } }
-
class LoggerFacade { public function logInfo($zprava) { echo $zprava . PHP_EOL; } public function logError($zprava) { fwrite(STDERR, $zprava . PHP_EOL); } }
-
class LoggerFacade { logInfo(zprava) { console.log(zprava); } logError(zprava) { console.error(zprava); } }
-
class LoggerFacade: def logInfo(self, zprava): print(zprava) def logError(self, zprava): print(zprava, file=sys.stderr)
V další lekci, Iterator, si ukážeme návrhový vzor Iterator. Tento vzor chování zavádí samostatný objekt, který umožňuje lineární procházení kolekcemi, aniž bychom museli znát vnitřní strukturu těchto kolekcí.