scala way chain of responsibility design pattern using partial functions

scala partial functions is a function that does not provide an answer for every possible input value it can be given. It provides an answer only for a subset of possible data, and defines the data it can handle using the method isDefinedAt in the PartialFunction trait.

Please go through the link here scala-chain-responsibility-design-pattern  which is a prerequisite for better understanding of this design pattern.

A feature of partial functions that we can use in chaining. For instance, one method may apply calibration based on the vendor type, and another method on device type and another on the tech type. Together they can solve all calibration type problems.

Lets code the solution using the scala partial function

class PartialFunctionCalibration {

val huwCalibration = new PartialFunction[Calibrate, Calibrate] {

def apply(calibrate: Calibrate) =
{
if (isDefinedAt(calibrate)) {
calibrate.rawData = calibrate.rawData * 2
println("huw" + calibrate.rawData);
calibrate
} else {
calibrate
}

}

def isDefinedAt(calibrate: Calibrate) =
{
if (calibrate.vendor == "HUW") {
true
} else {
false
}
}

}

val ciscoCalibration = new PartialFunction[Calibrate, Calibrate] {

def apply(calibrate: Calibrate) =
{
if (isDefinedAt(calibrate)) {
calibrate.rawData = calibrate.rawData * 2
println("cisco" + calibrate.rawData);
calibrate
} else {
calibrate
}
}

def isDefinedAt(calibrate: Calibrate) =
{
if (calibrate.vendor == "CISCO") {
true
} else {
false
}
}

}

val portCalibration = new PartialFunction[Calibrate, Calibrate] {

def apply(calibrate: Calibrate) =
{
if (isDefinedAt(calibrate)) {
calibrate.rawData = calibrate.rawData * 2
println("port" + calibrate.rawData);
calibrate
} else {
calibrate
}
}

def isDefinedAt(calibrate: Calibrate) =
{
if (calibrate.deviceType == "PORT") {
true
} else {
false
}
}

}

val vdslCalibration = new PartialFunction[Calibrate, Calibrate] {

def apply(calibrate: Calibrate) =
{
if (isDefinedAt(calibrate)) {
calibrate.rawData = calibrate.rawData * 2
println("vdsl" + calibrate.rawData);
calibrate
} else {
calibrate
}
}

def isDefinedAt(calibrate: Calibrate) =
{
if (calibrate.techType == "VDSL") {
true
} else {
false
}
}

}

}

Driver Code

object Test extends App {

val calibrate = new Calibrate("HUW", "PORT", "VDSL", 2);

val pf=new PartialFunctionCalibration();

val cool= pf.huwCalibration.andThen(pf.ciscoCalibration).andThen(pf.portCalibration).andThen(pf.vdslCalibration)(calibrate)

println(cool.rawData)

}

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);