In this final part of the series I am going to look into the Lambda Expressions pattern to define an internal DSL for constructing object graphs. I am also going to provide some conclusions for the three patterns.
Lambda Expressions are all the rage these days. The tide started to rise with the paradigm shift towards multi-threaded applications. It’s arguably simpler to create a thread-safe application if it’s solely composed of pure functions (i.e. methods without side effects) than the traditional way of composing and sharing potentially mutable objects. Although you can certainly write functional code in a non-functional language like Java 7, it gets a lot easier with Lambda Expressions in Java 8.
Let’s look at the following example code from Mohamed Sanaulla’s excellent blog posting again:
Graph(g -> {
g.edge(e -> {
e.from(a);
e.to(b);
e.weight(12.3);
});
g.edge(e -> {
e.from(b);
e.to(c);
e.weight(10.5);
});
});
First, the good parts: Like with the Method Chaining pattern (if done right), the order of statements is irrelevant: You can swap them at will and it will still construct virtually the same object graph.
Another advantage is that you can provide some context with configurable default values for some properties of the constructed object graph. Consider this example:
public class GraphBuilder {
double defaultWeight = 1.0;
...
public void edge(Consumer<EdgeBuilder> eConsumer){
EdgeBuilder eBuilder = new EdgeBuilder();
eBuilder.weight(defaultWeight); // configure default value
eConsumer.accept(eBuilder);
...
}
}
In this modified example from Mohamed Sanaulla, I have added the eBuilder.weight(defaultWeight)
statement.
The effect is that if the eConsumer
does not call the weight(Double w)
method, then defaultWeight
will prevail.
Note that because this is an instance field, I do not need to share mutable state with other threads (I am assuming that
there is a getter and a setter method for defaultWeight
).
As you can see, the Lambda Expressions pattern is pretty much on par with the Method Chaining pattern.
My biggest grief about it is that it’s a lot more code to write and read.
Compare the initial example to the equivalent expression with Method Chaining again (I am using my Builder
and
Injection
interfaces again):
Graph()
.edge()
.from(a)
.to(b)
.weight(12.3)
.inject()
.edge()
.from(b)
.to(c)
.weight(10.5)
.inject()
.build();
This code has the same number of lines (twelve), yet it doesn’t need the ugly function and method call notation.
Conclusion
Finally, let’s compare the three patterns Method Chaining, Nested Functions and Lambda Expressions for defining an internal DSL for constructing object graphs: Among the three, the Nested Functions pattern clearly wins the competition for conciseness. As a reminder, here is the equivalent expression using the Nested Functions pattern to construct the object graph:
Graph(
edge(from(a), to(b), weight(12.3),
edge(from(b), to(c), weight(10.5)
);
This is just four lines of code instead of twelve - hooray! However, the Nested Functions pattern has two serious disadvantages: First, it can’t disambiguate parameters of the same type, so it can’t help you to avoid mistakes in their ordering. Second, it can’t easily provide some configurable context to the construction procedure without sharing mutable state. Considering the shift of paradigm to multithreaded applications, the last point is probably the most important. Therefore, I would only choose this pattern if these points do not apply and I could be confident that they still don’t apply when considering foreseeable changes in future versions of my code.
The Method Chaining and Lambda Expressions pattern do much better in this respect. Though they are functionally equivalent, the Method Chaining pattern is more concise. And then there is another difference: The Method Chaining pattern naturally restricts you to do only object graph construction (anything else will not compile) and the IDE can easily guide you through this procedure with code completion. In contrast, there are no such constraints with the Lambda Expressions pattern. You may think this is an advantage of the Lambda Expressions pattern, but I consider this being a disadvantage: My argument is that the purpose is object construction here and anything else should be inhibited (e.g. compiling your claim for tax refunds).
So the winner is… (drum roll and fanfare): The Method Chaining pattern! (your mileage may vary)
Enjoy!