Vzorec odpovědnosti - Chain-of-responsibility pattern
v objektově orientovaný design, řetězec odpovědnosti je návrhový vzor skládající se ze zdroje příkazové objekty a série zpracování objektů.[1] Každý zpracovatelský objekt obsahuje logiku, která definuje typy příkazových objektů, které zvládne; zbytek se předá dalšímu zpracovatelskému objektu v řetězci. Existuje také mechanismus pro přidávání nových zpracovatelských objektů na konec tohoto řetězce.
Ve variantě standardního modelu odpovědnosti mohou někteří zpracovatelé jednat jako dispečeři, schopný vysílat příkazy v různých směrech, tvořící a strom odpovědnosti. V některých případech k tomu může dojít rekurzivně, když objekty zpracování volají vyšší objekty zpracování pomocí příkazů, které se pokoušejí vyřešit nějakou menší část problému; v tomto případě pokračuje rekurze, dokud není příkaz zpracován, nebo dokud nebude prozkoumán celý strom. An XML tlumočník může fungovat tímto způsobem.
Tento vzor podporuje myšlenku volné spojení.
Vzorec odpovědnosti je strukturálně téměř totožný s řetězcem odpovědnosti dekoratér vzor, rozdíl je v tom, že pro dekoratéra zpracovávají požadavek všechny třídy, zatímco pro řetězec odpovědnosti zpracovává požadavek přesně jedna z tříd v řetězci. Jedná se o přísnou definici konceptu odpovědnosti v EU GoF rezervovat. Mnoho implementací (například protokolovací nástroje níže nebo zpracování událostí uživatelského rozhraní nebo filtry servletů v Javě atd.) Však umožňuje převzít odpovědnost několika prvkům v řetězci.
Přehled
Řetěz odpovědnosti [2]designový vzor je jedním z dvaceti tří známých GoF návrhové vzory které popisují běžná řešení opakujících se problémů s návrhem při navrhování flexibilního a opakovaně použitelného objektově orientovaného softwaru, tj. objektů, které lze snadněji implementovat, měnit, testovat a znovu použít.
Jaké problémy může návrhový vzor Chain of Responsibility vyřešit? [3]
- Je třeba se vyhnout propojení odesílatele požadavku s jeho příjemcem.
- Mělo by být možné, že požadavek může zpracovat více přijímačů.
Implementace požadavku přímo v rámci třídy, která požadavek odesílá, je nepružná, protože spojuje třídu s konkrétním přijímačem a znemožňuje podporu více přijímačů.
Jaké řešení popisuje návrhový vzor Chain of Responsibility?
- Definujte řetězec objektů přijímače, které mají v závislosti na podmínkách běhu odpovědnost buď zpracovat požadavek, nebo jej předat dalšímu příjemci v řetězci (pokud existuje).
To nám umožňuje odeslat požadavek do řetězce příjemců, aniž bychom museli vědět, který z nich zpracovává požadavek. Požadavek se předává podél řetězce, dokud příjemce nezpracovává požadavek. Odesílatel požadavku již není spojen s konkrétním příjemcem.
Viz také diagram tříd a sekvencí UML níže.
Struktura
Třída a sekvenční diagram UML

Ve výše uvedeném UML třídní diagram, Odesílatel
třída přímo neodkazuje na konkrétní třídu přijímače. Odesílatel
Odkazuje na Psovod
rozhraní pro vyřízení požadavku (handler.handleRequest ()
), což činí Odesílatel
nezávisle na tom, který přijímač zpracovává požadavek Přijímač 1
, Přijímač2
, a Přijímač3
třídy implementují Psovod
rozhraní buď zpracováním nebo předáním požadavku (v závislosti na podmínkách běhu).
The UML sekvenční diagram ukazuje interakce za běhu: V tomto příkladu je Odesílatel
volání objektu handleRequest ()
na přijímač1
objekt (typu Psovod
). The přijímač1
předá požadavek na přijímač2
, který požadavek dále předá přijímač3
, který zpracovává (provádí) požadavek.
Příklad
Příklad Java
Níže je uveden příklad tohoto vzoru v jazyce Java. Záznamník se vytváří pomocí řetězce záznamníků, z nichž každý je konfigurován s různými úrovněmi protokolu.
import java.util.Arrays;import java.util.EnumSet;import java.util.function.Consumer;@Funkčnírozhraníveřejnost rozhraní Logger { veřejnost výčet LogLevel { INFO, LADIT, VAROVÁNÍ, CHYBA, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR; veřejnost statický LogLevel[] Všechno() { vrátit se hodnoty(); } } abstraktní prázdnota zpráva(Tětiva zpráva, LogLevel vážnost); výchozí Logger appendNext(Logger nextLogger) { vrátit se (zpráva, vážnost) -> { zpráva(zpráva, vážnost); nextLogger.zpráva(zpráva, vážnost); }; } statický Logger writeLogger(LogLevel[] úrovně, Spotřebitel<Tětiva> stringConsumer) { EnumSet<LogLevel> soubor = EnumSet.kopie(Pole.jako seznam(úrovně)); vrátit se (zpráva, vážnost) -> { -li (soubor.obsahuje(vážnost)) { stringConsumer.akceptovat(zpráva); } }; } statický Logger consoleLogger(LogLevel... úrovně) { vrátit se writeLogger(úrovně, zpráva -> Systém.chybovat.tisk(„Writing to console:“ + zpráva)); } statický Logger emailLogger(LogLevel... úrovně) { vrátit se writeLogger(úrovně, zpráva -> Systém.chybovat.tisk(„Odesílání e-mailem:“ + zpráva)); } statický Logger fileLogger(LogLevel... úrovně) { vrátit se writeLogger(úrovně, zpráva -> Systém.chybovat.tisk("Zápis do souboru protokolu:" + zpráva)); } veřejnost statický prázdnota hlavní(Tětiva[] args) { // Budujte neměnný řetězec odpovědnosti Logger záznamník = consoleLogger(LogLevel.Všechno()) .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR)) .appendNext(fileLogger(LogLevel.VAROVÁNÍ, LogLevel.CHYBA)); // Zpracovává consoleLogger, protože konzole má LogLevel všech záznamník.zpráva("Zadávání funkce ProcessOrder ().", LogLevel.LADIT); záznamník.zpráva(„Záznam objednávky načten.“, LogLevel.INFO); // Zpracovává consoleLogger a emailLogger, protože emailLogger implementuje Functional_Error & Functional_Message záznamník.zpráva("Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1.", LogLevel.FUNCTIONAL_ERROR); záznamník.zpráva(„Objednávka odeslána.“, LogLevel.FUNCTIONAL_MESSAGE); // Zpracovává consoleLogger a fileLogger, protože fileLogger implementuje Varování & Chyba záznamník.zpráva(„V Branch DataBase chybí podrobnosti o adrese zákazníka.“, LogLevel.VAROVÁNÍ); záznamník.zpráva("Údaje o adrese zákazníka chybí v databázi Data organizace.", LogLevel.CHYBA); }}
Příklad C #
Tento příklad C # používá aplikaci logger k výběru různých zdrojů na základě úrovně protokolu;
jmenný prostor ChainOfResponsibility{ [Vlajky] veřejnost výčet LogLevel { Žádný = 0, // 0 Info = 1, // 1 Ladit = 2, // 10 Varování = 4, // 100 Chyba = 8, // 1000 Funkční zpráva = 16, // 10000 FunctionalError = 32, // 100000 Všechno = 63 // 111111 } /// / / / Abstrakt Handler v řetězci vzorce odpovědnosti. /// veřejnost abstraktní třída Logger { chráněný LogLevel logMask; // Další Handler v řetězci chráněný Logger další; veřejnost Logger(LogLevel maska) { tento.logMask = maska; } /// /// Nastaví další protokolovač, aby vytvořil seznam / řetězec manipulátorů. /// veřejnost Logger SetNext(Logger nextlogger) { Logger lastLogger = tento; zatímco (lastLogger.další != nula) { lastLogger = lastLogger.další; } lastLogger.další = nextlogger; vrátit se tento; } veřejnost prázdnota Zpráva(tětiva zpráva, LogLevel vážnost) { -li ((vážnost & logMask) != 0) // Platí pouze v případě, že některý z bitů logMask je nastaven v závažnosti { Napsat zprávu(zpráva); } -li (další != nula) { další.Zpráva(zpráva, vážnost); } } abstraktní chráněný prázdnota Napsat zprávu(tětiva zpráva); } veřejnost třída ConsoleLogger : Logger { veřejnost ConsoleLogger(LogLevel maska) : základna(maska) { } chráněný přepsat prázdnota Napsat zprávu(tětiva zpráva) { Řídicí panel.WriteLine(„Writing to console:“ + zpráva); } } veřejnost třída EmailLogger : Logger { veřejnost EmailLogger(LogLevel maska) : základna(maska) { } chráněný přepsat prázdnota Napsat zprávu(tětiva zpráva) { // Zástupný symbol pro logiku odesílání pošty, obvykle jsou e-mailové konfigurace uloženy v konfiguračním souboru. Řídicí panel.WriteLine(„Odesílání e-mailem:“ + zpráva); } } třída FileLogger : Logger { veřejnost FileLogger(LogLevel maska) : základna(maska) { } chráněný přepsat prázdnota Napsat zprávu(tětiva zpráva) { // Zástupný symbol pro logiku psaní souborů Řídicí panel.WriteLine("Zápis do souboru protokolu:" + zpráva); } } veřejnost třída Program { veřejnost statický prázdnota Hlavní(tětiva[] args) { // Budujte řetězec odpovědnosti Logger záznamník; záznamník = Nový ConsoleLogger(LogLevel.Všechno) .SetNext(Nový EmailLogger(LogLevel.Funkční zpráva | LogLevel.FunctionalError)) .SetNext(Nový FileLogger(LogLevel.Varování | LogLevel.Chyba)); // Zpracovává ConsoleLogger, protože konzole má loglevel všech záznamník.Zpráva("Zadávání funkce ProcessOrder ().", LogLevel.Ladit); záznamník.Zpráva("Záznam objednávky načten.", LogLevel.Info); // Zpracovává ConsoleLogger a FileLogger, protože filelogger implementuje Varování & Chyba záznamník.Zpráva(„V Branch DataBase chybí podrobnosti o adrese zákazníka.“, LogLevel.Varování); záznamník.Zpráva("Údaje o adrese zákazníka chybí v databázi Data organizace.", LogLevel.Chyba); // Zpracovává ConsoleLogger a EmailLogger, protože implementuje funkční chybu záznamník.Zpráva("Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1.", LogLevel.FunctionalError); // Zpracovává ConsoleLogger a EmailLogger záznamník.Zpráva(„Objednávka odeslána.“, LogLevel.Funkční zpráva); } }} /* VýstupZápis do konzoly: Zadání funkce ProcessOrder ().Zápis do konzoly: Záznam objednávky byl načten.Zápis do konzoly: V Branch DataBase chybí podrobnosti o adrese zákazníka.Zápis do souboru protokolu: V pobočkové databázi chybí podrobnosti o adrese zákazníka.Zápis do konzoly: V adrese organizace DataBase chybí podrobnosti adresy zákazníka.Zápis do souboru protokolu: V adresáři organizace chybí podrobnosti adresy zákazníka.Zápis do konzoly: Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1.Odeslání e-mailem: Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1.Zápis do konzoly: Objednávka Odesláno.Odesílání e-mailem: Objednávka odeslána.*/
Příklad krystalu
výčet LogLevel Žádný Info Ladit Varování Chyba Funkční zpráva FunctionalError Všechnokonecabstraktní třída Logger vlastnictví log_levels vlastnictví další : Logger | Nula def inicializovat(*úrovně) @log_levels = [] z LogLevel úrovně.každý dělat |úroveň| @log_levels << úroveň konec konec def zpráva(zpráva : Tětiva, vážnost : LogLevel) -li @log_levels.zahrnuje?(LogLevel::Všechno) || @log_levels.zahrnuje?(vážnost) napsat zprávu(zpráva) konec @další.Snaž se(&.zpráva(zpráva, vážnost)) konec abstraktní def napsat zprávu(zpráva : Tětiva)konectřída ConsoleLogger < Logger def napsat zprávu(zpráva : Tětiva) uvádí "Zápis do konzoly: #{zpráva}" koneckonectřída EmailLogger < Logger def napsat zprávu(zpráva : Tětiva) uvádí „Odesílání prostřednictvím e-mailu: #{zpráva}" koneckonectřída FileLogger < Logger def napsat zprávu(zpráva : Tětiva) uvádí "Zápis do souboru protokolu: #{zpráva}" koneckonec# Program# Budujte řetězec odpovědnostizáznamník = ConsoleLogger.Nový(LogLevel::Všechno)logger1 = záznamník.další = EmailLogger.Nový(LogLevel::Funkční zpráva, LogLevel::FunctionalError)logger2 = logger1.další = FileLogger.Nový(LogLevel::Varování, LogLevel::Chyba)# Zpracovává ConsoleLogger, protože konzole má loglevel všechzáznamník.zpráva("Zadávání funkce ProcessOrder ().", LogLevel::Ladit)záznamník.zpráva(„Záznam objednávky načten.“, LogLevel::Info)# Zpracovává ConsoleLogger a FileLogger, protože filelogger implementuje Varování & Chybazáznamník.zpráva(„V Branch DataBase chybí podrobnosti o adrese zákazníka.“, LogLevel::Varování)záznamník.zpráva("Údaje o adrese zákazníka chybí v databázi Data organizace.", LogLevel::Chyba)# Zpracovává ConsoleLogger a EmailLogger, protože implementuje funkční chybuzáznamník.zpráva("Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1.", LogLevel::FunctionalError)# Zpracovali ConsoleLogger a EmailLoggerzáznamník.zpráva(„Objednávka odeslána.“, LogLevel::Funkční zpráva)
Výstup
Zápis do konzoly: Zadání funkce ProcessOrder (). Zápis do konzoly: Načtení záznamu objednávky. Psaní do konzoly: Údaje o adrese zákazníka chybí v pobočce DataBase. Psaní do souboru protokolu: Údaje o adrese zákazníka chybí v pobočce DataBase. Psaní do konzoly: Podrobnosti o adrese zákazníka chybí v Organisation DataBase.Writing to Log File: Customer Address details missing in Organization DataBase.Writing to console: Unable to process Order ORD1 Dated D1 For Customer C1. Odeslání prostřednictvím e-mailu: Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1. konzole: Objednávka odeslána. Odeslání e-mailem: Objednávka odeslána.
Příklad Pythonu
"""Příklad vzoru řetězce odpovědnosti."""z abc import ABCMeta, abstraktní metodaz výčet import Výčet, autotřída LogLevel(Výčet): "" "Úrovně protokolu Enum." "" ŽÁDNÝ = auto() INFO = auto() LADIT = auto() VAROVÁNÍ = auto() CHYBA = auto() FUNCTIONAL_MESSAGE = auto() FUNCTIONAL_ERROR = auto() VŠECHNO = auto()třída Logger: "" "Abstraktní obslužný program v řetězci vzorce odpovědnosti." "" __metaclass__ = ABCMeta další = Žádný def __init__(já, úrovně) -> Žádný: "" "Inicializovat nový záznamník. Argumenty: levels (list [str]): Seznam úrovní protokolu. """ já.log_levels = [] pro úroveň v úrovně: já.log_levels.připojit(úroveň) def set_next(já, next_logger: Logger): "" "Nastavit dalšího odpovědného protokolovače v řetězci. Argumenty: next_logger (Logger): Další odpovědný logger. Vrátí: Logger: Další odpovědný logger. """ já.další = next_logger vrátit se já.další def zpráva(já, zpráva: str, vážnost: LogLevel) -> Žádný: "" "Obslužný program pro psaní zpráv. Argumenty: msg (str): Řetězec zprávy. závažnost (LogLevel): Závažnost zprávy jako výčet na úrovni protokolu. """ -li LogLevel.VŠECHNO v já.log_levels nebo vážnost v já.log_levels: já.napsat zprávu(zpráva) -li já.další je ne Žádný: já.další.zpráva(zpráva, vážnost) @abstractmethod def napsat zprávu(já, zpráva: str) -> Žádný: "" "Abstraktní metoda psaní zprávy. Argumenty: msg (str): Řetězec zprávy. Vyvolává: NotImplementedError """ vyzdvihnout NotImplementedError(„Tuto metodu byste měli implementovat.“)třída ConsoleLogger(Logger): def napsat zprávu(já, zpráva: str) -> Žádný: "" "Přepíše nadřazenou abstraktní metodu zápisu do konzoly. Argumenty: msg (str): Řetězec zprávy. """ tisk(„Writing to console:“, zpráva)třída EmailLogger(Logger): "" "Přepíše nadřazenou abstraktní metodu pro odeslání e-mailu. Argumenty: msg (str): Řetězec zprávy. """ def napsat zprávu(já, zpráva: str) -> Žádný: tisk(F„Odesílání e-mailem: {zpráva}")třída FileLogger(Logger): "" "Přepíše nadřazenou abstraktní metodu pro zápis souboru. Argumenty: msg (str): Řetězec zprávy. """ def napsat zprávu(já, zpráva: str) -> Žádný: tisk(F"Zápis do souboru protokolu: {zpráva}")def hlavní(): "" "Budování řetězce odpovědnosti." "" záznamník = ConsoleLogger([LogLevel.VŠECHNO]) email_logger = záznamník.set_next( EmailLogger([LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR]) ) # Protože později nemusíme nikde používat instanci záznamníku souborů # Nenastavíme pro něj žádnou hodnotu. email_logger.set_next( FileLogger([LogLevel.VAROVÁNÍ, LogLevel.CHYBA]) ) # ConsoleLogger bude tuto část kódu zpracovávat od zprávy # má úroveň protokolu ze všech záznamník.zpráva("Zadávání funkce ProcessOrder ().", LogLevel.LADIT) záznamník.zpráva("Záznam objednávky načten.", LogLevel.INFO) # ConsoleLogger a FileLogger tuto část zpracují od dataloggeru # implementuje VAROVÁNÍ a CHYBU záznamník.zpráva( „V Branch DataBase chybí podrobnosti o adrese zákazníka.“, LogLevel.VAROVÁNÍ ) záznamník.zpráva( "Údaje o adrese zákazníka chybí v databázi Data organizace.", LogLevel.CHYBA ) # ConsoleLogger a EmailLogger budou tuto část zpracovávat při jejich implementaci # funkční chyba záznamník.zpráva( "Nelze zpracovat objednávku ORD1 ze dne D1 pro zákazníka C1.", LogLevel.FUNCTIONAL_ERROR ) záznamník.zpráva(„Objednávka byla odeslána.“, LogLevel.FUNCTIONAL_MESSAGE)-li __název__ == "__hlavní__": hlavní()
Implementace
Kakao a kakao Touch
The Kakao a Kakaový dotek rámce, používané pro OS X a iOS aplikace aktivně používají vzor odpovědnosti pro zpracování událostí. Objekty, které se účastní řetězce, se nazývají odpovídač objekty, dědí z NSResponder
(OS X) /UIResponder
(iOS) třída. Všechny objekty zobrazení (NSView
/UIView
), zobrazit objekty řadiče (NSViewController
/UIViewController
), okenní objekty (NSWindow
/UIWindow
) a objekt aplikace (NSApplication
/UIApplication
) jsou objekty odpovědí.
Typicky, když pohled přijme událost, kterou nedokáže zpracovat, odešle ji do svého superview, dokud nedosáhne kontroleru pohledu nebo objektu okna. Pokud okno nemůže zpracovat událost, událost se odešle do objektu aplikace, který je posledním objektem v řetězci. Například:
- V systému OS X lze pohyb texturovaného okna myší provádět z libovolného místa (nejen z záhlaví), pokud na tomto místě není zobrazení, které zpracovává události přetahování, jako jsou ovládací prvky posuvníku. Pokud takové zobrazení (nebo superview) neexistuje, jsou události přetažení odeslány do řetězce do okna, které událost přetažení zpracovává.
- V systému iOS je typické zpracovávat události zobrazení v řadiči zobrazení, který spravuje hierarchii zobrazení, namísto podtřídy samotného zobrazení. Vzhledem k tomu, že řadič zobrazení leží v řetězci respondentů po všech jeho spravovaných dílčích zobrazeních, může zachytit jakékoli události zobrazení a zpracovat je.
Viz také
Reference
- ^ „Archivovaná kopie“. Archivovány od originál dne 2018-02-27. Citováno 2013-11-08.CS1 maint: archivovaná kopie jako titul (odkaz)
- ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Návrhové vzory: Prvky opakovaně použitelného objektově orientovaného softwaru. Addison Wesley. str.223ff. ISBN 0-201-63361-2.CS1 maint: více jmen: seznam autorů (odkaz)
- ^ „Návrhový vzor řetězce odpovědnosti - problém, řešení a použitelnost“. w3sDesign.com. Citováno 2017-08-12.
- ^ „Návrhový vzor řetězce odpovědnosti - struktura a spolupráce“. w3sDesign.com. Citováno 2017-08-12.