Ланцюжок відповідальностей
Ланцюжок відповідальностей - шаблон об'єктно-орієнтованого дизайну у програмуванні.
В об'єктно-орієнтованому дизайні, шаблон «ланцюжок відповідальностей» є шаблоном, який складається з об'єктів «команда» і серії об'єктів-виконавців. Кожен об'єкт-виконавець має логіку, що описує типи об'єктів «команда», які він може обробляти, а також як передати далі ланцюжком ті об'єкти-команди, що він не може обробляти. Крім того існує механізм для додавання нових призначених для обробки об'єктів у кінець ланцюжка.
У варіаціях стандартного ланцюжка відповідальностей, деякі обробники можуть бути в ролі диспетчерів, які здатні відсилати команди в різні напрямки формуючи Дерево відподальності. У деяких випадках це можна організувати рекурсивно, коли об'єкт який оброблюється викликає об'єкт вищого рівня обробки з командою що пробує вирішити меншу частину проблеми; у цьому випадку рекурсія продовжує виконуватися поки команда не виконається, або поки дерево повністю не буде оброблене. 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 с. — ISBN 0-201-71594-5.