Chain-of-responsibility pattern
In object-oriented design, the chain-of-responsibility pattern is a 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.
Example
The Wikibook Computer Science Design Patterns has a page on the topic of: Chain-of-responsibility implementations in various languages |
Java example
Below is an example of this pattern in Java. In this example we have different roles, each having a fixed purchasing limit and a successor. Every time a user in a role receives a purchase request that exceeds his or her limit, the request is passed to his or her successor.
The PurchasePower abstract class with the abstract method processRequest.
abstract class PurchasePower {
protected static final double BASE = 500;
protected PurchasePower successor;
abstract protected double getAllowable();
abstract protected String getRole();
public void setSuccessor(PurchasePower successor) {
this.successor = successor;
}
public void processRequest(PurchaseRequest request){
if (request.getAmount() < this.getAllowable()) {
System.out.println(this.getRole() + " will approve $" + request.getAmount());
} else if (successor != null) {
successor.processRequest(request);
}
}
}
Four implementations of the abstract class above: Manager, Director, Vice President, President
class ManagerPPower extends PurchasePower {
protected double getAllowable(){
return BASE*10;
}
protected String getRole(){
return "Manager";
}
}
class DirectorPPower extends PurchasePower {
protected double getAllowable(){
return BASE*20;
}
protected String getRole(){
return "Director";
}
}
class VicePresidentPPower extends PurchasePower {
protected double getAllowable(){
return BASE*40;
}
protected String getRole(){
return "Vice President";
}
}
class PresidentPPower extends PurchasePower {
protected double getAllowable(){
return BASE*60;
}
protected String getRole(){
return "President";
}
}
The following code defines the PurchaseRequest class that keeps the request data in this example.
class PurchaseRequest {
private double amount;
private String purpose;
public PurchaseRequest(double amount, String purpose) {
this.amount = amount;
this.purpose = purpose;
}
public double getAmount() {
return amount;
}
public void setAmount(double amt) {
amount = amt;
}
public String getPurpose() {
return purpose;
}
public void setPurpose(String reason) {
purpose = reason;
}
}
In the following usage example, the successors are set as follows: Manager -> Director -> Vice President -> President
class CheckAuthority {
public static void main(String[] args) {
ManagerPPower manager = new ManagerPPower();
DirectorPPower director = new DirectorPPower();
VicePresidentPPower vp = new VicePresidentPPower();
PresidentPPower president = new PresidentPPower();
manager.setSuccessor(director);
director.setSuccessor(vp);
vp.setSuccessor(president);
// Press Ctrl+C to end.
try {
while (true) {
System.out.println("Enter the amount to check who should approve your expenditure.");
System.out.print(">");
double d = Double.parseDouble(new BufferedReader(new InputStreamReader(System.in)).readLine());
manager.processRequest(new PurchaseRequest(d, "General"));
}
} catch(Exception e) {
System.exit(1);
}
}
}
C# example
This C# examples uses the logger application to select different sources based on the log level;
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)
{
next = nextlogger;
return nextlogger;
}
public void Message(string msg, LogLevel severity)
{
if ((severity & logMask) != 0) //True only if all 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, logger1, logger2;
logger = new ConsoleLogger(LogLevel.All);
logger1 = logger.SetNext(new EmailLogger(LogLevel.FunctionalMessage | LogLevel.FunctionalError));
logger2 = logger1.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.
*/
Implementations
Cocoa and Cocoa Touch
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.