173. Implementing the Loan pattern

In this problem, we will talk about implementing the Loan pattern. Let's assume that we have a file containing three numbers (let's say, doubles), and each number is a coefficient of a formula. For example, the numbers x, y, and z are the coefficients of the following two formulas: x+y-z and x-y*sqrt(z). In the same manner, we can write other formulas as well.

At this point, we have enough experience to recognize that this scenario sounds like a good fit for Behavior Parameterization. This time, we don't define a custom functional interface, and we use a built-in functional interface called Function<T, R>. This functional interface represents a function that accepts one argument and produces a result. The signature of its abstract method is R apply(T t).

This functional interface becomes an argument of a static method that's meant to implement the Loan pattern. Let's place this method in a class called Formula:

public class Formula {
...
public static double compute(
Function<Formula, Double> f) throws IOException {
...
}
}

Notice that the compute() method accepts lambdas of the Formula -> Double type while it is declared in the Formula class. Let's reveal the entire source code of compute():

public static double compute(
Function<Formula, Double> f) throws IOException {

Formula formula = new Formula();
double result = 0.0 d;

try {
result = f.apply(formula);
} finally {
formula.close();
}

return result;
}

There are three points that should be highlighted here. First, when we create a new instance of Formula, we actually open a new Scanner to our file (check the private constructor of this class):

public class Formula {

private final Scanner scanner;
private double result;

private Formula() throws IOException {
result = 0.0 d;

scanner = new Scanner(
Path.of("doubles.txt"), StandardCharsets.UTF_8);
}
...
}

Second, when we execute the lambda, we are actually calling a chain of instance methods of Formula that perform the computation (apply the formula). Each of these methods returns the current instance. The instance methods that should be called are defined in the body of the lambda expression.

We only need the following computations, but more can be added:

public Formula add() {
if (scanner.hasNextDouble()) {
result += scanner.nextDouble();
}

return this;
}

public Formula minus() {
if (scanner.hasNextDouble()) {
result -= scanner.nextDouble();
}

return this;
}

public Formula multiplyWithSqrt() {
if (scanner.hasNextDouble()) {
result *= Math.sqrt(scanner.nextDouble());
}

return this;
}

Since the result of the computation (the formula) is a double, we need to provide a Terminal method that returns the final result:

public double result() {
return result;
}

Finally, we close the Scanner and reset the result. This takes place in the private close() method:

private void close() {
try (scanner) {
result = 0.0 d;
}
}

These pieces have been glued into the code bundled with this book under a class named Formula.

Now, do you remember our formulas? We had x+y-z and x-y*sqrt(z). The first one can be written as follows:

double xPlusYMinusZ = Formula.compute((sc)
-> sc.add().add().minus().result());

The second formula can be written as follows:

double xMinusYMultiplySqrtZ = Formula.compute((sc)
-> sc.add().minus().multiplyWithSqrt().result());

Notice that we can focus on our formulas and we don't need to bother with opening and closing the file. Moreover, the fluent API allows us to shape any formula and it is very easy to enrich it with more operations.

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

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