Chain-of-responsibility pattern: Difference between revisions

Content deleted Content added
Undid revision 1134961940 by 2A02:A315:4240:A80:7D75:5C85:7663:88CC (talk)
Fix compilation error in C++ code
 
(14 intermediate revisions by 8 users not shown)
Line 1:
{{Short description|Programming pattern}}
In [[object-oriented design]], the '''chain-of-responsibility pattern''' is a [[Behavioral pattern|behavioral]] [[design pattern (computer science)|design pattern]] consisting of a source of [[Command pattern|command objects]] and a series of '''processing objects'''.<ref>{{Cite web |url=http://www.blackwasp.co.uk/ChainOfResponsibility.aspx |title=Chain of Responsibility Design Pattern |access-date=2013-11-08 |archive-url=https://web.archive.org/web/20180227070352/http://www.blackwasp.co.uk/ChainOfResponsibility.aspx |archive-date=2018-02-27 |url-status=dead }}</ref> Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.
 
Line 8 ⟶ 9:
 
==Overview==
The Chain of Responsibility<ref name="GoF">{{cite book|author=Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides|title=Design Patterns: Elements of Reusable Object-Oriented Software|year=1994|publisher=Addison Wesley|isbn=0-201-63361-2|pages=[https://archive.org/details/designpatternsel00gamm/page/223 223ff]|url-access=registration|url=https://archive.org/details/designpatternsel00gamm/page/223}}</ref>
The Chain of Responsibility
<ref name="GoF">{{cite book|author=Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides|title=Design Patterns: Elements of Reusable Object-Oriented Software|year=1994|publisher=Addison Wesley|isbn=0-201-63361-2|pages=[https://archive.org/details/designpatternsel00gamm/page/223 223ff]|url-access=registration|url=https://archive.org/details/designpatternsel00gamm/page/223}}</ref>
design pattern is one of the twenty-three well-known
''[[Design Patterns|GoF design patterns]]''
Line 15:
 
===What problems can the Chain of Responsibility design pattern solve?===
<ref>{{cite web|title=The Chain of Responsibility design pattern - Problem, Solution, and Applicability|url=http://w3sdesign.com/?gr=b01&ugr=proble|website=w3sDesign.com|access-date=2017-08-12}}</ref>
* Coupling the sender of a request to its receiver should be avoided.
* It should be possible that more than one receiver can handle a request.
 
Implementing a request directly within the class that sends the request is inflexible
because it couples the class to a particular receiver and makes it impossible to support multiple receivers.<ref>{{cite web|title=The Chain of Responsibility design pattern - Problem, Solution, and Applicability|url=http://w3sdesign.com/?gr=b01&ugr=proble|website=w3sDesign.com|access-date=2017-08-12}}</ref>
 
===What solution does the Chain of Responsibility design pattern describe?===
Line 48 ⟶ 47:
<!-- Wikipedia is not a list of examples. Do not add examples from your favorite programming language here; this page exists to explain the design pattern, not to show how it interacts with subtleties of every language under the sun. Feel free to add examples here: http://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Chain_of_responsibility -->
 
This C++11 implementation is based on the pre C++98 implementation in the book.<ref>{{cite book |author=Erich Gamma |title=Design Patterns: Elements of Reusable Object-Oriented Software |publisher=Addison Wesley |year=1994 |isbn=0-201-63361-2 |pages=189 ff.}}</ref>
{{wikibooks|Computer Science Design Patterns|Chain of responsibility|Chain-of-responsibility implementations in various languages}}
<syntaxhighlight lang="c++">
#include <iostream>
#include <memory>
 
typedef int Topic;
=== Java example ===
constexpr Topic NO_HELP_TOPIC = -1;
Below is an example of this pattern in Java.
A logger is created using a chain of loggers, each one configured with different log levels.
 
// defines an interface for handling requests.
<syntaxhighlight lang="java" line="1">
class HelpHandler { // Handler
import java.util.Arrays;
public:
import java.util.EnumSet;
HelpHandler(HelpHandler* h = nullptr, Topic t = NO_HELP_TOPIC)
import java.util.function.Consumer;
: successor(h), topic(t) {}
virtual bool hasHelp() {
return topic != NO_HELP_TOPIC;
}
virtual void setHandler(HelpHandler*, Topic) {}
virtual void handleHelp() {
std::cout << "HelpHandler::handleHelp\n";
// (optional) implements the successor link.
if (successor != nullptr) {
successor->handleHelp();
}
}
virtual ~HelpHandler() = default;
HelpHandler(const HelpHandler&) = delete; // rule of three
HelpHandler& operator=(const HelpHandler&) = delete;
private:
HelpHandler* successor;
Topic topic;
};
 
@FunctionalInterface
public interface Logger {
public enum LogLevel {
INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;
 
class Widget : public static LogLevel[] all()HelpHandler {
public:
return values();
Widget(const Widget&) = delete; // rule of three
}
Widget& operator=(const Widget&) = delete;
}
protected:
Widget(Widget* w, Topic t = NO_HELP_TOPIC)
: HelpHandler(w, t), parent(nullptr) {
parent = w;
}
private:
Widget* parent;
};
 
// handles requests it is responsible for.
abstract void message(String msg, LogLevel severity);
class Button : public Widget { // ConcreteHandler
 
public:
default Logger appendNext(Logger nextLogger) {
Button(std::shared_ptr<Widget> h, Topic t = NO_HELP_TOPIC) : Widget(h.get(), t) {}
return (msg, severity) -> {
virtual void handleHelp() {
message(msg, severity);
// if the ConcreteHandler can handle the request, it does so; otherwise it forwards the request to its successor.
nextLogger.message(msg, severity);
std::cout << "Button::handleHelp\n";
};
if (hasHelp()) {
// handles requests it is responsible for.
} else {
// can access its successor.
HelpHandler::handleHelp();
}
}
};
 
class Dialog : public Widget { // ConcreteHandler
static Logger writeLogger(LogLevel[] levels, Consumer<String> stringConsumer) {
public:
EnumSet<LogLevel> set = EnumSet.copyOf(Arrays.asList(levels));
Dialog(std::shared_ptr<HelpHandler> h, Topic t = NO_HELP_TOPIC) : Widget(nullptr) {
return (msg, severity) -> {
if setHandler(seth.containsget(severity), t) {;
}
stringConsumer.accept(msg);
virtual void handleHelp() {
}
std::cout << "Dialog::handleHelp\n";
};
// Widget operations that Dialog overrides...
if(hasHelp()) {
// offer help on the dialog
} else {
HelpHandler::handleHelp();
}
}
};
 
class Application : public HelpHandler {
static Logger consoleLogger(LogLevel... levels) {
public:
return writeLogger(levels, msg -> System.err.println("Writing to console: " + msg));
Application(Topic t) : HelpHandler(nullptr, t) {}
}
virtual void handleHelp() {
std::cout << "Application::handleHelp\n";
// show a list of help topics
}
};
 
int main() {
static Logger emailLogger(LogLevel... levels) {
constexpr Topic PRINT_TOPIC = 1;
return writeLogger(levels, msg -> System.err.println("Sending via email: " + msg));
constexpr Topic PAPER_ORIENTATION_TOPIC = 2;
}
constexpr Topic APPLICATION_TOPIC = 3;
// The smart pointers prevent memory leaks.
std::shared_ptr<Application> application = std::make_shared<Application>(APPLICATION_TOPIC);
std::shared_ptr<Dialog> dialog = std::make_shared<Dialog>(application, PRINT_TOPIC);
std::shared_ptr<Button> button = std::make_shared<Button>(dialog, PAPER_ORIENTATION_TOPIC);
 
button->handleHelp();
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);
}
}
</syntaxhighlight>
 
=== C# example ===
This C# examples uses the logger application to select different sources based on the log level;
 
<syntaxhighlight lang="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.
*/
</syntaxhighlight>
 
=== Crystal example ===
<syntaxhighlight lang="ruby">
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)
</syntaxhighlight>
 
Output
<pre>
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.
</pre>
 
=== Python 3 example ===
<syntaxhighlight lang="python">
"""
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()
</syntaxhighlight>
 
=== PHP example ===
<syntaxhighlight lang="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.
*/
</syntaxhighlight>