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