Behavior Parameterization

jeromevillamor

Jerome Ryan Villamor

Posted on April 11, 2023

Behavior Parameterization

One of the simplest ways to improve code is to pass behavior as a parameter to a method. Business requirements change every time and so our code needs to adapt. Take a look at the code below to see how we can introduce behavior parameterization to achieve maintainability and flexibility to our code.

Let’s say we have two requirements.

  1. Filter green apples from the list of apples.
  2. Filter apples by its weight.
public List<Apple> filterApplesByColor(List<Apple> inventory, String color){
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(apple.getColor().equals(color)){
            result.add(apple);
        }
    }
    return result;
}

public List<Apple> filterApplesByWeight(List<Apple> inventory, int weight){
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(apple.getWeight() > weight){
            result.add(apple);
        }
    }
    return result;
}


List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> heavyApples = filterApplesByWeight(inventory, 150);

Enter fullscreen mode Exit fullscreen mode

Notice how are we duplicating the code here. The two methods have the same code, only the difference is the condition statement inside of it. What happens if there are additional requirements such as filtering by size, shape or its origin? We will end up on a lot of duplicate codes which violate the DRY (Don’t repeat yourself) principle.

By using behavior parameterization, we instead focusing on the condition statement rather than creating their own dedicated method. Sounds complex but it is so easy to implement. To achieve it, first we need to create an interface.

public interface ApplePredicate{
    boolean isValid(Apple apple);
}
Enter fullscreen mode Exit fullscreen mode

Then we will create a method that instead of hard coding the condition statement inside of it, we will rather pass the above interface as parameter.

public List<Apple> filterApples(List<Apple> inventory, ApplePredicate applePredicate){
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(applePredicate.isValid(apple)){
            result.add(apple);
        }
    }
    return result;
}

Enter fullscreen mode Exit fullscreen mode

In order to use the filterApples method, we will use anonymous class. Of course, we can create a new class instead but that defeats the purpose of maintainability as creating one class per condition is too much for this kind of scenario.

List<Apple> greenApples = filterApples(inventory, new ApplePredicate() {
    public boolean isValid(Apple apple){
        return apple.getColor().equals("green"); 
    }
});

List<Apple> heavyApples = filterApples(inventory, new ApplePredicate() {
    public boolean isValid(Apple apple){
        return apple.getWeight() > 150; 
    }
});

Enter fullscreen mode Exit fullscreen mode

Our code becomes simpler. We are reusing the same method whatever filter requirements we may want. Filter by shape or origin? Just pass the condition!

But we still have one problem. I don’t know about you but using anonymous class is too verbose for me. We can shorten our code using lambda. If you don’t know lambda yet, don’t worry I will create a separate article about it. For now, think of lambda as sweet syntax for anonymous class that contains only one method (which is applicable to our scenario).

The only changes that we need is to add an @FunctionalInterface annotation in our interface.

@FunctionalInterface
public interface ApplePredicate{
    boolean isValid(Apple apple);
}

public List<Apple> filterApples(List<Apple> inventory, ApplePredicate applePredicate){
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(applePredicate.isValid(apple)){
            result.add(apple);
        }
    }
    return result;
}

Enter fullscreen mode Exit fullscreen mode

Then we can change the anonymous class into lambda.

List<Apple> greenApples = filterApples(inventory, () -> apple.getColor().equals("green"));

List<Apple> heavyApples = filterApples(inventory, () -> apple.getWeight() > weight; );

Enter fullscreen mode Exit fullscreen mode

Now, we have created a more maintainable and flexible code using behavior parameterization. Next time we add another filter, we will not create a method or a class. Just pass the condition of what we want, and it will be given to us. Pretty cool, right?

💖 💪 🙅 🚩
jeromevillamor
Jerome Ryan Villamor

Posted on April 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related