strategy design pattern in scala with real world example in scala

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Problem

Lets say we have a data comparison use case and we need to compare the raw data with the aggregated data based on the deviation mode that will be configured in the database and also we need to have separate algorithm based on the duration mode that will be set in the database.

Prerequisite

Please refer the earlier post on how we have implemented this in java for more information and understanding here strategy-design-pattern-real-world-example-java and its a prerequisite to understand the below article.

The functional programming techniques in Scala make certain OOP design patterns, such as the Strategy Pattern, obsolete. As you can pass algorithms around just like you pass objects around, there is no need for the Strategy Pattern in scala.

We will be defining two objects DeviationMode and DurationMode .

The DeviationMode has meanDeviationHigherStrategy, meanDeviationLowerStrategy and compareMeanDeviation behaviour which is defined as a function which we will be passing to the callback methods mean and compare. We also have factory pattern implemented to return appropriate meanDeviationLowerStrategy or meanDeviationHigherStrategy based on the configuration for deviation mode [higher and lower].


object DeviationMode {

val meanDeviationHigherStrategy = (rawMetricValue: Double, averageMetricValue: Double) =>
{
if (rawMetricValue > averageMetricValue) {
((rawMetricValue - averageMetricValue) / averageMetricValue) * 100;

} else {
0
}

}

val meanDeviationLowerStrategy = (rawMetricValue: Double, averageMetricValue: Double) =>
{
if (averageMetricValue > rawMetricValue) {
((averageMetricValue - rawMetricValue) / averageMetricValue) * 100

} else {
0
}

}

val compareMeanDeviation = (meanDevaition: Double, threshold: Double) =>
{
if (meanDevaition > threshold) true else false
}

def mean(callback: (Double, Double) => Double, raw: Double, agg: Double) =
{
callback(raw, agg);
}

def compare(callback: (Double, Double) => Boolean, mean: Double, threshold: Double) =
{
callback(mean, threshold);
}

def getDeviationMode(mode: String) =
{

if (mode == "LOWER") {
meanDeviationLowerStrategy
} else if (mode == "HIGHER") {
meanDeviationHigherStrategy
} else {
throw new IllegalArgumentException("Supported type are LOWER and HIGHER.")
}

}

}

The DurationMode has getTimestampsSameWeek and getTimestampsPreviousWeek behaviour which is defined as a function which we will be passing to the callback methods getTimeStamps. We also have factory pattern implemented to return appropriate getTimestampsPreviousWeek or getTimestampsSameWeek based on the configuration for DurationMode [PW and SW].


object DurationMode {

val getTimestampsSameWeek = (timeStamp: String, duration: String) =>
{
Vector("TodayDate")
}

val getTimestampsPreviousWeek = (timeStamp: String, duration: String) =>
{
Vector("LastDate")
}

def getTimeStamps(callback: (String, String) => Vector[String], timeStamp: String, duration: String) =
{
callback(timeStamp, duration);
}

def getDurationMode(duration: String) =
{
if (duration == "PW") {
getTimestampsPreviousWeek
} else if (duration == "SW") {
getTimestampsSameWeek
} else {
throw new IllegalArgumentException("Supported type are PW and SW.")
}
}

}

And finally we have a driver class where we are getting all the required parameter[which is hard coded here for simplicity] and based the configuration we get the deviationModeFunction and the durationModeFunction. We are passing the durationModeFunction as a parameter to the getTimeStamps method along with other required parameters to get the vector as return type. Similarly we are calling mean and compare method which takes a function as a parameter along with the other required parameters.

So basically we have passed the algorithm as a parameter to the function. Scala code tends to be much more concise. In this particular case, because you can pass algorithms around just like you pass objects around, there is no need for the Strategy Pattern.


object Driver {

def main(args: Array[String]): Unit = {

val deviationMode: String = "LOWER";
val duration: String = "5";
val durationType: String = "SW";
val deviationModeFunction = DeviationMode.getDeviationMode(deviationMode)
val durationModeFunction = DurationMode.getDurationMode(durationType)
val timeValue = DurationMode.getTimeStamps(durationModeFunction, "timeStamp", duration)
val mean = DeviationMode.mean(deviationModeFunction, 2, 25)
val breach = DeviationMode.compare(DeviationMode.compareMeanDeviation, mean, 88)

if (breach && timeValue.contains("TodayDate")) {
print("breach generated")
} else {
print("no breach generated")
}

}

}