Ланцюжок відповідальностей

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

Ланцюжок відповідальностей - шаблон об'єктно-орієнтованого дизайну у програмуванні.

В об'єктно-орієнтованому дизайні, шаблон «ланцюжок відповідальностей» є шаблоном, який складається з об'єктів «команда» і серії об'єктів-виконавців. Кожен об'єкт-виконавець має логіку, що описує типи об'єктів «команда», які він може обробляти, а також як передати далі ланцюжком ті об'єкти-команди, що він не може обробляти. Крім того існує механізм для додавання нових призначених для обробки об'єктів у кінець ланцюжка.

У варіаціях стандартного ланцюжка відповідальностей, деякі обробники можуть бути в ролі диспетчерів, які здатні відсилати команди в різні напрямки формуючи Дерево відподальності. У деяких випадках це можна організувати рекурсивно, коли об'єкт який оброблюється викликає об'єкт вищого рівня обробки з командою що пробує вирішити меншу частину проблеми; у цьому випадку рекурсія продовжує виконуватися поки команда не виконається, або поки дерево повністю не буде оброблене. 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); // не обробиться
		}
	}
}

Джерела[ред.ред. код]

Література[ред.ред. код]

Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М.: «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5