Chain-of-responsibility pattern: Difference between revisions

Content deleted Content added
Fix compilation error in C++ code
 
(30 intermediate revisions by 19 users not shown)
Line 1:
{{Short description|Programming pattern}}
In [[object-oriented design]], the '''chain-of-responsibility pattern''' is a [[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=Archived copy |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. Thus, the chain of responsibility is an object oriented version of the <code>if ... else if ... else if ....... else ... endif</code>{{Dubious |Talk ifelseifelseifelseendif|date=August 2020}} idiom, with the benefit that the condition&ndash;action blocks can be dynamically rearranged and reconfigured at runtime.
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.
 
In a variation of the standard chain-of-responsibility model, some handlers may act as [[dynamic dispatch|dispatcher]]s, capable of sending commands out in a variety of directions, forming a ''tree of responsibility''. In some cases, this can occur recursively, with processing objects calling higher-up processing objects with commands that attempt to solve some smaller part of the problem; in this case recursion continues until the command is processed, or the entire tree has been explored. An [[XML]] [[Interpreter (computing)|interpreter]] might work in this manner.
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]]''
that describe common solutions to recurring design problems when designing flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.
 
<big>===What problems can the Chain of Responsibility design pattern solve?</big>===
<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|accessdate=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>
 
<big>===What solution does the Chain of Responsibility design pattern describe?</big> ===
 
* Define a chain of receiver objects having the responsibility, depending on run-time conditions, to either handle a request or forward it to the next receiver on the chain (if any).
Line 35 ⟶ 34:
== Structure ==
=== UML class and sequence diagram ===
[[File:w3sDesign Chain of Responsibility Design Pattern UML.jpg|frame|none|A sample UML class and sequence diagram for the Chain of Responsibility design pattern. <ref>{{cite web|title=The Chain of Responsibility design pattern - Structure and Collaboration|url=http://w3sdesign.com/?gr=b01&ugr=struct|website=w3sDesign.com|accessdateaccess-date=2017-08-12}}</ref>]]
 
In the above [[Unified Modeling Language|UML]] [[class diagram]], the <code>Sender</code> class doesn't refer to a particular receiver class directly.
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++">
=== Java example ===
#include <iostream>
Below is an example of this pattern in Java.
#include <memory>
A logger is created using a chain of loggers, each one configured with different log levels.
 
typedef int Topic;
<syntaxhighlight lang="java">
constexpr Topic NO_HELP_TOPIC = -1;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.Consumer;
 
// defines an interface for handling requests.
@FunctionalInterface
class HelpHandler { // Handler
public interface Logger {
public enum LogLevel {:
HelpHandler(HelpHandler* h = nullptr, Topic t = NO_HELP_TOPIC)
INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;
: 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;
};
 
public static LogLevel[] all() {
return values();
}
}
 
class Widget : public HelpHandler {
abstract void message(String msg, LogLevel severity);
public:
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.
default Logger appendNext(Logger nextLogger) {
class Button : public Widget { // ConcreteHandler
return (msg, severity) -> {
public:
message(msg, severity);
Button(std::shared_ptr<Widget> h, Topic t = NO_HELP_TOPIC) : Widget(h.get(), t) {}
nextLogger.message(msg, severity);
virtual void handleHelp() {
};
// if the ConcreteHandler can handle the request, it does so; otherwise it forwards the request to its successor.
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));
}
 
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 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(object):
"""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>