scala chain of responsibility design pattern real world example in scala

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

class Calibrate(val vendor:String,val deviceType:String,val techType:String,var rawData:Double) {

}
Lets code the abstract handler

abstract class CalibrateHandlerBase {

var successor=None:Option[CalibrateHandlerBase];

def applyCorrection(calibrate: Calibrate): Double;

}

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.We have also created a nocalibration class which we will be using to set the successor for the last object in the chain and also we will be using this as the default value in the getorelse method.


class HuwCalibration() extends CalibrateHandlerBase {

def applyCorrection(calibrate: Calibrate): Double =
{

if (calibrate.vendor == "HUW") {
calibrate.rawData = calibrate.rawData * 2;
return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
} else {
return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
}

}

}


class CiscoCalibration() extends CalibrateHandlerBase {

def applyCorrection(calibrate: Calibrate): Double =
{

if (calibrate.vendor == "CISCO") {
calibrate.rawData = calibrate.rawData * 2;

return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
} else {
return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
}

}

}


class PortCalibration() extends CalibrateHandlerBase {

def applyCorrection(calibrate: Calibrate): Double =
{

if (calibrate.deviceType == "PORT") {
calibrate.rawData = calibrate.rawData * 2;
return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
} else {
return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
}

}

}


class VdslCalibration() extends CalibrateHandlerBase {

def applyCorrection(calibrate: Calibrate): Double =
{

if (calibrate.techType == "VDSL") {
calibrate.rawData = calibrate.rawData * 2;
return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
} else {
return return successor.getOrElse(new NoCalibration()).applyCorrection(calibrate);
}

}

}

Lets code the test class

object Test extends App {

val calibrate = new Calibrate("HUW", "PORT", "VDSL", 2);
val huwCalibration=new HuwCalibration();
val ciscoCalibration=new CiscoCalibration();
val portCalibration=new PortCalibration();
val vdslCalibration=new VdslCalibration();
huwCalibration.successor=Some(ciscoCalibration);
ciscoCalibration.successor=Some(portCalibration);
portCalibration.successor=Some(vdslCalibration);
vdslCalibration.successor=Some(new NoCalibration())
println(huwCalibration.applyCorrection(calibrate));

}

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.

val calibratedData:Double = new DeviceTypeCalibration(new TechTypeCalibration(new HuwCalibration()))
.getCalibration(rawData);