Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 20 - Chain of responsibility

V minulé lekci, Iterator, jsme si ukázali 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í.

V dnešním tutoriálu Návrhové vzory GoF si představíme návrhový vzor Chain of responsibility, který umožňuje oddělení odesílatele požadavku od jeho příjemců.

Motivace použití vzoru Chain of responsibility

Ve vývoji by se nám hodilo oddělit odesílatele požadavku od jeho příjemců tak, aby požadavek odesílatele byl předáván mezi příjemci až k tomu, který je kompetentní jej vyřídit. A zároveň ošetřit stav, kdy kompetentní příjemce nebude nalezen.

Pokud bychom napevno spojili třídu, která posílá nějaký požadavek a třídu, která jej zpracovává, porušili bychom návrhový vzor low coupling, o kterém se dočteme v lekci Návrhové vzory GRASP. Také bychom přišli o možnost napojení na více handlerů, které požadavek zpracovávají. Toho se v praxi často využívá při aplikování různých filtrů, které jsou v řetězu za sebou a požadavek postupně nějak zpracovávají.

A právě tyto problémy řeší návrhový vzor Chain of responsibility. Jeho použití není omezené jen na lineární datové struktury, nýbrž předávání odpovědnosti může probíhat i na úrovni k rodiči ve stromových strukturách. Někdy bývá vzor kombinovaný se vzorem Composite. Návrhový vzor Composite definuje nejefektivnější způsob, jak takové stromové struktury vytvářet. Na takový strom někdy může být referováno jako na Tree of responsibility.

Některé frameworky používají vzor Chain of responsibility na implementaci událostního modelu.

Definice vzoru Chain of responsibility

Asi nás nepřekvapí, že příjemce požadavku Handler je ve vzoru definován jako rozhraní. S tímto rozhraním komunikuje odesílatel požadavku. Rozhraní příjemce požadavku implementují v řetězu nebo stromu jednotlivé handlery požadavku, které jsou spolu propojené pomocí referencí.

Podívejme se na grafické znázornění UML diagramem:

Návrhový vzor Chain of responsibility z GOF - Návrhové vzory GoF

Handler typicky poskytuje:

  • metody pro nastavení dalšího handleru,
  • polymorfní metodu pro vyřízení požadavku,
  • metodu pro předání požadavku dalšímu handleru.

Příklad implementace vzoru Chain of responsibility

Na vzor Chain of responsibility se dá vymyslet spousta příkladů, jelikož tímto způsobem funguje velké množství služeb. Když si například objednáváme zboží z ciziny, náš požadavek vyřídí řetěz hned několik pošt. Reálné praktické užití ovšem zpravidla bývá v implementaci filtrů. Filtry jsou spojené za sebou a požadavek pohltí v případě, že není žádoucí. Až teprve poslední článek řetězce požadavek vyřídí.

Vytvořme si ukázku řetězu takových filtrů na požadavek přijetí emailu. Požadavek postupně obdrží:

  1. spamfilter,
  2. uživatelský filtr,
  3. handler, který jej vloží do příchozí pošty,
  4. handler, který vyvolá upozornění na nový email.

Udělejme si příklad na příchod emailu, který proputuje několika filtry. Filtry bude možné libovolně přidávat a měnit právě díky vzoru Chain of responsibility.

Abstraktní třída HandlerPozadavku

Nejprve bychom definovali abstrakci pro handlery v řetězu:

  • public abstract class HandlerPozadavku
    {
        private HandlerPozadavku dalsi;
    
        public HandlerPozadavku SetDalsi(HandlerPozadavku dalsi)
        {
            this.dalsi = dalsi;
            return dalsi;
        }
    
        protected void NechVyriditDalsiho(Pozadavek pozadavek)
        {
            if (dalsi != null)
                dalsi.VyridPozadavek(pozadavek);
        }
    
        public abstract void VyridPozadavek(Pozadavek pozadavek);
    }
  • public abstract class HandlerPozadavku {
        private HandlerPozadavku dalsi;
    
        public HandlerPozadavku setDalsi(HandlerPozadavku dalsi) {
            this.dalsi = dalsi;
            return dalsi;
        }
    
        protected void nechVyriditDalsiho(Pozadavek pozadavek) {
            if (dalsi != null)
                dalsi.vyridPozadavek(pozadavek);
        }
    
        public abstract void vyridPozadavek();
    }
  • abstract class HandlerPozadavku {
        private $dalsi;
    
        public function setDalsi($dalsi) {
            $this->dalsi = $dalsi;
            return $dalsi;
        }
    
        protected function nechVyriditDalsiho($pozadavek) {
            if ($this->dalsi !== null)
                $this->dalsi->vyridPozadavek($pozadavek);
        }
    
        abstract public function vyridPozadavek($pozadavek);
    }
  • function HandlerPozadavku() {
        this.dalsi = null;
    }
    
    HandlerPozadavku.prototype.setDalsi = function(dalsi) {
        this.dalsi = dalsi;
        return dalsi;
    };
    
    HandlerPozadavku.prototype.nechVyriditDalsiho = function(pozadavek) {
        if (this.dalsi !== null) {
            this.dalsi.vyridPozadavek(pozadavek);
        }
    };
    
    HandlerPozadavku.prototype.vyridPozadavek = function() {
        throw new Error('Abstraktní metoda vyridPozadavek() musí být implementována v dědících třídách');
    };
  • from abc import ABC, abstractmethod
    
    class HandlerPozadavku(ABC):
        def __init__(self):
            self.dalsi = None
    
        def set_dalsi(self, dalsi):
            self.dalsi = dalsi
            return dalsi
    
        def nech_vyridit_dalsiho(self, pozadavek):
            if self.dalsi is not None:
                self.dalsi.vyrid_pozadavek(pozadavek)
    
        @abstractmethod
        def vyrid_pozadavek(self, pozadavek):
            pass

U Javascriptové verze si můžeme všimnout využití vlastnosti prototype. Ta umožňuje přiřadit k metodě funkce. Metoda, ke které jsou nové funkce přiřazené se stává konstruktorem. V kódu níže pomocí new HandlerPozadavku() vytvoříme instanci typu HandlerPozadavku, kterou nahradíme rozhraní, které Javascript nepodporuje.

V metodě setDalsi() vracíme další handler v řetězu, abychom mohli pomocí vzoru Method chaining (více v lekci Method chaining a method cascading) rovnou poskládat celý řetěz. Řetěz bychom poskládali například takto:

  • spamFiltr.SetDalsi(uzivatelskyFiltr).SetDalsi(prichoziPostaHandler);
  • spamFiltr.setDalsi(uzivatelskyFiltr).setDalsi(prichoziPostaHandler);
  • $spamFiltr->setDalsi($uzivatelskyFiltr)->setDalsi($prichoziPostaHandler);
  • spamFiltr.handler.setDalsi(uzivatelskyFiltr).handler.setDalsi(prichoziPostaHandler);
  • spamFiltr.setDalsi(uzivatelskyFiltr).setDalsi(prichoziPostaHandler)

Řetěz můžeme takto jednoduše pospojovat na jediném řádku.

Implementace konkrétních handlerů

Možná podoba konkrétních handlerů je následující:

  • public class SpamFiltr: HandlerPozadavku
    {
        public override void VyridPozadavek(Pozadavek pozadavek)
        {
            if (!pozadavek.Email.Text.Contains("free pills")) // Jednoduchý spamfiltr
            {
                NechVyriditDalsiho(pozadavek);
            }
        }
    }
    
    public class UzivatelskyFiltr: HandlerPozadavku
    {
        public List<string> ZakazaneAdresy = new List<string>();
    
        public override void VyridPozadavek(Pozadavek pozadavek)
        {
            if (!ZakazaneAdresy.Contains(pozadavek.Email.Adresa)) // Jednoduchý uživatelský filtr
            {
                NechVyriditDalsiho(pozadavek);
            }
        }
    }
    
    public class PrichoziPostaHandler: HandlerPozadavku
    {
        private PrichoziPosta posta;
    
        public PrichoziPostaHandler(PrichoziPosta posta)
        {
            this.posta = posta;
        }
    
        public override void VyridPozadavek(Pozadavek pozadavek)
        {
            posta.Pridej(pozadavek.Email);
        }
    }
  • public class SpamFiltr extends HandlerPozadavku {
        public void vyridPozadavek(Pozadavek pozadavek) {
            if (!pozadavek.Email.Text.Contains("free pills")) { // Jednoduchý spamfiltr
                nechVyriditDalsiho(pozadavek);
            }
        }
    }
    
    public class UzivatelskyFiltr extends HandlerPozadavku {
        public List<String> zakazaneAdresy = new ArrayList<String>();
    
        public void vyridPozadavek(Pozadavek pozadavek) {
            if (!zakazaneAdresy.contains(pozadavek.email.adresa)) { // Jednoduchý uživatelský filtr
                nechVyriditDalsiho(pozadavek);
            }
        }
    }
    
    public class PrichoziPostaHandler extends HandlerPozadavku {
        private PrichoziPosta posta;
    
        public PrichoziPostaHandler(PrichoziPosta posta) {
            this.posta = posta;
        }
    
        public void vyridPozadavek(Pozadavek pozadavek) {
            posta.pridej(pozadavek.email);
        }
    }
  • class SpamFiltr extends HandlerPozadavku {
        public function vyridPozadavek($pozadavek) {
            if (strpos($pozadavek->getEmail()->getText(), "free pills") === false) { // Jednoduchý spamfiltr
                $this->nechVyriditDalsiho($pozadavek);
            }
        }
    }
    
    class UzivatelskyFiltr extends HandlerPozadavku {
        public $zakazaneAdresy = [];
    
        public function vyridPozadavek($pozadavek) {
            if (!in_array($pozadavek->getEmail()->getAdresa(), $this->zakazaneAdresy)) { // Jednoduchý uživatelský filtr
                $this->nechVyriditDalsiho($pozadavek);
            }
        }
    }
    
    class PrichoziPostaHandler extends HandlerPozadavku {
        private $posta;
    
        public function __construct($posta) {
            $this->posta = $posta;
        }
    
        public function vyridPozadavek($pozadavek) {
            $this->posta->pridej($pozadavek->getEmail());
        }
    }
  • function SpamFiltr() {
        this.handler = new HandlerPozadavku();
    }
    
    SpamFiltr.prototype.vyridPozadavek = function(pozadavek) {
        if (!pozadavek.email.text.includes("free pills")) { // Jednoduchý spamfiltr
            this.handler.nechVyriditDalsiho(pozadavek);
        }
    };
    
    function UzivatelskyFiltr() {
        this.handler = new HandlerPozadavku();
        this.zakazaneAdresy = [];
    }
    
    UzivatelskyFiltr.prototype.vyridPozadavek = function(pozadavek) {
        if (!this.zakazaneAdresy.includes(pozadavek.email.adresa)) { // Jednoduchý uživatelský filtr
            this.handler.nechVyriditDalsiho(pozadavek);
        }
    };
    
    function PrichoziPostaHandler(posta) {
        this.handler = new HandlerPozadavku();
        this.posta = posta;
    }
    
    PrichoziPostaHandler.prototype.vyridPozadavek = function(pozadavek) {
        this.posta.pridej(pozadavek.email);
    };
  • class SpamFiltr(HandlerPozadavku):
        def vyridPozadavek(self, pozadavek):
            if "free pills" not in pozadavek.email.text:  # Jednoduchý spamfiltr
                self.nechVyriditDalsiho(pozadavek)
    
    
    class UzivatelskyFiltr(HandlerPozadavku):
        def __init__(self):
            self.zakazaneAdresy = []
    
        def vyridPozadavek(self, pozadavek):
            if pozadavek.email.adresa not in self.zakazaneAdresy:  # Jednoduchý uživatelský filtr
                self.nechVyriditDalsiho(pozadavek)
    
    
    class PrichoziPostaHandler(HandlerPozadavku):
        def __init__(self, posta):
            self.posta = posta
    
        def vyridPozadavek(self, pozadavek):
            self.posta.pridej(pozadavek.email)

Funkčnosti jednotlivých filtrů jsou samozřejmě extrémně zjednodušené a pouze ilustrativní. Celý řetěz bychom dali do pohybu předáním požadavku jeho prvnímu článku, kterým je SpamFiltr.

V další lekci, Command (Příkaz), si představíme návrhový vzor Command, jenž nám umožňuje zapouzdřit žádost o provedení operace do objektu.


 

Předchozí článek
Iterator
Všechny články v sekci
Návrhové vzory GoF
Přeskočit článek
(nedoporučujeme)
Command (Příkaz)
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
14 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