Day 4 (pushing the behavior as a parameter)

Meeting time! We cannot continue to add more filters like this; filtering with every attribute we can think of will end up in a huge Filters class that has big, complex methods with too many parameters and tons of boilerplate code.

The main problem is that we have different behaviors wrapped in boilerplate code. So, it will be nice to write the boilerplate code only once and push the behavior as a parameter. This way, we can shape any selection condition/criteria as behavior and juggle them as desired. The code will become more clear, flexible, easy to maintain, and have fewer parameters.

This is known as Behavior Parameterization, which is illustrated in the following diagram (the left-hand side shows what we have now; the right-hand side shows what we want):

If we think of each selection condition/criteria as a behavior, then it is pretty intuitive to think of each behavior as an implementation of an interface. Basically, all these behaviors have something in common – a selection condition/criteria and a return of the boolean type (this is known as a predicate). In the context of an interface, this is a contract that can be written as follows:

public interface MelonPredicate {
boolean test(Melon melon);
}

Furthermore, we can write different implementations of MelonPredicate. For example, filtering the Gac melons can be written like this:

public class GacMelonPredicate implements MelonPredicate {
@Override
public boolean test(Melon melon) {
return "gac".equalsIgnoreCase(melon.getType());
}
}

Alternatively, filtering all the melons that are heavier than 5,000g can be written:

public class HugeMelonPredicate implements MelonPredicate {
@Override
public boolean test(Melon melon) {
return melon.getWeight() > 5000;
}
}

This technique has a name – the Strategy design pattern. According to GoF, this can "Define a family of algorithms, encapsulate each one, and make them interchangeable. The strategy pattern lets the algorithm vary independently from client to client."

So, the main idea is to dynamically select the behavior of an algorithm at runtime. The MelonPredicate interface unifies all the algorithms dedicated to selecting melons, and each implementation of it is a strategy.

At the moment, we have the strategies, but we don't have any method that receives a MelonPredicate parameter. We need a filterMelons() method, as shown in the following diagram:

So, we need a single parameter and multiple behaviors. Let's look at the source code for filterMelons():

public static List<Melon> filterMelons(
List<Melon> melons, MelonPredicate predicate) {

List<Melon> result = new ArrayList<>();

for (Melon melon: melons) {
if (melon != null && predicate.test(melon)) {
result.add(melon);
}
}

return result;
}

This is much better! We can reuse this method with different behaviors as follows (here, we pass GacMelonPredicate and HugeMelonPredicate):

List<Melon> gacs = Filters.filterMelons(
melons, new GacMelonPredicate());

List<Melon> huge = Filters.filterMelons(
melons, new HugeMelonPredicate());
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.12.186