Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Chain of Responsibility Design Pattern in Java: 2 Implementations

DZone's Guide to

Chain of Responsibility Design Pattern in Java: 2 Implementations

Learn about two implementations of the Chain of Responsibility design pattern in Java: one versus multiple request handlers serving the input request.

· Java Zone ·
Free Resource

Java-based (JDBC) data connectivity to SaaS, NoSQL, and Big Data. Download Now.

The Chain of Responsibility (COR) design pattern is used when more than one object handles a request and performs their corresponding responsibilities to complete the whole task.

The pattern can be used to achieve loose coupling in software design, where the request can be passed through a chain of objects or request handler for processing. Based on some criteria in each handler object, it will handle the request or pass it to the next handler. Following is a representation of the COR pattern:

Image title

The COR pattern can be implemented in two ways.

1. One Request Handler Will Serve the Input Request

The request initiator is not aware of the exact handler that will serve its request. Every handler in the chain will have the responsibility to decide if they can serve the request. If any handler decides to forward the request, it should be capable of choosing the next handler and forwarding it. There is a possible scenario in which none of the handlers may serve the request.

This type of COR pattern implementation is present in Java’s exception handling mechanism. We can have multiple catch blocks in a try-catch block code. Here, every catch block is kind of a handler to handle that particular exception.

When an exception occurs in the try block, it sends the exception to the first catch block to process. If the catch block is not able to handle it, it forwards the exception to the next handler in the chain, i.e. the next catch block. If even the last catch block is not able to handle it, then the exception is thrown outside of the chain to the calling program.

2. Multiple Request Handlers Will Serve the Input Request

There is another type of COR pattern implementation in which more than one handler participates in the processing to complete the whole task. In this implementation, each handler performs its part of the task (if required based on some condition) and forwards the request to the next handler. The whole task is completed once the request travels through the complete chain.

Spring’s HandlerInterceptorAdapter is an example of this type of COR pattern. The HandlerInterceptorAdapter defines three methods: preHandle(), postHandle(), and afterCompletion(). Here, the preHandle() follows the COR pattern if we have multiple interceptors for intercepting a request. Then, the request flows through all the preHandle() methods sequentially, and each preHandle() can perform some task and modify the request before it reaches to the controller. Here’s what a simple preHandle() implementation will look like:

@Override

public boolean preHandle(

  HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {

    // your code

    return true;

}

This method is called before handling a request. If it returns true, the Spring framework sends the request further to the next handler method (to the next interceptor’s preHandle). If the method returns false, Spring assumes that request has been handled, and no further processing is required. By this boolean flag, each handler of the chain chooses the next handler. Now, let's consider a simple implementation of COR pattern.

We have a request that needs to collect data from different external third-party data storages like a database, file storage, and cloud environments. We will develop a chain of such data collectors that are responsible for collecting data from different sources. First, we will define the type of RequestHandler.

This is an interface defining the basic blueprint of all the concrete RequestHandlers.

package com.tuturself.cor;

public interface RequestHandler {

void setNextRequestHandler(RequestHandler requestHandler);

        boolean process(RequestData requestData);
}

Now, we will define the three concrete request handlers that are responsible for collecting data from each of the sources (database, file, and cloud). In addition, each handler has a pointer to the next handler object so that control can be passed to the next handler when the current handler finishes.

DBDataHandler.java:

package com.tuturself.cor;

public class DBDataHandler implements RequestHandler {

    private RequestHandler requestHandler;

    @Override
    public void setNextRequestHandler(RequestHandler requestHandler) {
        this.requestHandler = requestHandler;

    }

    @Override
    public boolean process(RequestData requestData) {
        requestData.setMetaDBData("Meta Data from DB is populated");
        return requestHandler == null ? true : requestHandler.process(requestData);
    }
}

FileDataHandler.java:

package com.tuturself.cor;

public class FileDataHandler implements RequestHandler {

    private RequestHandler requestHandler;

    @Override
    public void setNextRequestHandler(RequestHandler requestHandler) {
        this.requestHandler = requestHandler;

    }

    @Override
    public boolean process(RequestData requestData) {
        requestData.setMetaFileData("Meta Data from File is populated");
        return requestHandler == null ? true : requestHandler.process(requestData);
    }
}

CloudDataHandler.java:

package com.tuturself.cor;

public class CloudDataHandler implements RequestHandler {

    private RequestHandler requestHandler;

    @Override
    public void setNextRequestHandler(RequestHandler requestHandler) {
        this.requestHandler = requestHandler;

    }

    @Override
    public boolean process(RequestData requestData) {
        requestData.setMetaCloudData("Meta Data from Cloud is populated");
        return requestHandler == null ? true : requestHandler.process(requestData);
    }
}

Now, we will have a domain object that carries data through the chain of handlers named RequestData.

package com.tuturself.cor;

public class RequestData {

    private String metaDBData;
    private String metaFileData;
    private String metaCloudData;

    public String getMetaDBData() {
        return metaDBData;
    }

    public void setMetaDBData(String metaDBData) {
        this.metaDBData = metaDBData;
    }

    public String getMetaFileData() {
        return metaFileData;
    }

    public void setMetaFileData(String metaFileData) {
        this.metaFileData = metaFileData;
    }

    public String getMetaCloudData() {
        return metaCloudData;
    }

    public void setMetaCloudData(String metaCloudData) {
        this.metaCloudData = metaCloudData;
    }

    @Override
    public String toString() {
        return "RequestData [metaDBData=" + metaDBData + ",\n " +
            "metaFileData=" + metaFileData + ",\n metaCloudData=" +
            metaCloudData + "]";
    }
}

Now, let's test the code. Here, we are creating an object of each handler type, then creating a chain of handlers, and finally calling the process method of the first handler object in the chain. The first handler will call the next handler when it is finished, and this step will be continued until the chain is finished.

package com.tuturself.cor;

import java.util.*;

public class TestCOR {

    public static void main(String[] args) {
        RequestData requestData = new RequestData();
        List < RequestHandler > requestHandlers = new ArrayList < > ();
        requestHandlers.add(new DBDataHandler());
        requestHandlers.add(new FileDataHandler());
        requestHandlers.add(new CloudDataHandler());
        // create the chain of Handler
        for (int i = 0; i < requestHandlers.size() - 1; i++) {
            requestHandlers.get(i).setNextRequestHandler(requestHandlers.get(i + 1));
        }
        requestHandlers.get(0).process(requestData);
        System.out.println(requestData);
    }
}

Output:

RequestData [metaDBData=Meta Data from DB is populated,
 metaFileData=Meta Data from File is populated,
 metaCloudData=Meta Data from Cloud is populated]

Benefits of the chain of responsibility pattern:

  1. Decouples the sender and receivers of the request.
  2. Simplifies our object because it doesn't have to know the chain’s structure and keep direct references to its members.
  3. Allows us to add or remove responsibilities dynamically by changing the members or order of the chain.

Drawbacks of the chain of responsibility pattern:

  1. Execution of the request isn’t guaranteed; it may fall off the end of the chain if no object handles it.
  2. It can be hard to observe and debug the runtime characteristics.

Connect any Java based application to your SaaS data.  Over 100+ Java-based data source connectors.

Topics:
java ,request handlers ,design patterns ,chain of request ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}