Chain-of-responsibility pattern: Difference between revisions
m →Java example: reverted method name to match other examples |
Fix compilation error in C++ code |
||
(36 intermediate revisions by 24 users not shown) | |||
Line 1: | 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>http://www.blackwasp.co.uk/ChainOfResponsibility.aspx</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> idiom, with the benefit that the condition–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. |
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 5: | Line 6: | ||
This pattern promotes the idea of [[loose coupling]]. |
This pattern promotes the idea of [[loose coupling]]. |
||
The chain-of-responsibility pattern is structurally nearly identical to the [[decorator pattern]], the difference being that for the decorator, all classes handle the request, while for the chain of responsibility, exactly one of the classes in the chain handles the request. This is a strict definition of the Responsibility concept in the [[Design Patterns|GoF]] book. However, many implementations (such as loggers below, or UI event handling, or servlet filters in Java, etc) allow several elements in the chain to take responsibility. |
The chain-of-responsibility pattern is structurally nearly identical to the [[decorator pattern]], the difference being that for the decorator, all classes handle the request, while for the chain of responsibility, exactly one of the classes in the chain handles the request. This is a strict definition of the Responsibility concept in the [[Design Patterns|GoF]] book. However, many implementations (such as loggers below, or UI event handling, or servlet filters in Java, etc.) allow several elements in the chain to take responsibility. |
||
==Overview== |
==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 pattern is one of the twenty-three well-known |
||
''[[Design Patterns|GoF design patterns]]'' |
''[[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. |
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. |
||
===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|accessdate=2017-08-12}}</ref> |
|||
* Coupling the sender of a request to its receiver should be avoided. |
* 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. |
* 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 |
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. |
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?=== |
|||
* 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). |
* 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). |
||
This enables to send a request to a chain of receivers |
This enables us to send a request to a chain of receivers |
||
without having to know which one handles the request. |
without having to know which one handles the request. |
||
The request gets passed along the chain until a receiver handles the request. |
The request gets passed along the chain until a receiver handles the request. |
||
Line 35: | Line 34: | ||
== Structure == |
== Structure == |
||
=== UML class and sequence diagram === |
=== 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. |
[[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|access-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. |
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: | Line 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 --> |
<!-- 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; |
|||
<source 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: |
|||
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) -> { |
|||
setHandler(h.get(), 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_Error |
|||
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> |
|||
</source> |
|||
=== C# example === |
|||
This C# examples uses the logger application to select different sources based on the log level; |
|||
<source 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. |
|||
*/ |
|||
</source> |
|||
=== Crystal example === |
|||
<source 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) |
|||
</source> |
|||
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 === |
|||
<source 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() |
|||
</source> |
|||
== Implementations == |
== Implementations == |
Latest revision as of 11:31, 1 June 2024
In object-oriented design, the chain-of-responsibility pattern is a behavioral design pattern consisting of a source of command objects and a series of processing objects.[1] 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 dispatchers, 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 might work in this manner.
This pattern promotes the idea of loose coupling.
The chain-of-responsibility pattern is structurally nearly identical to the decorator pattern, the difference being that for the decorator, all classes handle the request, while for the chain of responsibility, exactly one of the classes in the chain handles the request. This is a strict definition of the Responsibility concept in the GoF book. However, many implementations (such as loggers below, or UI event handling, or servlet filters in Java, etc.) allow several elements in the chain to take responsibility.
Overview
[edit]The Chain of Responsibility[2] design pattern is one of the twenty-three well-known 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.
What problems can the Chain of Responsibility design pattern solve?
[edit]- 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.[3]
What solution does the Chain of Responsibility design pattern describe?
[edit]- 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).
This enables us to send a request to a chain of receivers without having to know which one handles the request. The request gets passed along the chain until a receiver handles the request. The sender of a request is no longer coupled to a particular receiver.
See also the UML class and sequence diagram below.
Structure
[edit]UML class and sequence diagram
[edit]In the above UML class diagram, the Sender
class doesn't refer to a particular receiver class directly.
Instead, Sender
refers to the Handler
interface for handling a request (handler.handleRequest()
), which makes the Sender
independent of which receiver handles the request.
The Receiver1
, Receiver2
, and Receiver3
classes implement the Handler
interface by either handling or forwarding a request (depending on run-time conditions).
The UML sequence diagram
shows the run-time interactions: In this example, the Sender
object calls handleRequest()
on the receiver1
object (of type Handler
).
The receiver1
forwards the request to receiver2
, which in turn forwards the request to receiver3
, which handles (performs) the request.
Example
[edit]This C++11 implementation is based on the pre C++98 implementation in the book.[5]
#include <iostream>
#include <memory>
typedef int Topic;
constexpr Topic NO_HELP_TOPIC = -1;
// defines an interface for handling requests.
class HelpHandler { // Handler
public:
HelpHandler(HelpHandler* h = nullptr, Topic t = NO_HELP_TOPIC)
: 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;
};
class Widget : public HelpHandler {
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.
class Button : public Widget { // ConcreteHandler
public:
Button(std::shared_ptr<Widget> h, Topic t = NO_HELP_TOPIC) : Widget(h.get(), t) {}
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
public:
Dialog(std::shared_ptr<HelpHandler> h, Topic t = NO_HELP_TOPIC) : Widget(nullptr) {
setHandler(h.get(), t);
}
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 {
public:
Application(Topic t) : HelpHandler(nullptr, t) {}
virtual void handleHelp() {
std::cout << "Application::handleHelp\n";
// show a list of help topics
}
};
int main() {
constexpr Topic PRINT_TOPIC = 1;
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();
}
Implementations
[edit]Cocoa and Cocoa Touch
[edit]The Cocoa and Cocoa Touch frameworks, used for OS X and iOS applications respectively, actively use the chain-of-responsibility pattern for handling events. Objects that participate in the chain are called responder objects, inheriting from the NSResponder
(OS X)/UIResponder
(iOS) class. All view objects (NSView
/UIView
), view controller objects (NSViewController
/UIViewController
), window objects (NSWindow
/UIWindow
), and the application object (NSApplication
/UIApplication
) are responder objects.
Typically, when a view receives an event which it can't handle, it dispatches it to its superview until it reaches the view controller or window object. If the window can't handle the event, the event is dispatched to the application object, which is the last object in the chain. For example:
- On OS X, moving a textured window with the mouse can be done from any location (not just the title bar), unless on that location there's a view which handles dragging events, like slider controls. If no such view (or superview) is there, dragging events are sent up the chain to the window which does handle the dragging event.
- On iOS, it's typical to handle view events in the view controller which manages the view hierarchy, instead of subclassing the view itself. Since a view controller lies in the responder chain after all of its managed subviews, it can intercept any view events and handle them.
See also
[edit]References
[edit]- ^ "Chain of Responsibility Design Pattern". Archived from the original on 2018-02-27. Retrieved 2013-11-08.
- ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 223ff. ISBN 0-201-63361-2.
{{cite book}}
: CS1 maint: multiple names: authors list (link) - ^ "The Chain of Responsibility design pattern - Problem, Solution, and Applicability". w3sDesign.com. Retrieved 2017-08-12.
- ^ "The Chain of Responsibility design pattern - Structure and Collaboration". w3sDesign.com. Retrieved 2017-08-12.
- ^ Erich Gamma (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 189 ff. ISBN 0-201-63361-2.