Tuesday, December 15, 2015

Thanks lambdas

With this post, I want to show you step by step the process of transforming a static and rigid code, to something more flexible and reusable using lambda expressions. Let's stop talking, let's to see code.

Imagine you have a selling car business, and you want an application to filtering the cars by brand. Then your business logic could look like this.

// business logic
public static List<Car> filterCarsByBrand(List<Car> cars, String brand) {
ArrayList<Car> filteredCars = new ArrayList<Car>();
for (Car car : cars) {
if (brand.equals(car.getBrand())) {
filteredCars.add(car);
}
}
return filteredCars;
}
...
// from your programm, you can call it from this way
List<Car> filteredCars = filterCarsByBrand(cars, "Ferrari");

That it's ok. But now you want to filtering by brand and color too. You have to add a new method in order to support the new functionality. In this case, we can do it easily.

// business logic
public static List<Car> filterCarsByBrandAndColor(List<Car> cars, String brand, String color) {
// we can reuse the method to filtering by brand
List<Car> filteredCarsByBrand = filterCarsByBrand(cars, brand);
ArrayList<Car> filteredCars = new ArrayList<Car>();
for (Car car : cars) {
if (color.equals(car.getColor())) {
filteredCars.add(car);
}
}
return filteredCars;
}
...
// from your programm, you can call it from this way
List<Car> filteredCars = filterCarsByBrand(cars, "Ferrari", "red");

What can we say about this code? In front of any new requirement, a new method is implemented. Then we have the phenomenon of method explosion.

We can try to do it better with a new interface called CarChecker.

// new interface
public interface CarChecker {
boolean check(Car car);
}
...
//business logic
public static List<Car> filterCars(List<Car> cars, CarChecker carChecker) {
ArrayList<Car> filteredCars = new ArrayList<Car>();
for (Car car : cars) {
if (carChecker.check(car)) {
filteredCars.add(car);
}
}
return filteredCars;
}
...
// from your program
List<Car> filter1 = filterCars(input, new BrandCarChecker());
List<Car> filter2 = filterCars(input, new BrandAndColorCarChecker());
Yes, I know what are you thinking. Now we have a generic method, but now we have class explosion instead of method explosion. You are right. We can try to do it with anonymous classes.

List<Car> cars = filterCars(input, new CarChecker() {
public boolean check(Car car) {
return "Ferrari".equals(car.getBrand()) && "red".equals(car.getColor());
}
});
But CarChecker it's a functional interface. We can use lambda expressions.

List<Car> filteredCars = filterCars(input, car -> {
return "Ferrari".equals(car.getBrand()) && "red".equals(car.getColor());
});

At this point, our code looks cool. But Java 8 includes something called Predicate. Then we can rewrite our business logic to this.

// business logic
public static List<Car> filterCars(List<Car> cars, Predicate<Car> carPredicate) {
ArrayList<Car> filteredCars = new ArrayList<Car>();
for (Car car : cars) {
if (carPredicate.test(car)) {
filteredCars.add(car);
}
}
return filteredCars;
}
...
// from your program
List<Car> cars = filterCars(input, car -> {return "Ferrari".equals(car.getBrand()) && "red".equals(car.getColor());});
Usually, when you filter a collection of objects then you want to do something with the result. We can try to generalize a function to processing the result of cars filtering using Consumer interface, included also by Java 8.

// business logic
public static void processCars(List<Car> cars, Predicate<Car> carPredicate, Consumer<Car> carConsumer) {
for (Car car : cars) {
if (carPredicate.test(car)) {
carConsumer.accept(car);
}
}
}
...
// from your program
processCars(input, c -> { return "Ferrari".equals(c.getBrand()); }, c -> System.out.println(c));

And that's all. Code more flexible and reusable.

Thanks lambdas.

No comments:

Post a Comment