Chain of Responsibility Design Pattern

Thinking about the Chain of Responsibility, think of a train!

The pattern is like a train. We can plug-in goods compartments, AC/Non-AC, Pantry, chair car compartments in whatever sequence we want to the same engine. Moreover, even if it is a kilometer long, the complexity of the bogies remain limited. This is exactly the flexibility what the Chain of Responsibility design pattern provide to us.

Many a times we would have noticed our code to be too complex. Several hundreds or thousands of lines in a single method or a class might be giving us a maintenance nightmare. It happens not because our requirements are complex. But, mainly because we have ignored an important design principle, the principle of Single Responsibility.

The Chain of Responsibility is an important behavioral design pattern to implement Single Responsibility. It can divide our complex processing logic into simple and pluggable units. Just like the bogies in a train!

Depending on our use case, we can make One or more of the processing units in the chain to process our request.

Its a very powerful design pattern. In case we feel our application is overly complicated, we have most likely under utilized this pattern. But, before we discuss more on this, lets us have a look at its implementation.

 

How does the pattern work?

The diagram below shows how we can design multistep process using chain of responsibility.

As can notice we have separated each step into different classes. Then the obvious question would be how does it help ?

  • Firstly, it stops complicating the main process.
  • Secondly, for any addition or removal of these steps, we need not have to touch files of the other steps. Hence, allowing us follow the open and close principle.
  • Thirdly, it keeps our code simple and makes our unit testing easy.
  • Fourthly, these separate processing classes provides us an intuitive idea about the process without opening the code. Thus, it greatly helps the maintenance team.

A Demo Implementation

As we can see above, the main objective of this pattern is to divide the processing logic into simpler and manageable units. Depending on our use case, we can customize our execution process.

  • Skipping the Processing Units: We may need only one out of several processors like stepOne and stepTwo, for instance. Then, we can do that by adding the required conditional logics for those processing steps.
  • Breaking the Chain: When we have to stop the processing after a certain point, we may opt for not calling the continueNext(). This will stop the chain and come out of the processing without using the subsequent processors. This case is similar to how we use our servlet filters for instance.
    • We may also use our exception handling mechanisms to break out of the loop.

In our demo example we will use a slightly different approach for chaining, so that the core processing steps remains simple. Instead of the processing nodes in the chain using continueNext(), they will send a flag to an executor to handle the same.

Part 1: The Chain of Processors

Its like a regular processor interface with an additional feature to decide the continuation to its next step. As we have discussed above, we have modified the chain processors to send a boolean flag instead of triggering the next process itself.

The VerifyDeliveryPincode shows how we can conditionally decide to continue or break the chain.

package spectutz.dp.behavior.chain;

public interface IChainProcessor<E>{
	public static final boolean CONTINUE_CHAIN = true;
	public static final boolean BREAK_CHAIN = false;
	/*
	 * Return CONTINUE_CHAIN, in case we want to continue with next processor
	 * Return BREAK_CHAIN, in case we want to stop proceeding to the next processor 
	 */
	public boolean process(E e);
}
package spectutz.dp.behavior.chain.processor;

import java.util.Arrays;
import java.util.List;

import spectutz.dp.behavior.chain.IChainProcessor;
import spectutz.dp.behavior.chain.Order;

public class VerifyDeliveryPincode implements IChainProcessor<Order>{
    List<String> validPinCodes = Arrays.asList("10025", "10026", "10026");

	public boolean process(Order order) {
		
		if(validPinCodes.contains(order.getDeliveryPincode())) {
			System.out.println("Verified the delivery pincode to be fine.");

			return CONTINUE_CHAIN;
		}else {
			System.out.println("Delivery pincode not covered..."
					+ "\nException captured and further processing discontinued.");
			
			return BREAK_CHAIN;
		}		
	}	
}
package spectutz.dp.behavior.chain.processor;

import spectutz.dp.behavior.chain.IChainProcessor;
import spectutz.dp.behavior.chain.Order;

public class VerifyOrderValue implements IChainProcessor<Order>{

	public boolean process(Order order) {	
		System.out.println("Verified the total order value.");			
		
		return CONTINUE_CHAIN;
	}
}

package spectutz.dp.behavior.chain.processor;

import spectutz.dp.behavior.chain.IChainProcessor;
import spectutz.dp.behavior.chain.Order;

public class VerifyOffersAndDiscounts implements IChainProcessor<Order>{

	public boolean process(Order order) {
		System.out.println("Verified offers and discount!");
		
		return CONTINUE_CHAIN;
	};
}
package spectutz.dp.behavior.chain.processor;

import spectutz.dp.behavior.chain.IChainProcessor;
import spectutz.dp.behavior.chain.Order;

public class SaveOrderDetail implements IChainProcessor<Order>{

	public boolean process(Order order) {	
		System.out.println("Saved the order.");			
		return CONTINUE_CHAIN;
	}
}
package spectutz.dp.behavior.chain;

//A minimal order object for demo
public class Order{
	private String id;
	private String deliveryPincode;
	
	public Order(String id,String deliveryPincode) {
		this.id = id;
		this.deliveryPincode =deliveryPincode;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getDeliveryPincode() {
		return deliveryPincode;
	}
	public void setDeliveryPincode(String deliveryPincode) {
		this.deliveryPincode = deliveryPincode;
	}
}

Part 2: The Chain-of-Responsibility Executor

Its has two simple methods :

  1. One method to add the chain of processors one by one. We can create this list using any other means as well.
  2. The second method executes the chain sequentially and is uses the flag from each executor to either continue or break the chain.
package spectutz.dp.behavior.chain;
import java.util.ArrayList;
import java.util.List;

public class  ChainOfResponsibility<V>{
	//A chain of responsibilities : A flexible list of sub-steps
	private List<IChainProcessor<V>> chainOfProcessors;

	
	//Break the chain Get the first node of the chain and start the process
	public void process(V v) {
		try {
			if(chainOfProcessors!=null) {
				for (IChainProcessor<V> iChainProcessor : chainOfProcessors) {
					boolean continueChain = iChainProcessor.process(v);
					if(!continueChain)break;
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	//Add the processor to the end of the chain
	public ChainOfResponsibility<V> addProcessor(IChainProcessor<V> subProcess) {
		if(this.chainOfProcessors == null) {
			this.chainOfProcessors= new ArrayList<IChainProcessor<V>>();
		}
		this.chainOfProcessors.add(subProcess);
		return this;
	}
	
}

Demo Client

The demo client has two use cases to show, how can we execute all or a few of processors in the chain.

package spectutz.dp.behavior.chain;

import spectutz.dp.behavior.chain.processor.SaveOrderDetail;
import spectutz.dp.behavior.chain.processor.VerifyDeliveryPincode;
import spectutz.dp.behavior.chain.processor.VerifyOffersAndDiscounts;
import spectutz.dp.behavior.chain.processor.VerifyOrderValue;
import spectutz.dp.behavior.chain.processor.VerifyTaxOnItems;

public class ChainOfResponsibilityDemo{
	public static void main(String[] args) {		
		ChainOfResponsibility<Order> orderProcessorChain = new ChainOfResponsibility<Order>();
		
		//A chain of processors  
		orderProcessorChain.addProcessor(new VerifyDeliveryPincode());
		orderProcessorChain.addProcessor(new VerifyOrderValue());
		orderProcessorChain.addProcessor(new VerifyTaxOnItems());
		orderProcessorChain.addProcessor(new VerifyOffersAndDiscounts());
		orderProcessorChain.addProcessor(new SaveOrderDetail());
		
		System.out.println("\n***Demo breaking a chain***");//
		orderProcessorChain.process(new Order("1005","10020"));
		
		System.out.println("\n***Demo executing the complete chain***");//
		orderProcessorChain.process(new Order("1005","10025"));
	}
}

Output:

***Demo breaking a chain using a wrong pincode***
Delivery pincode not covered...
Exception captured and further processing discontinued.

***Demo complete execution of chain***
Verified the delivery pincode to be fine.
Verified the total order value.
Verified tax on product items.
Verified offers and discount!
Saved the order.

Summary of the benefits

  • Firstly, it helps us break the system into simpler units which brings in clarity and makes our maintenance easy.
  • Secondly, the simpler units makes our unit testing easy.
  • Furthermore, as these units are pluggable, it minimizes our future enhancement efforts.