Design Pattern : Chain of Responsibility

Design Pattern : Chain of Responsibility

Design Patterns: Elements of Reusable Object-Oriented Software

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

What is Chain of Responsibility?

** Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.**

The Chain of Responsibility pattern is easy to understand and apply. In applications there is always a client that initiates a request and an application object that handles it.

What the Chain of Responsibility pattern states is – decouple the client who sends the request to the object that handles it. The solution is a list of handler objects, also known as responding objects each capable to deal with a specific nature of request. If one handler object can’t handle a request, it passes it to the next object in the chain. At the end of the chain, there will be one or more generic handler objects implementing default behavior for the request.

You can relate the Chain of Responsibility pattern with a **Customer Service **technical help desk that you call up with a technical query/help for some product or service (think yourself as a request object). A technical help desk executive tries to resolve it (Think in terms of objects – the first object in the chain). If they can’t resolve it – maybe for some billing related issues, it moves to a billing help desk executive (the second object). If the billing help desk can’t resolve either, your request goes to the general help desk (the third object), and so on – until someone handles your request.

chain.png

A classic example of the Dependency Inversion Principle that states: Modules should be independent. They should depend on abstractions. and abstractions should not depend on details. Details should depend on abstractions. Our object chain depends on the abstraction (AbstractSupportCenter)

On the context of the support system example, the participants of the Chain of Responsibility pattern are:

  • Handler (AbstractSupportHandler) Abstract base class acting as the interface to handle request. Optionally, but most often the Handler implements the chain links.

  • **ConcreteHandler (TechnicalSupportCenter, GeneralSupportCenter, and AdvanceSupportCenter) **Handles request, else passes it to the next successor of the handler chain.

  • Client(SupportCenterClient): Initiates a request that one of the chain of handlers (a ConcreteHandler) handles.

Let's get into the coding part

abstract class AbstractSupportCenter(private val _level: Int) {
    object Constants {
        /*Constants for Priority*/
        val GENERAL: Int = 1
        val TECHNICAL: Int = 2
        val ADVANCE: Int = 3
    }

    private var nextHandler: AbstractSupportCenter? = null

    open fun nextHandler(handler: AbstractSupportCenter) {
        this.nextHandler = handler
    }

    open fun receiveRequest(level: Int, message: String) {
        when (this._level <= level) {
            true -> handleRequest(message)
            else -> nextHandler?.receiveRequest(level, message)
                ?: kotlin.run { println("Next handler not found to handle this request.") }
        }
    }

    protected abstract fun handleRequest(message: String)
}

We have an abstract class called AbstractSupportCenter, we have two public APIs nextHandler(handler: AbstractSupportCenter) and fun receiveRequest(level: Int, message: String) and one abstract API handleRequest(message: String)

nextHandler(handler: AbstractSupportCenter) API to store the next handler, which handler [AbstractSupportCenter] will take care of the client request.

receiveRequest(level: Int, message: String) takes level : Int to determine request priority and message : String from the client.

handleRequest(message: String) must be implemented by the Concrete Classes.

Let's implement concrete classes.

class TechnicalSupportCenter(level: Int) : AbstractSupportCenter(level) {
    override fun handleRequest(message: String) {
        println("TechnicalSupportHandler: Processing request $message")
    }
}

class GeneralSupportCenter(level: Int) : AbstractSupportCenter(level) {
    override fun handleRequest(message: String) {
        println("GeneralSupportCenter: Processing request $message")
    }
}

class AdvanceSupportCenter(level: Int) : AbstractSupportCenter(level) {
    override fun handleRequest(message: String) {
        println("AdvanceSupportCenter: Processing request $message")
    }
}

Now, let's create our SupportCenterClient access point for the Client to submit a new request to the Support System.

object SupportCenterClient {
    val handlerChain: AbstractSupportCenter
        get() {
            val general = GeneralSupportCenter(AbstractSupportCenter.Constants.GENERAL)
            val technical = TechnicalSupportCenter(AbstractSupportCenter.Constants.TECHNICAL)
            val advance = AdvanceSupportCenter(AbstractSupportCenter.Constants.ADVANCE)
            /*Assign the next handler for the [GeneralSupportCenter]*/
            general.nextHandler(technical)
            /*Assign the next handler for the [TechnicalSupportCenter]*/
            technical.nextHandler(advance)
            return general
        }
}

In SupportCenterClient, we are initializing all the concrete classes and assigning the next handler of the Chain to process the request for further.

Let's test

class ChainOfResponsibilityExample {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SupportCenterClient.handlerChain.apply {
                println(".....")
                receiveRequest(AbstractSupportCenter.Constants.GENERAL, "I'm having general issue.")
                println(".....")
                receiveRequest(AbstractSupportCenter.Constants.TECHNICAL, "I'm having technical issue.")
                println(".....")
                receiveRequest(AbstractSupportCenter.Constants.ADVANCE, "I'm having advance issue.")
                println(".....")
            }
        }
    }
}

The result would be:

.....
GeneralSupportCenter: Processing request I'm having general issue.
.....
GeneralSupportCenter: Processing request I'm having technical issue.
TechnicalSupportHandler: Processing request I'm having technical issue.
.....
GeneralSupportCenter: Processing request I'm having advance issue.
TechnicalSupportHandler: Processing request I'm having advance issue.
AdvanceSupportCenter: Processing request I'm having advance issue.
.....

Full implementation in Kotlin