Ланцюжок відповідальностей - шаблон об'єктно-орієнтованого дизайну у програмуванні.
В об'єктно-орієнтованому дизайні, шаблон «ланцюжок відповідальностей» є шаблоном, який складається з об'єктів «команда» і серії об'єктів-виконавців. Кожен об'єкт-виконавець має логіку, що описує типи об'єктів «команда», які він може обробляти, а також як передати далі ланцюжком ті об'єкти-команди, що він не може обробляти. Крім того існує механізм для додавання нових призначених для обробки об'єктів у кінець ланцюжка.
У варіаціях стандартного ланцюжка відповідальностей, деякі обробники можуть бути в ролі диспетчерів, які здатні відсилати команди в різні напрямки формуючи Дерево відподальності. У деяких випадках це можна організувати рекурсивно, коли об'єкт який оброблюється викликає об'єкт вищого рівня обробки з командою що пробує вирішити меншу частину проблеми; у цьому випадку рекурсія продовжує виконуватися поки команда не виконається, або поки дерево повністю не буде оброблене. XML-інтерпретатор (проаналізований, але який ще не було поставлено на виконання) може бути хорошим прикладом.
Цей шаблон застосовує ідею слабкого зв'язку, який розглядається як програмування у найкращих практиках.
Застосування
Шаблон рекомендований для використання в умовах:
- В розроблюваної системі є група об'єктів, які можуть обробляти повідомлення певного типу;
- Всі повідомлення повинні бути оброблені хоча б одним об'єктом системи;
- Повідомлення в системі обробляються за схемою «обробив сам або передай іншому», тобто одні повідомлення обробляються на тому рівні, де вони отримані, а інші пересилаються об'єктам іншого рівня.
Переваги
- Відокремлює відправника запиту та його одержувачів.
- Спрощує ваш об'єкт, оскільки він не повинен знати про структуру ланцюга та зберігати прямі посилання на його членів.
- Дозволяє динамічно додавати або видаляти відповідальність, змінюючи учасників або замовлення ланцюга.
Недоліки
- Важко спостерігати характеристики виконання та налагодження.
Зв'язок з іншими патернами
- Ланцюжок обов’язків та Декоратор виконують операції через серію пов’язаних об’єктів. Але Ланцюжок обов’язків може виконувати довільні дії, незалежні одна від одної, а також у будь-який момент переривати виконання, а декоратори розширюють певну дію, не ламаючи інтерфейс базової операції і не перериваючи виконання інших декораторів.
Приклади
Java
Наведений нижче Java код ілюструє шаблон на прикладі реалізації класу для логування. Кожен обробник для логування вирішує чи потрібна якась додаткова подія на його рівні логування, і потім передає далі повідомлення наступному обробнику.
Вивід є таким:
Запис до stdout: Entering function y. Запис до stdout: Step1 completed. Відправка через e-mail: Step1 completed. Запис до stdout: An error has occurred. Sending via e-mail: An error has occurred. Writing to stderr: An error has occurred.
Зверніть увагу, що цей приклад не треба розцінювати як рекомендацію писати логування для класів таким чином як тут приводиться. Це просто приклад.
Так само зверніть увагу що у чистому втіленні ланцюга відповідальностей, логувальник не буде передавати відповідальності далі, по інших ланках після обробки повідомлення. У наведеному прикладі повідомлення буде передане далі по ланках, незалежно від того було воно оброблене чи ні.
import java.util.*; abstract class Logger { public static int ERR = 3; public static int NOTICE = 5; public static int DEBUG = 7; protected int mask; //Наступний елемент в ланцюжку відповідальності protected Logger next; public void setNext( Logger l) { next = l; } public void message( String msg, int priority ) { if ( priority <= mask ) { writeMessage( msg ); if ( next != null ) { next.message( msg, priority ); } } } abstract protected void writeMessage( String msg ); } class StdoutLogger extends Logger { public StdoutLogger( int mask ) { this.mask = mask; } protected void writeMessage( String msg ) { System.out.println( "Запис до stdout: " + msg ); } } class EmailLogger extends Logger { public EmailLogger( int mask ) { this.mask = mask; } protected void writeMessage( String msg ) { System.out.println( "Відправка через email: " + msg ); } } class StderrLogger extends Logger { public StderrLogger( int mask ) { this.mask = mask; } protected void writeMessage( String msg ) { System.err.println( "Відправка до stderr: " + msg ); } } public class ChainOfResponsibilityExample { public static void main( String[] args ) { // Build the chain of responsibility Logger l,l1,l2; l = new StdoutLogger(Logger.DEBUG); l1 = new EmailLogger(Logger.NOTICE); l.setNext(l1); l2 = new StderrLogger(Logger.ERR); l1.setNext(l2); // Handled by StdoutLogger l.message( "Entering function y.", Logger.DEBUG ); // Handled by StdoutLogger and EmailLogger l.message( "Step1 completed.", Logger.NOTICE ); // Handled by all three loggers l.message( "An error has occurred.", Logger.ERR ); } }
C# Рішення що базується на абстрактному класі
using System; namespace Chain_of_responsibility{ public abstract class Chain{ private Chain _next; public Chain Next{ get{return _next;} set{_next = value;} } public void Message(object command){ if ( Process(command) == false && _next != null ){ _next.Message(command); } } public static Chain operator +(Chain lhs, Chain rhs){ Chain last = lhs; while ( last.Next != null ){ last = last.Next; } last.Next = rhs; return lhs; } protected abstract bool Process(object command); } public class StringHandler : Chain{ protected override bool Process(object command){ if ( command is string ){ Console.WriteLine("StringHandler can handle this message : {0}",(string)command); return true; } return false; } } public class IntegerHandler : Chain{ protected override bool Process(object command){ if ( command is int ){ Console.WriteLine("IntegerHandler can handle this message : {0}",(int)command); return true; } return false; } } public class NullHandler : Chain{ protected override bool Process(object command){ if ( command == null ){ Console.WriteLine("NullHandler can handle this message."); return true; } return false; } } public class IntegerBypassHandler : Chain{ protected override bool Process(object command){ if ( command is int ){ Console.WriteLine("IntegerBypassHandler can handle this message : {0}",(int)command); return false; // Always pass to next handler } return false; // завжди передавати наступному обробникові } } class TestMain{ static void Main(string[] args){ Chain chain = new StringHandler(); chain += new IntegerBypassHandler(); chain += new IntegerHandler(); chain += new IntegerHandler(); // ніколи не дойде сюди chain += new NullHandler(); chain.Message("1st string value"); chain.Message(100); chain.Message("2nd string value"); chain.Message(4.7f); // не обробляється chain.Message(null); } } }
C#, Інтерфейс базоване рішення
using System; using System.Collections; using System.Collections.Generic; namespace Chain_of_responsibility{ public interface IChain{ bool Process(object command); } public class Chain{ private List<IChain> list; public List<IChain> List{ get{ return this.list; } } public Chain(){ this.list = new List<IChain>(); } public void Message(object command){ foreach (IChain item in this.list){ bool result = item.Process(command); if (result == true) break; } } public void Add(IChain handler){ this.list.Add(handler); } } public class StringHandler : IChain{ public bool Process(object command){ if (command is string){ Console.WriteLine("StringHandler can handle this message : {0}", (string)command); return true; } return false; } } public class IntegerHandler : IChain{ public bool Process(object command){ if (command is int){ Console.WriteLine("IntegerHandler can handle this message : {0}", (int)command); return true; } return false; } } public class NullHandler : IChain{ public bool Process(object command){ if (command == null){ Console.WriteLine("NullHandler can handle this message."); return true; } return false; } } public class IntegerBypassHandler : IChain{ public bool Process(object command){ if (command is int){ Console.WriteLine("IntegerBypassHandler can handle this message : {0}", (int)command); return false; // завжди передавати наступному обробнику } return false; // завжди передавати наступному обробнику } } class TestMain{ static void Main(string[] args){ Chain chain = new Chain(); chain.Add(new StringHandler()); chain.Add(new IntegerBypassHandler()); chain.Add(new IntegerHandler()); chain.Add(new IntegerHandler()); // Ніколи не виконається chain.Add(new NullHandler()); chain.Message("1st string value"); chain.Message(100); chain.Message("2nd string value"); chain.Message(4.7f); // Не виконається chain.Message(null); } } }
C#, Рішення що базується на делегатах
using System; namespace Chain_of_responsibility{ public static class StaticState{ private static int count; public static bool StringHandler(object command){ if ( command is string ){ string th; count++; if ( count % 10 == 1 ) th = "st"; else if ( count % 10 == 2 ) th = "nd"; else if ( count % 10 == 3 ) th = "rd"; else th = "th"; Console.WriteLine("StringHandler can handle this {0}{1} message : {2}",count,th,(string)command); return true; } return false; } } public static class NoState { public static bool StringHandler2(object command) { if ( command is string ){ Console.WriteLine("StringHandler2 can handle this message : {0}",(string)command); return true; } return false; } public static bool IntegerHandler(object command){ if ( command is int ){ Console.WriteLine("IntegerHandler can handle this message : {0}",(int)command); return true; } return false; } } public static class Chain{ public delegate bool MessageHandler(object message); public static event MessageHandler Message; public static void Process(object message){ foreach(MessageHandler handler in Message.GetInvocationList()){ if(handler(message)) break; } } } class TestMain{ static void Main(string[] args){ Chain.Message += StaticState.StringHandler; Chain.Message += NoState.StringHandler2; Chain.Message += NoState.IntegerHandler; Chain.Message += NoState.IntegerHandler; Chain.Process("1st string value"); Chain.Process(100); Chain.Process("2nd string value"); Chain.Process(4.7f); // не обробиться } } }
C++
#include <iostream> #include <vector> using namespace std; template< typename T> void purge(T& cont) { for (typename T::iterator it = cont.begin(); it != cont.end(); ++it) { delete *it; } cont.clear(); } enum Answer { NO, YES }; // загальний спосіб вирішення struct GimmeStrategy { virtual Answer canIHave() = 0; virtual ~GimmeStrategy() {} }; // конкрентні способи struct AskMom : public GimmeStrategy { Answer canIHave() { cout << " Mooom ? Can I have this ? " << endl; return NO; } }; struct AskDad : public GimmeStrategy { Answer canIHave() { cout << " Dad.I really need this!" << endl; return NO; } }; struct AskGrandpa : public GimmeStrategy { Answer canIHave() { cout << " Grandpa, is it my birthday yet ? " << endl; return NO; } }; struct AskGrandma : public GimmeStrategy { Answer canIHave() { cout << " Grandma, I really love you!" << endl; return YES; } }; class Gimme : public GimmeStrategy { private: vector< GimmeStrategy*> chain; public: Gimme() { chain.push_back(new AskMom()); chain.push_back(new AskDad()); chain.push_back(new AskGrandpa()); chain.push_back(new AskGrandma()); } Answer canIHave() { vector< GimmeStrategy*> ::iterator it = chain.begin(); while (it != chain.end()) { if ((*it++)->canIHave() == YES) { return YES; } } cout << " whiiiilnnne!" << endl; return NO; } ~Gimme() { purge(chain); } }; // Альтернативний спосіб перебору стратегій – узагальнена рекурсія class Gimme2 : public GimmeStrategy { private: vector< GimmeStrategy*> chain; size_t toTry; public: Gimme2() : toTry(0) { chain.push_back(new AskMom()); chain.push_back(new AskDad()); chain.push_back(new AskGrandpa()); chain.push_back(new AskDad()); chain.push_back(new AskMom()); chain.push_back(new AskGrandma()); } Answer canIHave() { if (toTry >= chain.size()) return NO; if (chain[toTry++]->canIHave() == YES) return YES; return canIHave(); } ~Gimme2() { purge(chain); } }; void main() { Gimme Boy; if ((bool)Boy.canIHave()) cout << " *** Gimme is winner!\n"; else cout << " *** You must try again, Gimme...\n"; }
Джерела
- Design Patterns: Elements of Reusable Object-Oriented Software [ 9 листопада 2012 у Wayback Machine.]
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — .
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Lancyuzhok vidpovidalnostej shablon ob yektno oriyentovanogo dizajnu u programuvanni V ob yektno oriyentovanomu dizajni shablon lancyuzhok vidpovidalnostej ye shablonom yakij skladayetsya z ob yektiv komanda i seriyi ob yektiv vikonavciv Kozhen ob yekt vikonavec maye logiku sho opisuye tipi ob yektiv komanda yaki vin mozhe obroblyati a takozh yak peredati dali lancyuzhkom ti ob yekti komandi sho vin ne mozhe obroblyati Krim togo isnuye mehanizm dlya dodavannya novih priznachenih dlya obrobki ob yektiv u kinec lancyuzhka U variaciyah standartnogo lancyuzhka vidpovidalnostej deyaki obrobniki mozhut buti v roli dispetcheriv yaki zdatni vidsilati komandi v rizni napryamki formuyuchi Derevo vidpodalnosti U deyakih vipadkah ce mozhna organizuvati rekursivno koli ob yekt yakij obroblyuyetsya viklikaye ob yekt vishogo rivnya obrobki z komandoyu sho probuye virishiti menshu chastinu problemi u comu vipadku rekursiya prodovzhuye vikonuvatisya poki komanda ne vikonayetsya abo poki derevo povnistyu ne bude obroblene XML interpretator proanalizovanij ale yakij she ne bulo postavleno na vikonannya mozhe buti horoshim prikladom Cej shablon zastosovuye ideyu slabkogo zv yazku yakij rozglyadayetsya yak programuvannya u najkrashih praktikah ZastosuvannyaShablon rekomendovanij dlya vikoristannya v umovah V rozroblyuvanoyi sistemi ye grupa ob yektiv yaki mozhut obroblyati povidomlennya pevnogo tipu Vsi povidomlennya povinni buti obrobleni hocha b odnim ob yektom sistemi Povidomlennya v sistemi obroblyayutsya za shemoyu obrobiv sam abo peredaj inshomu tobto odni povidomlennya obroblyayutsya na tomu rivni de voni otrimani a inshi peresilayutsya ob yektam inshogo rivnya PerevagiVidokremlyuye vidpravnika zapitu ta jogo oderzhuvachiv Sproshuye vash ob yekt oskilki vin ne povinen znati pro strukturu lancyuga ta zberigati pryami posilannya na jogo chleniv Dozvolyaye dinamichno dodavati abo vidalyati vidpovidalnist zminyuyuchi uchasnikiv abo zamovlennya lancyuga NedolikiVazhko sposterigati harakteristiki vikonannya ta nalagodzhennya Zv yazok z inshimi paternamiLancyuzhok obov yazkiv ta Dekorator vikonuyut operaciyi cherez seriyu pov yazanih ob yektiv Ale Lancyuzhok obov yazkiv mozhe vikonuvati dovilni diyi nezalezhni odna vid odnoyi a takozh u bud yakij moment pererivati vikonannya a dekoratori rozshiryuyut pevnu diyu ne lamayuchi interfejs bazovoyi operaciyi i ne pererivayuchi vikonannya inshih dekoratoriv PrikladiJava Navedenij nizhche Java kod ilyustruye shablon na prikladi realizaciyi klasu dlya loguvannya Kozhen obrobnik dlya loguvannya virishuye chi potribna yakas dodatkova podiya na jogo rivni loguvannya i potim peredaye dali povidomlennya nastupnomu obrobniku Vivid ye takim Zapis do stdout Entering function y Zapis do stdout Step1 completed Vidpravka cherez e mail Step1 completed Zapis do stdout An error has occurred Sending via e mail An error has occurred Writing to stderr An error has occurred Zvernit uvagu sho cej priklad ne treba rozcinyuvati yak rekomendaciyu pisati loguvannya dlya klasiv takim chinom yak tut privoditsya Ce prosto priklad Tak samo zvernit uvagu sho u chistomu vtilenni lancyuga vidpovidalnostej loguvalnik ne bude peredavati vidpovidalnosti dali po inshih lankah pislya obrobki povidomlennya U navedenomu prikladi povidomlennya bude peredane dali po lankah nezalezhno vid togo bulo vono obroblene chi ni Pochatkovij kod na Java import java util abstract class Logger public static int ERR 3 public static int NOTICE 5 public static int DEBUG 7 protected int mask Nastupnij element v lancyuzhku vidpovidalnosti protected Logger next public void setNext Logger l next l public void message String msg int priority if priority lt mask writeMessage msg if next null next message msg priority abstract protected void writeMessage String msg class StdoutLogger extends Logger public StdoutLogger int mask this mask mask protected void writeMessage String msg System out println Zapis do stdout msg class EmailLogger extends Logger public EmailLogger int mask this mask mask protected void writeMessage String msg System out println Vidpravka cherez email msg class StderrLogger extends Logger public StderrLogger int mask this mask mask protected void writeMessage String msg System err println Vidpravka do stderr msg public class ChainOfResponsibilityExample public static void main String args Build the chain of responsibility Logger l l1 l2 l new StdoutLogger Logger DEBUG l1 new EmailLogger Logger NOTICE l setNext l1 l2 new StderrLogger Logger ERR l1 setNext l2 Handled by StdoutLogger l message Entering function y Logger DEBUG Handled by StdoutLogger and EmailLogger l message Step1 completed Logger NOTICE Handled by all three loggers l message An error has occurred Logger ERR C Rishennya sho bazuyetsya na abstraktnomu klasi Pochatkovij kod na C using System namespace Chain of responsibility public abstract class Chain private Chain next public Chain Next get return next set next value public void Message object command if Process command false amp amp next null next Message command public static Chain operator Chain lhs Chain rhs Chain last lhs while last Next null last last Next last Next rhs return lhs protected abstract bool Process object command public class StringHandler Chain protected override bool Process object command if command is string Console WriteLine StringHandler can handle this message 0 string command return true return false public class IntegerHandler Chain protected override bool Process object command if command is int Console WriteLine IntegerHandler can handle this message 0 int command return true return false public class NullHandler Chain protected override bool Process object command if command null Console WriteLine NullHandler can handle this message return true return false public class IntegerBypassHandler Chain protected override bool Process object command if command is int Console WriteLine IntegerBypassHandler can handle this message 0 int command return false Always pass to next handler return false zavzhdi peredavati nastupnomu obrobnikovi class TestMain static void Main string args Chain chain new StringHandler chain new IntegerBypassHandler chain new IntegerHandler chain new IntegerHandler nikoli ne dojde syudi chain new NullHandler chain Message 1st string value chain Message 100 chain Message 2nd string value chain Message 4 7f ne obroblyayetsya chain Message null C Interfejs bazovane rishennya Pochatkovij kod na C using System using System Collections using System Collections Generic namespace Chain of responsibility public interface IChain bool Process object command public class Chain private List lt IChain gt list public List lt IChain gt List get return this list public Chain this list new List lt IChain gt public void Message object command foreach IChain item in this list bool result item Process command if result true break public void Add IChain handler this list Add handler public class StringHandler IChain public bool Process object command if command is string Console WriteLine StringHandler can handle this message 0 string command return true return false public class IntegerHandler IChain public bool Process object command if command is int Console WriteLine IntegerHandler can handle this message 0 int command return true return false public class NullHandler IChain public bool Process object command if command null Console WriteLine NullHandler can handle this message return true return false public class IntegerBypassHandler IChain public bool Process object command if command is int Console WriteLine IntegerBypassHandler can handle this message 0 int command return false zavzhdi peredavati nastupnomu obrobniku return false zavzhdi peredavati nastupnomu obrobniku class TestMain static void Main string args Chain chain new Chain chain Add new StringHandler chain Add new IntegerBypassHandler chain Add new IntegerHandler chain Add new IntegerHandler Nikoli ne vikonayetsya chain Add new NullHandler chain Message 1st string value chain Message 100 chain Message 2nd string value chain Message 4 7f Ne vikonayetsya chain Message null C Rishennya sho bazuyetsya na delegatah Pochatkovij kod na C using System namespace Chain of responsibility public static class StaticState private static int count public static bool StringHandler object command if command is string string th count if count 10 1 th st else if count 10 2 th nd else if count 10 3 th rd else th th Console WriteLine StringHandler can handle this 0 1 message 2 count th string command return true return false public static class NoState public static bool StringHandler2 object command if command is string Console WriteLine StringHandler2 can handle this message 0 string command return true return false public static bool IntegerHandler object command if command is int Console WriteLine IntegerHandler can handle this message 0 int command return true return false public static class Chain public delegate bool MessageHandler object message public static event MessageHandler Message public static void Process object message foreach MessageHandler handler in Message GetInvocationList if handler message break class TestMain static void Main string args Chain Message StaticState StringHandler Chain Message NoState StringHandler2 Chain Message NoState IntegerHandler Chain Message NoState IntegerHandler Chain Process 1st string value Chain Process 100 Chain Process 2nd string value Chain Process 4 7f ne obrobitsya C Priklad realizaciyi na movi S include lt iostream gt include lt vector gt using namespace std template lt typename T gt void purge T amp cont for typename T iterator it cont begin it cont end it delete it cont clear enum Answer NO YES zagalnij sposib virishennya struct GimmeStrategy virtual Answer canIHave 0 virtual GimmeStrategy konkrentni sposobi struct AskMom public GimmeStrategy Answer canIHave cout lt lt Mooom Can I have this lt lt endl return NO struct AskDad public GimmeStrategy Answer canIHave cout lt lt Dad I really need this lt lt endl return NO struct AskGrandpa public GimmeStrategy Answer canIHave cout lt lt Grandpa is it my birthday yet lt lt endl return NO struct AskGrandma public GimmeStrategy Answer canIHave cout lt lt Grandma I really love you lt lt endl return YES class Gimme public GimmeStrategy private vector lt GimmeStrategy gt chain public Gimme chain push back new AskMom chain push back new AskDad chain push back new AskGrandpa chain push back new AskGrandma Answer canIHave vector lt GimmeStrategy gt iterator it chain begin while it chain end if it gt canIHave YES return YES cout lt lt whiiiilnnne lt lt endl return NO Gimme purge chain Alternativnij sposib pereboru strategij uzagalnena rekursiya class Gimme2 public GimmeStrategy private vector lt GimmeStrategy gt chain size t toTry public Gimme2 toTry 0 chain push back new AskMom chain push back new AskDad chain push back new AskGrandpa chain push back new AskDad chain push back new AskMom chain push back new AskGrandma Answer canIHave if toTry gt chain size return NO if chain toTry gt canIHave YES return YES return canIHave Gimme2 purge chain void main Gimme Boy if bool Boy canIHave cout lt lt Gimme is winner n else cout lt lt You must try again Gimme n DzherelaDesign Patterns Elements of Reusable Object Oriented Software 9 listopada 2012 u Wayback Machine LiteraturaAlan Shallouej Dzhejms R Trott Shablony proektirovaniya Novyj podhod k obektno orientirovannomu analizu i proektirovaniyu Design Patterns Explained A New Perspective on Object Oriented Design M Vilyams 2002 288 s ISBN 0 201 71594 5