168. Implementing the Execute Around pattern

The Execute Around pattern tries to eliminate the boilerplate code that surrounds specific tasks. For example, the tasks specific to a file need to be surrounded by code for the purpose of opening and closing the file.

Mainly, the Execute Around pattern is useful in scenarios that imply tasks that take place inside a resource open-close lifespan. For example, let's assume that we have a Scanner and that our first task consists of reading a double value from a file:

try (Scanner scanner = new Scanner(
Path.of("doubles.txt"), StandardCharsets.UTF_8)) {

if (scanner.hasNextDouble()) {
double value = scanner.nextDouble();
}
}

Later on, another task consists of printing all double values:

try (Scanner scanner = new Scanner(
Path.of("doubles.txt"), StandardCharsets.UTF_8)) {
while (scanner.hasNextDouble()) {
System.out.println(scanner.nextDouble());
}
}

The following diagram highlights the boilerplate code that surrounds these two tasks:

In order to avoid this boilerplate code, the Execute Around pattern relies on Behavior Parameterization (further detailed in the Writing functional interfaces section). The steps that are needed to accomplish this are as follows:

  1. The first step is to define a functional interface that matches the Scanner -> double signature, which may throw an IOException:
@FunctionalInterface
public interface ScannerDoubleFunction {
double readDouble(Scanner scanner) throws IOException;
}

Declaring the functional interface is just half of the solution.

  1. So far, we can write a lambda of the Scanner -> double type, but we need a method that receives it and executes it. For this, let's consider the following method in the Doubles utility class:
public static double read(ScannerDoubleFunction snf)
throws IOException {

try (Scanner scanner = new Scanner(
Path.of("doubles.txt"), StandardCharsets.UTF_8)) {

return snf.readDouble(scanner);
}
}

The lambda that's passed to the read() method is executed inside the body of this method. When we pass the lambda, we provide an implementation of the abstract method known as readDouble() directly inline. Mainly, this is perceived as an instance of the functional interface, ScannerDoubleFunction, and so we can call the readDouble() method to obtain the desired result.

  1. Now, we can simply pass our tasks as lambdas and reuse the read() method. For example, our tasks can be wrapped in two static methods, as shown here (this practice is needed to obtain clean code and avoid big lambdas):
private static double getFirst(Scanner scanner) {
if (scanner.hasNextDouble()) {
return scanner.nextDouble();
}

return Double.NaN;
}

private static double sumAll(Scanner scanner) {
double sum = 0.0d;
while (scanner.hasNextDouble()) {

sum += scanner.nextDouble();
}

return sum;
}

  1. Having these two tasks as examples, we can write other tasks as well. Let's pass them to the read() method:
double singleDouble 
= Doubles.read((Scanner sc) -> getFirst(sc));
double sumAllDoubles
= Doubles.read((Scanner sc) -> sumAll(sc));

The Execute Around pattern is quite useful for eliminating the boilerplate code that's specific for opening and closing resources (I/O operations).

..................Content has been hidden....................

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