Chain of Responsibility Design Pattern in Scala

1. Definition

The Chain of Responsibility pattern decouples request senders from receivers by allowing more than one object to handle a request. It chains the receiving objects and passes the request along the chain until an object handles it or the end of the chain is reached.

2. Problem Statement

Imagine you're designing a system where multiple objects can process a request, but you don't want to hard-code which object should handle which type of request. Instead, you want a mechanism where each object either processes the request or passes it to the next object in the chain.

3. Solution

Establish a chain of objects. A request enters one end of the chain, and as it progresses through the chain, each object either processes the request or decides to let the next object in the chain handle it.

4. Real-World Use Cases

1. Event handling in GUI libraries: Events can be handled by any component in the component hierarchy, or passed up to a parent component.

2. Middleware in web frameworks: Each piece of middleware can either handle a request-response or pass it to the next middleware.

5. Implementation Steps

1. Define an interface/handler that will determine the methods for processing requests and setting the next link in the chain.

2. Concrete handlers will implement this interface, providing specific implementations for processing and determining when to pass the request up the chain.

3. Client sets up the chain and sends the request.

6. Implementation in Scala Programming

// Handler trait
trait Handler {
  var nextHandler: Option[Handler] = None
  def setNext(handler: Handler): Unit = nextHandler = Some(handler)
  def handleRequest(request: String): Unit
}
// Concrete Handler 1
class FirstHandler extends Handler {
  def handleRequest(request: String): Unit = {
    if (request == "Request1") {
      println("FirstHandler handled Request1")
    } else {
      nextHandler.foreach(_.handleRequest(request))
    }
  }
}
// Concrete Handler 2
class SecondHandler extends Handler {
  def handleRequest(request: String): Unit = {
    if (request == "Request2") {
      println("SecondHandler handled Request2")
    } else {
      nextHandler.foreach(_.handleRequest(request))
    }
  }
}
// Client
object ChainClient extends App {
  val firstHandler = new FirstHandler
  val secondHandler = new SecondHandler
  firstHandler.setNext(secondHandler)
  firstHandler.handleRequest("Request1")
  firstHandler.handleRequest("Request2")
}

Output:

FirstHandler handled Request1
SecondHandler handled Request2

Explanation:

1. A Handler trait is defined that allows for setting the next handler in the chain and has an abstract method for handling the request.

2. FirstHandler and SecondHandler are concrete implementations of the Handler trait. They check if they can handle the request and if not, they pass it to the next handler in the chain.

3. The client sets up the chain and then sends requests.

7. When to use?

Use the Chain of Responsibility pattern when:

1. More than one object may handle a request, and the handler isn't known a priori, it should be determined automatically.

2. You want to issue a request to one of several objects without specifying explicitly which one should handle it.

3. The set of objects that can handle a request should be specified dynamically.


Comments