Builder As A (Fail-Fast) State Machine
This is an idea that came to me a few weeks ago while designing a “Generator” class that had to send the input to an encapsulated Writer
. It was, in fact, the Builder pattern. However, the rules were a bit more complex, the user had to call the add...()
methods in a certain way, for the output to be generated correctly.
Needless to say, I didn’t like the option of having one single BuilderImpl
class that would set and verify all sorts of flags internally, in order to know what and when it was allowed to do. The solution was to build a Finite State Machine, since the builder’s interface was fluent. As usual, in this post I’ll illustrate it all with an example.
Let’s assume we want to implement a DateBuilder
that would generate a String
in the classic dd.mm.yyyy
format (maybe with other types of separators as well, not only .
). For the sake of simplicity, we’ll focus only on format and forget cases such as number of days in a month, leap years etc. First comes the interface:
public interface DateBuilder { DateBuilder addDay(final Integer day); DateBuilder addMonth(final Integer month); DateBuilder addYear(final Integer year); DateBuilder addSeparator(final String sep); String build(); }
The interface above will have five implementations: StringDateBuilder
(the public entry point), ExpectSeparator
, ExpectMonth
, ExpectYear
and ExpectBuild
(these four are package protected, invisible to the user). StringDataBuilder
looks like this:
public final class StringDateBuilder implements DateBuilder { private final StringBuilder date = new StringBuilder(); @Override public DateBuilder addDay(final Integer day) { this.date.append(String.valueOf(day)); return new ExpectSeparator(this.date); } @Override public DateBuilder addMonth(final Integer month) { throw new UnsupportedOperationException( "A day is expected first! Use #addDay!" ); } @Override public DateBuilder addYear(final Integer year) { throw new UnsupportedOperationException( "A day is expected first! Use #addDay!" ); } @Override public DateBuilder addSeparator(final String sep) { throw new UnsupportedOperationException( "A day is expected first! Use #addDay!" ); } @Override public String build() { throw new UnsupportedOperationException( "Nothing to build yet! Use #addDay!" ); } }
I’m sure you get the point already: the other four implementations will handle their own situations. For instance, ExpectSeparator
will throw an exception from all methods except addSeparator(...)
, where it will append the separator to the StringBuilder
and return an instance of ExpectMonth
. Finally, the last node of this machine will be ExpectBuild
(returned by ExpectYear
after adding the year), which will throw exceptions from all methods besides build()
.
This design helped me keep my code objects small, free of flags and if/else
forks. As usual, each of the classes above are easily tested and the builder’s behaviour is easily changeable by switching the returned implementations.
Of course, I am not the only one with these thoughts: mr. Nicolas Fränkel wrote about this very idea just last month here. However, I felt the need to bring my two cents because I did not like his example entirely: he used different interfaces for the builder’s nodes in an attempt to keep the builder safe and idiot-proof (e.g. don’t even allow the user to see an addMonth
or build
method if they shouldn’t use it). This is something I don’t agree with because it means even more code for me to manage and besides, the client will be more coupled with the builder’s logic. I’d rather just force the user into learning how to use the builder (it shouldn’t be a big effort for them, since they’re supposed to catch any exceptions with the simplest of unit tests, right? right…)
I found this article too, which offers a broader, more theoretical explanation, not necessarily tied to the Builder pattern – if you think about it, this approach could be used with any kind of object that has to change its behaviour based on its internal state.
Published on Java Code Geeks with permission by MIhai Andronache, partner at our JCG program. See the original article here: Builder As A (Fail-Fast) State Machine Opinions expressed by Java Code Geeks contributors are their own. |
That is a good starting point. For dummies, I propose you to describe an AbstractDateBuilder where all methods throw UnsupportedOperationException and document at least first two implementations providing only addDay and then addMonth…
It is a pretty good solution, but regarding the UnsulportedOperationExceptions, I still prefer validating/controllong API usage in compile-time over runtime whenever possible.
APIs amount grows in an incredible pace and it’s way easier to learn a new one, when it simply disallows you making a mistake like calling unsupported methods. That’s just my point of view based on my personal experience, though.
I know its abstract example. But cant find real example for this case. I mean, there is always way to collect all string and concanate them in build. If it is clean builder pattern. No matter if object would be string or larger structure.
I am using fail fast in builder, but only when field is mandatory. And two chcecks, one in ‘withField’ and then in ‘build’.
Yes, the example is obviously too abstract/simple, people didn’t like it too much. But here’s a concrete example (when you cannot change the Builder’s interface):
Reference Implementation of JSON-P’s JsonObject generator/builder: https://github.com/javaee/jsonp/blob/master/impl/src/main/java/org/glassfish/json/JsonGeneratorImpl.java
And here is my implementation of the same builder: https://github.com/amihaiemil/eo-jsonp-impl/blob/master/src/main/java/com/amihaiemil/eojsonp/RtJsonGenerator.java