Groovy - Strategy Pattern using Closures

Suppose we have a list of numbers.

def numbers = [1, 2, 3, 4, 5, 6, -1]

We are asked to do the following

  1. Find out all the even numbers in the list
  2. Find out all the numbers divisible by 3
  3. Find out all the negative numbers in the list

Let's write a function each for solving these independent of each other. We will end up with a code like this.

def filterEven(def numbers) {
    def evenNumbers = []
    for (number in numbers) {
        if (number % 2 == 0) {
            evenNumbers << number
        }
    }
    evenNumbers
}

def filterDivisibleByThree(def numbers) {
    def numbersDivisibleByThree = []
    for (number in numbers) {
        if (number % 3 == 0) {
            numbersDivisibleByThree << number
        }
    }
    numbersDivisibleByThree
}

def filterNegative(def numbers) {
    def negativeNumbers = []
    for (number in numbers) {
        if (number < 0) {
            negativeNumbers << number
        }
    }
    negativeNumbers
}

println filterEven(numbers) // [2, 4, 6]
println filterDivisibleByThree(numbers) // [3, 6]
println filterNegative(numbers) // [-1]

If you observe these methods carefully, you will figure out that the only difference between them is how to we decide if a number to be included in the result or not. Let's refactor the above code to extract the algorithm into respective closures and generalise the iteration part.

def isEven = { it % 2 == 0 }
def isDivisibleByThree = { it % 3 == 0 }
def isNegative = { it < 0 }

def filterByCriteria(def numbers, def criteria) {
    def result = []
    for(number in numbers){
        if(criteria(number)) {
            result << number
        }
    }
    result
}

println filterByCriteria(numbers, isEven) // [2, 4, 6]
println filterByCriteria(numbers, isDivisibleByThree) // [3, 6]
println filterByCriteria(numbers, isNegative) // [-1]
You may take a look at my post on higher order functions, if you are not already familiar with the concept.

Groovy already comes with a standard version of the above method filterByCriteria called findAll. Let's refactor the code to make use of findAll.

println numbers.findAll(isEven) // [2, 4, 6]
println numbers.findAll(isDivisibleByThree) // [3, 6]
println numbers.findAll(isNegative) // [-1]

What we have achieved through the above refactoring is  - we have evolved the solution to make use of the strategy pattern! First we started out by identifying the need to have different implementations for the specific filtering logic (strategy), while generalising the rest of the process. Instead of creating class per concrete strategy, we simply used closure per concrete strategy. This was possible due to the higher order method findAll.

So many a times higher order methods and closures may lead to simpler implementation of the strategy pattern than class-based implementations.

Show Comments