Chain of Responsibility Design Pattern

The Chain of Responsibility is a behavioral design pattern that allows an object to pass a request along a chain of potential handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. This pattern avoids coupling the sender of a request to its receiver, giving more than one object a chance to handle the request.

Concept:

  • Handler: Defines an interface for handling requests and, optionally, implements the successor link.
  • ConcreteHandler: Handles requests it is responsible for; otherwise, it forwards the request to its successor.
  • Client: Initiates requests to a chain of handlers.
  • Request: Represents the request to be handled.

Example:

Let's consider a scenario where we have a system for processing purchase requests, and we want to implement a Chain of Responsibility to handle different levels of approval.

Handler Interface:

# Handler Interface
class Approver
  attr_reader :successor

  def initialize(successor = nil)
    @successor = successor
  end

  def process_request(request)
    raise NotImplementedError, 'Subclasses must implement the process_request method'
  end
end

Concrete Handlers:

# Concrete Handler: Manager
class Manager < Approver
  def process_request(request)
    if request.amount <= 1000
      puts "Manager approves the request with amount #{request.amount}"
    elsif @successor
      @successor.process_request(request)
    else
      puts 'Request cannot be approved.'
    end
  end
end

# Concrete Handler: Director
class Director < Approver
  def process_request(request)
    if request.amount <= 5000
      puts "Director approves the request with amount #{request.amount}"
    elsif @successor
      @successor.process_request(request)
    else
      puts 'Request cannot be approved.'
    end
  end
end

# Concrete Handler: VicePresident
class VicePresident < Approver
  def process_request(request)
    if request.amount <= 10000
      puts "Vice President approves the request with amount #{request.amount}"
    elsif @successor
      @successor.process_request(request)
    else
      puts 'Request cannot be approved.'
    end
  end
end

Request Class:

# Request Class
class PurchaseRequest
  attr_reader :amount

  def initialize(amount)
    @amount = amount
  end
end

Client Code:

# Client code using the Chain of Responsibility Pattern
purchase_request = PurchaseRequest.new(8000)

manager = Manager.new
director = Director.new
vice_president = VicePresident.new

manager.successor = director
director.successor = vice_president

manager.process_request(purchase_request)

In this example:

  • Approver is the handler interface.
  • Manager, Director, and VicePresident are concrete handlers. Each handler decides whether to approve the request or pass it to the next handler in the chain.
  • PurchaseRequest represents the request to be handled.
  • The client code sets up the chain of responsibility and initiates the request through the first handler.

This pattern allows us to create a flexible and decoupled chain of handlers where each handler can either handle the request or pass it to the next handler in the chain. It's particularly useful in scenarios where multiple objects may handle a request, and the client doesn't need to know which object will ultimately process the request.