Схема цепочки ответственности - Chain-of-responsibility pattern

В объектно-ориентированном проектировании , то цепочка обязанностей является поведенческий шаблон проектирования , состоящий из источника командных объектов и серии обработки объектов . Каждый объект обработки содержит логику, которая определяет типы командных объектов, которые он может обрабатывать; остальные передаются следующему обрабатывающему объекту в цепочке. Также существует механизм для добавления новых объектов обработки в конец этой цепочки.

В варианте стандартной модели цепочки ответственности некоторые обработчики могут действовать как диспетчеры , способные посылать команды в различных направлениях, формируя дерево ответственности . В некоторых случаях это может происходить рекурсивно, когда обрабатывающие объекты вызывают вышестоящие обрабатывающие объекты с командами, которые пытаются решить некоторую меньшую часть проблемы; в этом случае рекурсия продолжается до тех пор, пока команда не будет обработана или пока не будет исследовано все дерево. Таким образом может работать интерпретатор XML .

Этот паттерн продвигает идею слабой связи .

Шаблон цепочки ответственности структурно почти идентичен шаблону декоратора , с той разницей, что для декоратора все классы обрабатывают запрос, в то время как для цепочки ответственности запрос обрабатывает ровно один из классов в цепочке. Это строгое определение концепции ответственности в книге GoF . Однако многие реализации (например, средства ведения журнала ниже, обработка событий пользовательского интерфейса или фильтры сервлетов в Java и т. Д.) Позволяют нескольким элементам в цепочке брать на себя ответственность.

Обзор

Шаблон проектирования цепочка ответственности - один из двадцати трех хорошо известных шаблонов проектирования GoF, которые описывают общие решения повторяющихся проблем проектирования при разработке гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменить, протестировать. и повторно использовать.

Какие проблемы может решить шаблон проектирования «Цепочка ответственности»?

  • Следует избегать связывания отправителя запроса с его получателем.
  • Должно быть возможно, что более одного получателя могут обработать запрос.

Реализация запроса непосредственно в классе, который отправляет запрос, негибкая, потому что она связывает класс с конкретным получателем и делает невозможным поддержку нескольких получателей.

Какое решение описывает шаблон проектирования «Цепочка ответственности»?

  • Определите цепочку объектов-получателей, несущих ответственность, в зависимости от условий выполнения, либо за обработку запроса, либо за его пересылку следующему получателю в цепочке (если таковой имеется).

Это позволяет нам отправлять запрос цепочке получателей, не зная, какой из них обрабатывает запрос. Запрос передается по цепочке, пока получатель не обработает запрос. Отправитель запроса больше не привязан к конкретному получателю.

См. Также схему классов и последовательности UML ниже.

Состав

Схема классов и последовательности UML

Пример класса UML и диаграмма последовательности для шаблона проектирования «Цепочка ответственности».

В приведенной выше UML - диаграмме класса , то Senderкласс не относится к определенному классу приемника непосредственно. Вместо этого Senderотносится к Handlerинтерфейсу для обработки запроса ( handler.handleRequest()), который делает Senderнезависимым от того, какой получатель обрабатывает запрос. В Receiver1, Receiver2и Receiver3классы реализуют Handlerинтерфейс либо обработки или пересылки запроса ( в зависимости от условий времени выполнения). UML - диаграмма последовательности показывает время выполнения взаимодействий: В этом примере объект вызывает на объекте (типа ). Объект пересылает запрос тому , который, в свою очередь, пересылает запрос тому , который обрабатывает (выполняет) запрос.
SenderhandleRequest()receiver1Handlerreceiver1receiver2receiver3

Пример

Пример Java

Ниже приведен пример этого шаблона на Java. Регистратор создается с использованием цепочки регистраторов, каждый из которых настроен на разные уровни ведения журнала.

import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.Consumer;

@FunctionalInterface
public interface Logger {
    public enum LogLevel {
        INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

        public static LogLevel[] all() {
            return values();
        }
    }

    abstract void message(String msg, LogLevel severity);

    default Logger appendNext(Logger nextLogger) {
        return (msg, severity) -> {
            message(msg, severity);
            nextLogger.message(msg, severity);
        };
    }

    static Logger writeLogger(LogLevel[] levels, Consumer<String> stringConsumer) {
        EnumSet<LogLevel> set = EnumSet.copyOf(Arrays.asList(levels));
        return (msg, severity) -> {
            if (set.contains(severity)) {
                stringConsumer.accept(msg);
            }
        };
    }

    static Logger consoleLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Writing to console: " + msg));
    }

    static Logger emailLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Sending via email: " + msg));
    }

    static Logger fileLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Writing to Log File: " + msg));
    }
}

class Runner {
    public static void main(String[] args) {
        // Build an immutable chain of responsibility
        Logger logger = consoleLogger(LogLevel.all())
                .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
                .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR));

        // Handled by consoleLogger since the console has a LogLevel of all
        logger.message("Entering function ProcessOrder().", LogLevel.DEBUG);
        logger.message("Order record retrieved.", LogLevel.INFO);

        // Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
        logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR);
        logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE);

        // Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
        logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING);
        logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR);
    }
}

Пример C #

В этом примере C # приложение ведения журнала используется для выбора различных источников в зависимости от уровня журнала;

namespace ChainOfResponsibility
{
    [Flags]
    public enum LogLevel
    {
        None = 0,                 //        0
        Info = 1,                 //        1
        Debug = 2,                //       10
        Warning = 4,              //      100
        Error = 8,                //     1000
        FunctionalMessage = 16,   //    10000
        FunctionalError = 32,     //   100000
        All = 63                  //   111111
    }
 
    /// <summary>
    /// Abstract Handler in chain of responsibility pattern.
    /// </summary>
    public abstract class Logger
    {
        protected LogLevel logMask;
 
        // The next Handler in the chain
        protected Logger next;
 
        public Logger(LogLevel mask)
        {
            this.logMask = mask;
        }
 
        /// <summary>
        /// Sets the Next logger to make a list/chain of Handlers.
        /// </summary>
        public Logger SetNext(Logger nextlogger)
        {
            Logger lastLogger = this;

            while (lastLogger.next != null)
            {
                lastLogger = lastLogger.next;
            }

            lastLogger.next = nextlogger;
            return this;
        }
 
        public void Message(string msg, LogLevel severity)
        {
            if ((severity & logMask) != 0) // True only if any of the logMask bits are set in severity
            {
                WriteMessage(msg);
            }
            if (next != null) 
            {
                next.Message(msg, severity); 
            }
        }
 
        abstract protected void WriteMessage(string msg);
    }
 
    public class ConsoleLogger : Logger
    {
        public ConsoleLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            Console.WriteLine("Writing to console: " + msg);
        }
    }
 
    public class EmailLogger : Logger
    {
        public EmailLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            // Placeholder for mail send logic, usually the email configurations are saved in config file.
            Console.WriteLine("Sending via email: " + msg);
        }
    }
 
    class FileLogger : Logger
    {
        public FileLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            // Placeholder for File writing logic
            Console.WriteLine("Writing to Log File: " + msg);
        }
    }
 
    public class Program
    {
        public static void Main(string[] args)
        {
            // Build the chain of responsibility
            Logger logger;
            logger = new ConsoleLogger(LogLevel.All)
                             .SetNext(new EmailLogger(LogLevel.FunctionalMessage | LogLevel.FunctionalError))
                             .SetNext(new FileLogger(LogLevel.Warning | LogLevel.Error));
 
            // Handled by ConsoleLogger since the console has a loglevel of all
            logger.Message("Entering function ProcessOrder().", LogLevel.Debug);
            logger.Message("Order record retrieved.", LogLevel.Info);
 
            // Handled by ConsoleLogger and FileLogger since filelogger implements Warning & Error
            logger.Message("Customer Address details missing in Branch DataBase.", LogLevel.Warning);
            logger.Message("Customer Address details missing in Organization DataBase.", LogLevel.Error);
 
            // Handled by ConsoleLogger and EmailLogger as it implements functional error
            logger.Message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FunctionalError);
 
            // Handled by ConsoleLogger and EmailLogger
            logger.Message("Order Dispatched.", LogLevel.FunctionalMessage);
        }
    }
}
 
/* Output
Writing to console: Entering function ProcessOrder().
Writing to console: Order record retrieved.
Writing to console: Customer Address details missing in Branch DataBase.
Writing to Log File: Customer Address details missing in Branch DataBase.
Writing to console: Customer Address details missing in Organization DataBase.
Writing to Log File: Customer Address details missing in Organization DataBase.
Writing to console: Unable to Process Order ORD1 Dated D1 For Customer C1.
Sending via email: Unable to Process Order ORD1 Dated D1 For Customer C1.
Writing to console: Order Dispatched.
Sending via email: Order Dispatched.
*/

Пример кристалла

enum LogLevel
  None
  Info
  Debug
  Warning
  Error
  FunctionalMessage
  FunctionalError
  All
end

abstract class Logger
  property log_levels
  property next : Logger | Nil

  def initialize(*levels)
    @log_levels = [] of LogLevel

    levels.each do |level|
      @log_levels << level
    end
  end

  def message(msg : String, severity : LogLevel)
    if @log_levels.includes?(LogLevel::All) || @log_levels.includes?(severity)
      write_message(msg)
    end
    @next.try(&.message(msg, severity))
  end

  abstract def write_message(msg : String)
end

class ConsoleLogger < Logger
  def write_message(msg : String)
    puts "Writing to console: #{msg}"
  end
end

class EmailLogger < Logger
  def write_message(msg : String)
    puts "Sending via email: #{msg}"
  end
end

class FileLogger < Logger
  def write_message(msg : String)
    puts "Writing to Log File: #{msg}"
  end
end

# Program
# Build the chain of responsibility
logger = ConsoleLogger.new(LogLevel::All)
logger1 = logger.next = EmailLogger.new(LogLevel::FunctionalMessage, LogLevel::FunctionalError)
logger2 = logger1.next = FileLogger.new(LogLevel::Warning, LogLevel::Error)

# Handled by ConsoleLogger since the console has a loglevel of all
logger.message("Entering function ProcessOrder().", LogLevel::Debug)
logger.message("Order record retrieved.", LogLevel::Info)

# Handled by ConsoleLogger and FileLogger since filelogger implements Warning & Error
logger.message("Customer Address details missing in Branch DataBase.", LogLevel::Warning)
logger.message("Customer Address details missing in Organization DataBase.", LogLevel::Error)

# Handled by ConsoleLogger and EmailLogger as it implements functional error
logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel::FunctionalError)

# Handled by ConsoleLogger and EmailLogger
logger.message("Order Dispatched.", LogLevel::FunctionalMessage)

Выход

Writing to console: Entering function ProcessOrder().
Writing to console: Order record retrieved.
Writing to console: Customer Address details missing in Branch DataBase.
Writing to Log File: Customer Address details missing in Branch DataBase.
Writing to console: Customer Address details missing in Organization DataBase.
Writing to Log File: Customer Address details missing in Organization DataBase.
Writing to console: Unable to Process Order ORD1 Dated D1 For Customer C1.
Sending via email: Unable to Process Order ORD1 Dated D1 For Customer C1.
Writing to console: Order Dispatched.
Sending via email: Order Dispatched.

Пример Python 2

"""
Chain of responsibility pattern example.
"""
from abc import ABCMeta, abstractmethod
from enum import Enum, auto


class LogLevel(Enum):
    """ Log Levels Enum."""
    NONE = auto()
    INFO = auto()
    DEBUG = auto()
    WARNING = auto()
    ERROR = auto()
    FUNCTIONAL_MESSAGE = auto()
    FUNCTIONAL_ERROR = auto()
    ALL = auto()


class Logger:
    """Abstract handler in chain of responsibility pattern."""
    __metaclass__ = ABCMeta

    next = None

    def __init__(self, levels) -> None:
        """Initialize new logger.

        Arguments:
            levels (list[str]): List of log levels.
        """
        self.log_levels = []

        for level in levels:
            self.log_levels.append(level)

    def set_next(self, next_logger: Logger):
        """Set next responsible logger in the chain.

        Arguments:
            next_logger (Logger): Next responsible logger.
        Returns: Logger: Next responsible logger.
        """
        self.next = next_logger
        return self.next

    def message(self, msg: str, severity: LogLevel) -> None:
        """Message writer handler.

        Arguments:
            msg (str): Message string.
            severity (LogLevel): Severity of message as log level enum.
        """
        if LogLevel.ALL in self.log_levels or severity in self.log_levels:
            self.write_message(msg)

        if self.next is not None:
            self.next.message(msg, severity)

    @abstractmethod
    def write_message(self, msg: str) -> None:
        """Abstract method to write a message.

        Arguments:
            msg (str): Message string.
        Raises: NotImplementedError
        """
        raise NotImplementedError("You should implement this method.")


class ConsoleLogger(Logger):
    def write_message(self, msg: str) -> None:
        """Overrides parent's abstract method to write to console.

        Arguments:
            msg (str): Message string.
        """
        print("Writing to console:", msg)


class EmailLogger(Logger):
    """Overrides parent's abstract method to send an email.

    Arguments:
        msg (str): Message string.
    """
    def write_message(self, msg: str) -> None:
        print(f"Sending via email: {msg}")


class FileLogger(Logger):
    """Overrides parent's abstract method to write a file.

    Arguments:
        msg (str): Message string.
    """
    def write_message(self, msg: str) -> None:
        print(f"Writing to log file: {msg}")


def main():
    """Building the chain of responsibility."""
    logger = ConsoleLogger([LogLevel.ALL])
    email_logger = logger.set_next(
        EmailLogger([LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR])
    )
    # As we don't need to use file logger instance anywhere later
    # We will not set any value for it.
    email_logger.set_next(
        FileLogger([LogLevel.WARNING, LogLevel.ERROR])
    )

    # ConsoleLogger will handle this part of code since the message
    # has a log level of all
    logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
    logger.message("Order record retrieved.", LogLevel.INFO)

    # ConsoleLogger and FileLogger will handle this part since file logger
    # implements WARNING and ERROR
    logger.message(
        "Customer Address details missing in Branch DataBase.",
        LogLevel.WARNING
    )
    logger.message(
        "Customer Address details missing in Organization DataBase.",
        LogLevel.ERROR
    )

    # ConsoleLogger and EmailLogger will handle this part as they implement
    # functional error
    logger.message(
        "Unable to Process Order ORD1 Dated D1 for customer C1.",
        LogLevel.FUNCTIONAL_ERROR
    )
    logger.message("OrderDispatched.", LogLevel.FUNCTIONAL_MESSAGE)


if __name__ == "__main__":
    main()

Пример PHP

<?php

abstract class Logger
{

    /**
     * Bitmask flags for severity.
     */
    public const NONE = 0;
    public const INFO = 0b000001;
    public const DEBUG = 0b000010;
    public const WARNING = 0b000100;
    public const ERROR = 0b001000;
    public const FUNCTIONAL_MESSAGE = 0b010000;
    public const FUNCTIONAL_ERROR = 0b100000;
    public const ALL = 0b111111;

    /** @var int A bitmask flag from this class. */
    protected int $logMask;

    /** @var \Logger|null An optional next logger to handle the message */
    protected ?Logger $next = null;

    /**
     * Logger constructor.
     *
     * @param int $mask
     *   A bitmask flag from this class.
     */
    public function __construct(int $mask)
    {
        $this->logMask = $mask;
    }

    /**
     * Set next responsible logger in the chain.
     *
     * @param \Logger $nextLogger
     *   Next responsible logger.
     *
     * @return \Logger
     *    Logger: Next responsible logger.
     */
    public function setNext(Logger $nextLogger): Logger
    {
        $this->next = $nextLogger;

        return $nextLogger;
    }

    /**
     * Message writer handler.
     *
     * @param string $msg
     *   Message string.
     * @param int $severity
     *   Severity of message as a bitmask flag from this class.
     *
     * @return $this
     */
    public function message(string $msg, int $severity): Logger
    {
        if ($severity & $this->logMask) {
            $this->writeMessage($msg);
        }
        if ($this->next !== null) {
            $this->next->message($msg, $severity);
        }

        return $this;
    }

    /**
     * Abstract method to write a message
     *
     * @param string $msg
     *   Message string.
     */
    abstract protected function writeMessage(string $msg): void;

}

class ConsoleLogger extends Logger
{

    protected function writeMessage(string $msg): void
    {
        echo "Writing to console: $msg\n";
    }

}

class EmailLogger extends Logger
{

    protected function writeMessage(string $msg): void
    {
        echo "Sending via email: $msg\n";
    }

}

class FileLogger extends Logger
{

    protected function writeMessage(string $msg): void
    {
        echo "Writing to a log file: $msg\n";
    }

}

$logger = new ConsoleLogger(Logger::ALL);
$logger
    ->setNext(new EmailLogger(Logger::FUNCTIONAL_MESSAGE | Logger::FUNCTIONAL_ERROR))
    ->setNext(new FileLogger(Logger::WARNING | Logger::ERROR));

$logger
    // Handled by ConsoleLogger since the console has a loglevel of all
    ->message("Entering function ProcessOrder().", Logger::DEBUG)
    ->message("Order record retrieved.", Logger::INFO)
    // Handled by ConsoleLogger and FileLogger since filelogger implements Warning & Error
    ->message("Customer Address details missing in Branch DataBase.", Logger::WARNING)
    ->message("Customer Address details missing in Organization DataBase.", Logger::ERROR)
    // Handled by ConsoleLogger and EmailLogger as it implements functional error
    ->message("Unable to Process Order ORD1 Dated D1 For Customer C1.", Logger::FUNCTIONAL_ERROR)
    // Handled by ConsoleLogger and EmailLogger
    ->message("Order Dispatched.", Logger::FUNCTIONAL_MESSAGE);

/* Output
Writing to console: Entering function ProcessOrder().
Writing to console: Order record retrieved.
Writing to console: Customer Address details missing in Branch DataBase.
Writing to a log file: Customer Address details missing in Branch DataBase.
Writing to console: Customer Address details missing in Organization DataBase.
Writing to a log file: Customer Address details missing in Organization DataBase.
Writing to console: Unable to Process Order ORD1 Dated D1 For Customer C1.
Sending via email: Unable to Process Order ORD1 Dated D1 For Customer C1.
Writing to console: Order Dispatched.
Sending via email: Order Dispatched.
*/

Реализации

Какао и прикосновение какао

В какао и какао Сенсорные рамки, используемые для OS X и IOS приложений , соответственно, активно использовать шаблон приемо-ответственности за обработку событий. Объекты, которые участвуют в цепочке, называются объектами- респондентами , унаследованными от класса NSResponder(OS X) / UIResponder(iOS). Все объекты представления ( NSView/ UIView), объекты контроллера представления ( NSViewController/ UIViewController), объекты окна ( NSWindow/ UIWindow) и объект приложения ( NSApplication/ UIApplication) являются объектами-респондентами.

Обычно, когда представление получает событие, которое оно не может обработать, оно отправляет его своему супервизору, пока оно не достигнет контроллера представления или объекта окна. Если окно не может обработать событие, оно отправляется объекту приложения, который является последним объектом в цепочке. Например:

  • В OS X перемещение текстурированного окна с помощью мыши можно выполнять из любого места (не только из строки заголовка), если только в этом месте нет представления, которое обрабатывает события перетаскивания, например элементы управления ползунком. Если такого представления (или супервизора) нет, события перетаскивания отправляются вверх по цепочке в окно, которое обрабатывает событие перетаскивания.
  • В iOS обычно обрабатывают события представления в контроллере представления, который управляет иерархией представления, вместо того, чтобы создавать подклассы самого представления. Поскольку контроллер представления находится в цепочке респондента после всех своих управляемых подпредставлений, он может перехватывать любые события представления и обрабатывать их.

Смотрите также

использованная литература