Chain of Responsibility Design Pattern in Kotlin

1. Definition

The Chain of Responsibility Design Pattern decouples the sender from the receiver by allowing more than one object to handle a request. These handlers are organized as a chain, and the request is passed along the chain until an object handles it or it reaches the end of the chain.

2. Problem Statement

Imagine a series of checks or processes that a request needs to pass through. Instead of hardcoding a sequence of calls or creating a tight coupling between objects, you'd prefer a more dynamic and decoupled way to process the request.

3. Solution

Establish a chain of objects, where each object can either handle the request or pass it to the next object in the chain. This way, the sender sends the request to the first object in the chain, and it travels along the chain until it's handled or reaches the end.

4. Real-World Use Cases

1. Event systems where events can be handled by multiple listeners.

2. Middleware in web servers, where requests pass through a series of processing stages.

3. Input validation and processing in software applications.

5. Implementation Steps

1. Define the handler interface or abstract class, which specifies a method to handle requests and set the next handler.

2. Implement concrete handlers, which decide whether to process the request or pass it along.

3. Link these handlers to form a chain.

6. Implementation in Kotlin

// Step 1: Define the handler interface
interface Handler {
    var nextHandler: Handler?
    fun handleRequest(request: String): Boolean
}

// Step 2: Implement concrete handlers
class FirstHandler : Handler {
    override var nextHandler: Handler? = null

    override fun handleRequest(request: String): Boolean {
        if (request == "Request1") {
            println("FirstHandler handled $request")
            return true
        }
        return nextHandler?.handleRequest(request) ?: false
    }
}

class SecondHandler : Handler {
    override var nextHandler: Handler? = null

    override fun handleRequest(request: String): Boolean {
        if (request == "Request2") {
            println("SecondHandler handled $request")
            return true
        }
        return nextHandler?.handleRequest(request) ?: false
    }
}

fun main() {
    val firstHandler = FirstHandler()
    val secondHandler = SecondHandler()

    firstHandler.nextHandler = secondHandler

    listOf("Request1", "Request2", "Request3").forEach { request ->
        val isHandled = firstHandler.handleRequest(request)
        if (!isHandled) {
            println("$request was not handled")
        }
    }
}

Output:

FirstHandler handled Request1
SecondHandler handled Request2
Request3 was not handled

Explanation:

1. We first defined a Handler interface that mandates implementing the handleRequest method and having a nextHandler property.

2. We created two concrete handlers: FirstHandler and SecondHandler. Each handler checks if it can handle the request; if not, it passes the request to its next handler.

3. In the main function, we set up the chain by assigning secondHandler as the next handler for firstHandler. We then test the chain by sending three requests. The first two are handled by the respective handlers, while the third isn't handled by any handler in the chain.

7. When to use?

The Chain of Responsibility pattern is useful when:

1. Multiple objects can handle a request, and the handler isn't known a priori, it's determined dynamically.

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

3. Requests should be handled by multiple objects in a chain or sequence.


Comments