In this second part of the series I am going to look into the Nested Functions pattern to define an internal DSL for constructing object graphs.
On Nested Functions
Let’s look at the following example code from Mohamed Sanaulla’s excellent blog posting again:
Graph(
edge(from(a), to(b), weight(12.3),
edge(from(b), to(c), weight(10.5)
);
Among the three patterns to construct this object graph (Method Chaining, Nested Functions and Lambda Expressions), this is clearly the most concise expression. So if you are going to configure a large graph, then this might be your first choice. However, when compared to the Method Chaining pattern, there are some disadvantages, too. Consider this example:
Graph(
edge(to(b), from(a), weight(12.3),
edge(to(c), from(b), weight(10.5)
);
Note that I have simply permuted the order of the from(String lbl)
and to(String lbl)
functions here.
However, the compiler cannot infer my intention because both functions simply return a Vertex
and the semantics for
which is the source and which is the destination are solely defined by the parameter ordering of the
edge(Vertex from, Vertex to, Double weight)
function. This would result in a bug!
To fix this issue, you would have to create custom classes to explicitly express the intention, e.g. FromVertexHolder
and ToVertexHolder
.
However, this results in a huge bloat of code.
For example, you would have to add a method for each possible parameter permutation.
You can imagine - I will not go into the details here.
Another disadvantage is that you cannot easily provide some configurable context to the static functions without waiving
thread-safety (let’s ignore ThreadLocal
for now):
Imagine you wanted to provide a default value for the weight property of edges and you wanted this value to be
configurable.
You might come up with something like this:
public class NestedEdgeBuilder {
public static double DEFAULT_WEIGHT;
public static Edge edge(Vertex from, Vertex to) {
return edge(from, to, DEFAULT_WEIGHT);
}
...
}
To change the default weight, a client would simply have to update the value of the public field DEFAULT_WEIGHT (note that I didn’t bother to write a getter and a setter method). However, sharing mutable state is a bad idea when it comes to multithreading and hence should be avoided.
To fix this issue, you could put the default value in an instance field (and write a getter and a setter method for it) . However, then you loose the ease of use in the client:
NestedEdgeBuilder neb = new NestedEdgeBuilder();
neb.setDefaultWeight(1.0);
Graph(
neb.edge(to(b), from(a), weight(12.3),
neb.edge(to(c), from(b), weight(10.5)
);
Next, if you are going to fix the previous issue too, then you should skip this approach altogether and use one of the alternatives instead.
Conclusion
Using the Nested Functions pattern to define an internal DSL for constructing object graphs is a good choice if
- Being concise is an important matter, e.g. when constructing a large object graph.
- All parameters have different types so that no two can be accidentally swapped.
- You don’t need to provide some configurable context to the construction, e.g. default values.
Enjoy!