Chain of Responsibility Pattern in Ruby: Flexible Request Handling

Explore the Chain of Responsibility pattern in Ruby, a powerful behavioral design pattern that enables flexible request handling by passing requests through a chain of handlers.

6.1 Chain of Responsibility Pattern

The Chain of Responsibility pattern is a behavioral design pattern that allows a request to be passed along a chain of handlers. Each handler in the chain has the opportunity to process the request or pass it to the next handler. This pattern is particularly useful in scenarios where multiple handlers can process a request, but the specific handler is determined at runtime.

Intent

The primary intent of the Chain of Responsibility pattern is to decouple the sender of a request from its receivers. By allowing multiple objects to handle the request, the pattern provides flexibility in request processing and enhances the maintainability of the codebase.

Problem Addressed

In many applications, requests need to be processed by different components or services. Hardcoding the logic to determine which component should handle a request can lead to tightly coupled and inflexible code. The Chain of Responsibility pattern addresses this problem by allowing requests to be passed through a chain of handlers, each of which can choose to handle the request or pass it along.

Key Participants

  • Handler: Defines an interface for handling requests and optionally forwarding them to the next handler.
  • ConcreteHandler: Implements the handler interface and processes requests it is responsible for.
  • Client: Initiates the request and passes it to the first handler in the chain.

Applicability

Use the Chain of Responsibility pattern when:

  • You have multiple objects that can handle a request, and the handler is determined at runtime.
  • You want to decouple the sender of a request from its receivers.
  • You want to add or change handlers dynamically without affecting the client code.

Implementing the Pattern in Ruby

Ruby’s dynamic features make it an excellent language for implementing the Chain of Responsibility pattern. Let’s explore how to implement this pattern in Ruby with a practical example.

Example Scenario

Consider a logging system where different log levels (e.g., INFO, DEBUG, ERROR) need to be processed by different handlers. We will implement a chain of log handlers that can process log messages based on their severity.

 1# Define the base Handler class
 2class LogHandler
 3  attr_accessor :next_handler
 4
 5  def initialize
 6    @next_handler = nil
 7  end
 8
 9  def handle_request(log_level, message)
10    if @next_handler
11      @next_handler.handle_request(log_level, message)
12    end
13  end
14end
15
16# Define a ConcreteHandler for INFO level logs
17class InfoLogHandler < LogHandler
18  def handle_request(log_level, message)
19    if log_level == :info
20      puts "INFO: #{message}"
21    else
22      super
23    end
24  end
25end
26
27# Define a ConcreteHandler for DEBUG level logs
28class DebugLogHandler < LogHandler
29  def handle_request(log_level, message)
30    if log_level == :debug
31      puts "DEBUG: #{message}"
32    else
33      super
34    end
35  end
36end
37
38# Define a ConcreteHandler for ERROR level logs
39class ErrorLogHandler < LogHandler
40  def handle_request(log_level, message)
41    if log_level == :error
42      puts "ERROR: #{message}"
43    else
44      super
45    end
46  end
47end
48
49# Client code
50info_handler = InfoLogHandler.new
51debug_handler = DebugLogHandler.new
52error_handler = ErrorLogHandler.new
53
54# Set up the chain of responsibility
55info_handler.next_handler = debug_handler
56debug_handler.next_handler = error_handler
57
58# Test the chain with different log levels
59info_handler.handle_request(:info, "This is an info message.")
60info_handler.handle_request(:debug, "This is a debug message.")
61info_handler.handle_request(:error, "This is an error message.")

Explanation of the Code

  • Handler Class: The LogHandler class defines the interface for handling requests and maintains a reference to the next handler in the chain.
  • Concrete Handlers: The InfoLogHandler, DebugLogHandler, and ErrorLogHandler classes extend LogHandler and implement the logic for handling specific log levels.
  • Chain Setup: The client code sets up the chain by linking handlers together. The info_handler is the first handler in the chain, followed by debug_handler and error_handler.
  • Request Handling: The handle_request method is called on the first handler. If the handler can process the request, it does so; otherwise, it passes the request to the next handler in the chain.

Variations of the Pattern

The Chain of Responsibility pattern can be implemented using different data structures, such as arrays or linked lists, to manage the handlers. In Ruby, arrays can be used to store handlers, allowing for easy addition and removal of handlers.

Using Arrays for Handlers

 1class ArrayLogHandler
 2  def initialize
 3    @handlers = []
 4  end
 5
 6  def add_handler(handler)
 7    @handlers << handler
 8  end
 9
10  def handle_request(log_level, message)
11    @handlers.each do |handler|
12      handler.handle_request(log_level, message)
13    end
14  end
15end
16
17# Client code
18array_handler = ArrayLogHandler.new
19array_handler.add_handler(InfoLogHandler.new)
20array_handler.add_handler(DebugLogHandler.new)
21array_handler.add_handler(ErrorLogHandler.new)
22
23# Test the chain with different log levels
24array_handler.handle_request(:info, "This is an info message.")
25array_handler.handle_request(:debug, "This is a debug message.")
26array_handler.handle_request(:error, "This is an error message.")

Benefits of the Chain of Responsibility Pattern

  • Decoupling: The pattern decouples the sender of a request from its receivers, allowing for more flexible and maintainable code.
  • Flexibility: Handlers can be added, removed, or changed without affecting the client code.
  • Responsibility Sharing: Multiple handlers can process a request, sharing the responsibility for request handling.

Design Considerations

  • Order of Handlers: The order in which handlers are linked can affect the behavior of the chain. Ensure handlers are ordered correctly to achieve the desired processing logic.
  • Termination: Ensure that the chain has a termination condition to prevent infinite loops. This can be achieved by having a default handler that always processes the request.

Ruby Unique Features

Ruby’s dynamic nature allows for flexible implementation of the Chain of Responsibility pattern. Features such as duck typing and open classes make it easy to extend and modify handlers at runtime.

Differences and Similarities

The Chain of Responsibility pattern is often confused with the Decorator pattern. While both patterns involve chaining objects, the Chain of Responsibility pattern focuses on passing requests through a chain, whereas the Decorator pattern focuses on adding behavior to objects.

Visualizing the Chain of Responsibility Pattern

To better understand the flow of requests through the chain, let’s visualize the pattern using a sequence diagram.

    sequenceDiagram
	    participant Client
	    participant InfoHandler
	    participant DebugHandler
	    participant ErrorHandler
	
	    Client->>InfoHandler: handle_request(:info, "message")
	    InfoHandler->>InfoHandler: Process request
	    InfoHandler->>DebugHandler: handle_request(:debug, "message")
	    DebugHandler->>DebugHandler: Process request
	    DebugHandler->>ErrorHandler: handle_request(:error, "message")
	    ErrorHandler->>ErrorHandler: Process request

Try It Yourself

Experiment with the code examples by adding new log levels or modifying the order of handlers in the chain. Try implementing a chain for a different scenario, such as handling different types of user input or processing different types of requests in a web application.

Knowledge Check

  • What is the primary intent of the Chain of Responsibility pattern?
  • How does the Chain of Responsibility pattern enhance flexibility in request handling?
  • What are the key participants in the Chain of Responsibility pattern?
  • How can arrays be used to implement the Chain of Responsibility pattern in Ruby?
  • What are the benefits of using the Chain of Responsibility pattern?

Embrace the Journey

Remember, mastering design patterns is a journey. As you continue to explore and implement different patterns, you’ll gain a deeper understanding of how to build scalable and maintainable applications. Keep experimenting, stay curious, and enjoy the journey!

Quiz: Chain of Responsibility Pattern

Loading quiz…
Revised on Thursday, April 23, 2026