Over the past few months I've spent a fair bit of time on a project using my MacBook Pro for development. This got me to run CCMenu again and, perhaps predictably, made me work on that in the evenings. While doing some long overdue refactorings I came across the need to construct domain objects in unit tests. So far I had used simple helper methods in the unit test itself, but the Java/Scala project I was on during the day made heavy use of the Builder pattern with fluent interfaces, and that got me thinking.
In the end I've come up with three variations of the Builder pattern in Objective-C, all of which have their pros and cons. In this post I want to show these patterns and invite comments on which one you prefer.
Before going into the detail, though, let's agree on what we're trying to build. It doesn't really matter, so let's pick cars, with properties such as make and model, plus the name of the current owner. To make it a bit harder, let's make the make and model properties immutable. The interface looks like this:
Classic Java style
A direct translation of the classic Java builder pattern would give us a class something like the following. Note that the builder provides defaults for the values in case they are not set.
The usage is straight-forward:
What I like about this approach is its simplicity. What's not so great is that the calls to the builder are deeply nested. In addition, in the builder we have to declare several variables and, if we cared about not leaking memory in tests, we'd have to add even more code to handle the assignments.
By the way, the use of a dictionary for the mutable properties makes not much sense in this case. It shines once you have more than a couple of properties as it reduces the number of variables and the number of calls in the build method. This is why I've shown this little pattern here.
Objective-C has categories and that opens up a different possibility for writing builders. Instead of building up the state in a builder object in order to create the real object at the end, we create the object upfront and then set the properties on it, using a category. The code looks like this:
The usage is near idential, with the invocation of car at the beginning of the chain replacing the call to build at the end.
An advantage of this solution is that the builder class is just one method long and doesn't grow as we add more properties. In fact, if we don't care about defaults, this style would work without any builder class at all. We also don't need duplicate variables for holding on to the values and, lastly, by using category methods the builder code lives inside the object and thus we have more liberty to access and manipulate the internal structures. This can be good and bad, I guess.
Looking for a solution that doesn't touch the Car class, that avoids deep nesting of method calls during the build, and that scales well with regards to the builder code size in relation to the number of attributes, I turned to reflection.
Starting with the usage, this builder is used as follows:
It is possible to give the properties in different sequences and to leave off properties:
In fact, the car prefix on the method is syntactic sugar and could be left off, too.
The implementation of this builder is as follows:
While more complex than the other ones, the implementation is pretty generic and most of the code could be moved into a generic builder base class. It is also fairly short and requires substantially less code to be added for additional properties than the other options.
Sounds perfect? There is one big drawback: the selector changes all the time and usually it would be unreasonable to declare all permutations, which means that Xcode and the compiler show warnings for every use of the builder. If anyone knows how to turn off that warning on a per-file basis...
The following table summarises the three approaches. What do you think?
|Classic Java style||Using categories||Using reflection|
|Lines of code||42||33||36|
|Lines of code per property||~8 *||~6 *||0 **|
|Advantages||Simple||Short, internal access||Generic, very short|
|Drawback||Lots of code||Changes object||Compiler warning|
* worse if we are strict about memory leaks
** if we break lines for formatting: 1-2