Design Pattern: Chain of Responsibility Pattern

Summary

Pattern NameChain of Responsibility Pattern
Pattern TypeBehavioral Pattern
ScopeObject
TaglineChain the steps of processing a command
Use cases1. Chain the steps of processing one after another
and ensure choosing the steps on the fly during processing
2. When we want to decouple the sender and receiver of an operation
Related PatternsComposite
Difficulty Leveldifficult
Implementations

Definition

Chain of Responsibility pattern forms a chain of objects that is responsible for handling a command/operation request. As the responsibility is distributed into one or more objects and a single object is not responsible for handling a request, so that ensures the decoupling of the request sender and processor.

The request is sent to the chain of objects without specifying the handler. Which object will handle the request is not predefined, that is decided on-the-fly while processing.

Implementation

Chain of Responsibility pattern has 2 elements involved in the implementation.

  1. Handler Interface: an interface(or abstract class) to ensure the common handler for the request. This defines a function for handling and might define a process for handling the next step.
  2. Concrete Handler Classes: classes that implement the handler interface and are responsible for handling the request.
Chain of Responsibility Structure Diagram

Examples

Example #1: Caching Data

Let’s take the example of caching data.

If the file is of type “CSS” or “JavaScript” we want to cache that in some CDN.

Or if the data is normal data then we want to cached it locally. We want to cache data less than 1024 characters in Redis and larger data in the disk.

General Class for Data [not related to the pattern implementation]

enum DATA_TYPE {
    DATA,
    JAVASCRIPT,
    CSS
}

class Data

    var type: DATA_TYPE
    var data: String
    var key: String

    constructor(typeParam: DATA_TYPE, keyParam: String, dataParam: String) 
       type = typeParam
       key = keyParam
       data = dataParam
    end constructor

    function getType(): DATA_TYPE
        return type
    end function

    function getKey(): String
        return key
    end fuction

    function getData(): String
        return data
    end function

end class

Cache Handler and Concrete Handler Classes

// CacheHandler abstract class

abstract class CacheHandler

   var nextCacheHandler: CacheHandler

    constructor(nextCacheHandlerParam: CacheHandler)
       nextCacheHandler = nextCacheHandlerParam
    end constructor

    public abstract void handleRequest(Data data)

end class


// CdnCacheHandler

class CdnCacheHandler extends CacheHandler

    constructor(nextCacheHandlerParam: CacheHandler) 
        super(nextCacheHandlerParam)
    end constructor

    function handleRequest(Data data)
        if (data.getType() == DATA_TYPE.CSS || data.getType() == DATA_TYPE.JAVASCRIPT) {
            // Write code to send the data file to some CDN

            print("Caching file '" + data.getKey() + "' in CDN")
        } else if (nextCacheHandler != null) {
            nextCacheHandler.handleRequest(data)
        }
    end function

end class


// RedisCacheHandler

class RedisCacheHandler extends CacheHandler

    constructor(nextCacheHandlerParam: CacheHandler) 
        super(nextCacheHandlerParam)
    end constructor

    function handleRequest(Data data)
        if (data.getType() == DATA_TYPE.DATA && data.getData().length() <= 1024) {
            // Write code to cache data in Redis

            print("Caching data '" + data.getKey() + "' in Redis")
        } else if (nextCacheHandler != null) {
            nextCacheHandler.handleRequest(data)
        }
    end function

end class


// DiskCacheHandler

class DiskCacheHandler extends CacheHandler

    constructor(nextCacheHandlerParam: CacheHandler) 
        super(nextCacheHandlerParam)
    end constructor

    
    function handleRequest(Data data) {
        if (data.getType() == DATA_TYPE.DATA && data.getData().length() > 1024) {
            // Write code to cache data in Disk

            print("Caching data '" + data.getKey() + "' in Disk")
        } else if (nextCacheHandler != null) {
            nextCacheHandler.handleRequest(data)
        }
    end function

end class

Demo

var cacheHandler: DiskCacheHandler = new DiskCacheHandler(new RedisCacheHandler(new CdnCacheHandler(null)))

// First request 
var data: Data = new Data(DATA_TYPE.DATA, "key1", "ABC320489un3429rn29urn29r82n9jfdn2")

cacheHandler.handleRequest(data)

// Second request
data = new Data(DATA_TYPE.CSS, "key2", ".some-class{border: 1px solid red; margin: 10px}")

cacheHandler.handleRequest(data)

Output

Caching data 'key1' in Redis
---------------------------
Caching file 'key2' in CDN

Code Implementations

Use the following links to check Singleton pattern implementation in specific programming languages.

Leave a Comment