Ланцюжок відповідальностей
Ланцюжок відповідальностей - шаблон об'єктно-орієнтованого дизайну у програмуванні.
В об'єктно-орієнтованому дизайні, шаблон «ланцюжок відповідальностей» є шаблоном, який складається з об'єктів «команда» і серії об'єктів-виконавців. Кожен об'єкт-виконавець має логіку, що описує типи об'єктів «команда», які він може обробляти, а також як передати далі ланцюжком ті об'єкти-команди, що він не може обробляти. Крім того існує механізм для додавання нових призначених для обробки об'єктів у кінець ланцюжка.
У варіаціях стандартного ланцюжка відповідальностей, деякі обробники можуть бути в ролі диспетчерів, які здатні відсилати команди в різні напрямки формуючи Дерево відподальності. У деяких випадках це можна організувати рекурсивно, коли об'єкт який оброблюється викликає об'єкт вищого рівня обробки з командою що пробує вирішити меншу частину проблеми; у цьому випадку рекурсія продовжує виконуватися поки команда не виконається, або поки дерево повністю не буде оброблене. XML-інтерпретатор (проаналізований, але який ще не було поставлено на виконання) може бути хорошим прикладом.
Цей шаблон застосовує ідею слабкого зв'язку, який розглядається як програмування у найкращих практиках.
Шаблон рекомендований для використання в умовах:
- В розроблюваної системі є група об'єктів, які можуть обробляти повідомлення певного типу;
- Всі повідомлення повинні бути оброблені хоча б одним об'єктом системи;
- Повідомлення в системі обробляються за схемою «обробив сам або передай іншому», тобто одні повідомлення обробляються на тому рівні, де вони отримані, а інші пересилаються об'єктам іншого рівня.
- Відокремлює відправника запиту та його одержувачів.
- Спрощує ваш об'єкт, оскільки він не повинен знати про структуру ланцюга та зберігати прямі посилання на його членів.
- Дозволяє динамічно додавати або видаляти відповідальність, змінюючи учасників або замовлення ланцюга.
- Важко спостерігати характеристики виконання та налагодження.
- Ланцюжок обов’язків та Декоратор виконують операції через серію пов’язаних об’єктів. Але Ланцюжок обов’язків може виконувати довільні дії, незалежні одна від одної, а також у будь-який момент переривати виконання, а декоратори розширюють певну дію, не ламаючи інтерфейс базової операції і не перериваючи виконання інших декораторів.
Наведений нижче 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 );
}
}
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);
}
}
}
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);
}
}
}
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); // не обробиться
}
}
}
#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-71863-5.