GOF:
- Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
- Chain the receiving objects and pass the request along the chain until an object handles it.
image source: https://refactoring.guru/design-patterns/chain-of-responsibility
![]() |
![]() |
![]() |
![]() |
-
- behavioral patterns.
- creates a chain of receiver objects for a request.
- This pattern decouples sender and receiver of a request based on type of request.
- In this pattern, normally each receiver contains reference to another receiver.
- If one object cannot handle the request then it passes the same to the next receiver and so on.
-
-
is a behavioral design pattern consisting of a source of command objects and a series of processing objects.
-
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
- decorator:: all classes handle the request,
- 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.
-
- 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.
- Imagine that you’re working on an online ordering system.
- You want to restrict access to the system so only authenticated users can create orders.
- Also, users who have administrative permissions must have full access to all orders.
After a bit of planning, you realized that these checks must be performed sequentially. The application can attempt to authenticate a user to the system whenever it receives a request that contains the user’s credentials. However, if those credentials aren’t correct and authentication fails, there’s no reason to proceed with any other checks.
-
During the next few months, you implemented several more of those sequential checks.
-
One of your colleagues suggested that it’s unsafe to pass raw data straight to the ordering system. So you added an extra validation step to sanitize the data in a request.
-
Later, somebody noticed that the system is vulnerable to brute force password cracking. To negate this, you promptly added a check that filters repeated failed requests coming from the same IP address.
-
Someone else suggested that you could speed up the system by returning cached results on repeated requests containing the same data. Hence, you added another check which lets the request pass through to the system only if there’s no suitable cached response.
With each new check the code became bigger, messier, and uglier..The bigger the code grew, the messier it became.
-
-
- 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.
Source: https://github.com/scottt2/design-patterns-in-dart/tree/master/chain_of_responsibility
- A logger is created using a chain of loggers,
- each one configured with different log levels.
- normally each receiver contains reference to another receiver.
- If one object cannot handle the request
- then it passes the same to the next receiver and so on.
- Implementation
- We have created an abstract class AbstractLogger with a level of logging.
- Then we have created three types of loggers extending the AbstractLogger.
- Each logger checks the level of message to its level and print accordingly
- otherwise does not print and pass the message to its next logger.
Create an abstract logger class.
enum LogLevel {
None,
Info,
Debug,
Warning,
Error,
FunctionalMessage,
FunctionalError
}
abstract class Logger {
final Set<LogLevel> levels;
Logger? _next;
Logger(this.levels);
bool get universal => levels.containsAll(LogLevel.values);
void set next(Logger nextLogger) => _next = nextLogger;
// void addLevel(LogLevel level) => levels.add(level);
void log(LogLevel level, String msg) {
// will do 2 things first check if this logger contains the specific level
// if true it will write the message and then go to next
// if else it will to next
if (levels.contains(level) || universal) {
write_message(msg);
}
_next?.log(level, msg);
}
void write_message(String msg);
}
Create concrete classes extending the logger.
class ConsoleLogger extends Logger {
ConsoleLogger(Set<LogLevel> levels) : super(levels);
void write_message(String msg) => print("[Console]: $msg");
}
class EmailLogger extends Logger {
EmailLogger(Set<LogLevel> levels) : super(levels);
void write_message(String msg) => print("[Email]: $msg");
}
class FileLogger extends Logger {
FileLogger(Set<LogLevel> levels) : super(levels);
void write_message(String msg) => print("[File]: $msg");
}
- Create different types of loggers.
- Assign them error levels and set next logger in each logger.
- Next logger in each logger represents the part of the chain.
void main() {
Logger logger = ConsoleLogger(Set.from(LogLevel.values));
Logger eLog = EmailLogger(
Set.from([LogLevel.FunctionalMessage, LogLevel.FunctionalError]));
Logger fLog = FileLogger(Set.from([LogLevel.Warning, LogLevel.Error]));
logger.next = eLog;
eLog.next = fLog;
logger.log(LogLevel.Debug, "Some amazingly helpful debug message");
logger.log(LogLevel.Info, "Pretty important information");
logger.log(LogLevel.Warning, "This is a warning!");
logger.log(LogLevel.Error, "AHHHHHHHHH!");
logger.log(LogLevel.FunctionalError, "This is not a show stopper");
logger.log(LogLevel.FunctionalMessage, "This is basically just info");
/*
[Console]: Some amazingly helpful debug message
[Console]: Pretty important information
[Console]: This is a warning!
[File]: This is a warning!
[Console]: AHHHHHHHHH!
[File]: AHHHHHHHHH!
[Console]: This is not a show stopper
[Email]: This is not a show stopper
[Console]: This is basically just info
[Email]: This is basically just info
*/
}
Example: click here
Example: click here
- normally each receiver contains reference to another receiver.
- If one object cannot handle the request
- then it passes the same to the next receiver and so on.
- GOF Intention: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
- Chain the receiving objects and pass the request along the chain until an object handles it.
-
https://github.com/scottt2/design-patterns-in-dart/tree/master/chain_of_responsibility
-
GOF Book
-
https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern#C#_example
-
https://refactoring.guru/design-patterns/chain-of-responsibility
-
https://www.tutorialspoint.com/design_pattern/chain_of_responsibility_pattern.htm