IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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

Vazby mezi objekty bez aplikování vzoru Mediátor - Návrhové vzory GoF

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:

Vazby mezi objekty po aplikování vzoru Mediátor - Návrhové vzory GoF

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:

Návrhový vzor Mediátor z GOF - Návrhové vzory GoF

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


 

Předchozí článek
Interpreter
Všechny články v sekci
Návrhové vzory GoF
Přeskočit článek
(nedoporučujeme)
Iterator
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
15 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity