Chain of responsibility is a behavioral design pattern which helps in decoupling the sender of a request from its receiver by giving multiple objects the chance to handle or process the request.
Usually this pattern ends when a request reaches an object that can process it but there are some variations to the chain of responsibility design pattern where we might need to push the request further or even multiply it and broadcast to other receivers.
Lets say we wanted to apply some calibration on the data we get from ems and what calibration to apply is decided on the basis vendor type,techtype,policytype and device type. So to solve this problem we will be using chain of responsibility pattern where we will creating a Calibrate object which will have vendor,deviceType,techType and rawData as instance variable. We will be passing this object to a chain of objects and based on some condition we will decide whether a specific logic needs to be applied or not on the calibrate object data.
If we closely look into the chain of responsibility pattern you can see there is some similarities to the decorator design pattern and infact we can use that pattern to solve the same problem.
Below is the generic class diagram of chain of responsibility pattern
Below is the class diagram we will be working on
Lets start with the domain object
public class Calibrate { private String vendor; private String deviceType; private String techType; private double rawData; public Calibrate(String vendor, String deviceType, String techType, double rawData) { super(); this.vendor = vendor; this.deviceType = deviceType; this.techType = techType; this.rawData = rawData; } public String getVendor() { return vendor; } public void setVendor(String vendor) { this.vendor = vendor; } public String getDeviceType() { return deviceType; } public void setDeviceType(String deviceType) { this.deviceType = deviceType; } public String getTechType() { return techType; } public void setTechType(String techType) { this.techType = techType; } public double getRawData() { return rawData; } public void setRawData(double rawData) { this.rawData = rawData; } }
Lets code the abstract handler
public abstract class CalibrateHandlerBase { private CalibrateHandlerBase successor; public CalibrateHandlerBase getSuccessor() { return successor; } public void setSuccessor(CalibrateHandlerBase successor) { this.successor = successor; } public abstract double applyCorrection(Calibrate calibrate); }
Lets code the concrete concrete handler here we have 4 concrete handler and the data will flow through the chain and if the condition is satisfied additional processing is done by each objects in the chain.
public class HuwCalibration extends CalibrateHandlerBase { @Override public double applyCorrection(Calibrate calibrate) { System.out.println("Huw calibration called"); if (calibrate.getVendor().equalsIgnoreCase("HUW")) { calibrate.setRawData(calibrate.getRawData() * 2); if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } else { if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } } }
public class CiscoCalibration extends CalibrateHandlerBase { @Override public double applyCorrection(Calibrate calibrate) { System.out.println("cisco calibration called"); if (calibrate.getVendor().equalsIgnoreCase("CISCO")) { calibrate.setRawData(calibrate.getRawData() * 2); if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } else { if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } } }
public class PortCalibration extends CalibrateHandlerBase { @Override public double applyCorrection(Calibrate calibrate) { System.out.println("port calibration called"); if (calibrate.getDeviceType().equalsIgnoreCase("PORT")) { calibrate.setRawData(calibrate.getRawData() * 2); if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } else { if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } } }
public class VdslCalibration extends CalibrateHandlerBase { @Override public double applyCorrection(Calibrate calibrate) { System.out.println("vdsl calibration called"); if (calibrate.getTechType().equalsIgnoreCase("VDSL")) { calibrate.setRawData(calibrate.getRawData() * 2); if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } else { if (getSuccessor() != null) { return getSuccessor().applyCorrection(calibrate); } else { return calibrate.getRawData(); } } } }
Lets code the test class
public class Test { public static void main(String[] args) { CalibrateHandlerBase huwCalibration = new HuwCalibration(); CalibrateHandlerBase ciscoCalibration = new CiscoCalibration(); CalibrateHandlerBase portCalibration = new PortCalibration(); CalibrateHandlerBase vdslCalibration = new VdslCalibration(); huwCalibration.setSuccessor(ciscoCalibration); ciscoCalibration.setSuccessor(portCalibration); portCalibration.setSuccessor(vdslCalibration); double calibratedData=huwCalibration.applyCorrection(new Calibrate("HUW", "PORT", "VDSL", 2)); double calibratedData2=huwCalibration.applyCorrection(new Calibrate("HUW", "PORT", "ADSL", 2)); System.out.println(calibratedData); System.out.println(calibratedData2); } }
As informed in the starting of this article there is similarities between decorator pattern and chain of responsibility pattern but if we look at the client code we can figure out that in chain of responsibility pattern we just need to set successor and the flow takes care of which object to use in the chain. But if we look into the decorator pattern we need to choose which object to use in the client program based on some parameter as below which may be bit inflexible. Chain of responsibility pattern is much more easier to use from client perspective though this can be overcome by mixing some other pattern with decorator patter.
double calibratedData = new DeviceTypeCalibration(new TechTypeCalibration(new HuwCalibration())) .calibrate(rawData);