Using Step Builder Pattern in Swift
A Java-like approach to reduce the number of parameters passed to a method.
Although the Builder pattern is one of the less spread patterns on iOS (in fact, I cannot recall a single builder in Cocoa Touch or Core Foundation/Foundation), it is a well known pattern on Android, more specifically in Java.
In this article I want to explain the process I took to refactor a piece of code using the Step Builder pattern.
These days I’ve received the specification of a caching system to allow offline consult on iOS applications. It is very simple in terms of API: cache data, get the cached data, delete certain cached data and invalidate the whole cache; the usual methods for the simplest cache system. Additionally there are four more features:
- Every single cached data can be related to an optional given
id, to store different
datafor the same
- Every single cached data can be encrypted with an optional
- When caching data, an optional
keepAliveUntildate can be passed to mark the expiration date of the stored data.
- When getting data, an optional
ifBeforedate can be passed to retrieve the cached data only if it is not expired.
So, taking into account these requisites, as a first approach I came up with this design:
It is obvious that the number of parameters passed to the methods is huge. This leads to problems of testability and API consuming; indeed, as it is a Swift protocol, there is no possibility to set default parameters, consuming the method
niling parameters as needed (default parameters in protocols can be mimicked with a protocol extension but I think it is not very elegant).
I decided to refactor it so I begun to think and search for a solution, coming up to the following:
- Maybe it is a problem of design. Is it desirable to allow different
encryptionKeyin the same
OfflineControlleror is it better to instantiate a controller per
encryptionKeyand pass them to the initializer? That could be the solution but it creates another layer of complexity in order to manage several controllers.
- Take a look at SRP violations. I don’t think this is the case, at least, I don’t see any.
- Try to encapsulate related data in new types and pass an instance. This is clear in the classic example
foo(x: Double, y: Double)being converted to
foo(point: Point)but in this case I cannot identify anything that can be encapsulated, and it will be an explosion of types as each method has a different signature. It also translates the complexity to the consumer of the API, having to instantiate a concrete object per method.
- Create a parameter type and use a builder to populate it depending on the method to invoke. I think it obscures the API: how do I know I can send
cachebut it does not makes sense when invoking
I was a bit stuck, and I was beginning to think of using another approach like Swift
enums to try to model this differently. But then, an Android developer friend of mine told me about a pattern in Java, the Step Builder. As mentioned here:
One of the main Step Builder pattern benefits is providing the client with the guidelines on how your API should be used. […] this pattern is often referred to as a wizard for building objects.
As the Builder approach was the one more likely to fit my requisites, I think this is what I was looking for.
The main difference between the Step Builder and the regular Builder is that the Step Builder guides the API consumer in the process of constructing the object, that is, as you set a field, the returned builder meant to continue the construction process depends on the previous value set. As the process of building is guided through a finite path, there is no possibility of wrong constructions.
Take a look at how a regular Builder called
OfflineActionBuilder can be used for my purposes (obviating Swift closure builder this time):
That is a perfect building chain that returns a correct object, but this is possible too:
It does not makes sense to cache no
data or to pass
ifBefore date. When
build() is called, we will get a
nil object due to missing mandatory fields, but in terms of readibility this is not the best approach, and it could be complicated in terms of validation.
So let’s design the Step Builder for this offline system. First of all, we will list the features that our Step Builder has to have:
- It has to allow the building of
- It has to allow to
build()only when the object returned is going to be correct in terms of consistency and API consuming.
- Optional properties cannot be set until mandatory ones are populated.
Next, we are going to identify the path for each of the three actions:
cachehas to be built mandatorily with
data, and optionally with
requestbut doesn’t need
data, and optional fields are
requestas mandatory, with
encryptionKeyas optional fields.
It is important to notice again that the optional fields can only be set after setting the mandatory ones.
Less words, show me the code! First, we write the object to be built:
A pretty simple DTO.
Now let’s define the protocols for the steps of the builder.
Let’s describe each one:
BuildStepspeaks by itself. It is the step responsible of building the final object.
RequestStepis the entry point to the building process. It exposes three methods, one per type of action we want to create, and depending on each method, it returns the builder casted to the next step.
DataStephas to populate
dataand returns the next step
CommonsStepis responsible to populate
CacheCommonsStepis responsible of populating
keepAliveUntilif constructing a
GetCommonsStepis responsible of populating
ifBeforeif constructing a
GetCommonsStep inherits from
CommonsStep as the three steps populate optional fields. In addition to that,
CommonsStep also inherits from
BuildStep as it is the only step that allows the building of the object. So, given this protocols hierarchy, all three
build() a correct object.
The implementation is pretty straightforward, each method sets its fields and returns the next step.
Wrapping up, this is how the final solution looks like:
Let’s see some examples of how it can be used for each action type:
It looks like a regular Builder but you will see it in action while typing in Xcode. For example, if you begin the building of the action
.toCache(_:), the next step will be
.data(_:), and after that you could populate the rest of the properties allowed in a
On the other hand, right after you begin to build a
.toDelete(_:) action, you could call
build() because there are no more mandatory fields to populate.
Let’s see it:
OfflineController protocol looks like this, and the implementer is the responsible of handling the passed
OfflineAction to dispatch it properly:
You can play with this example in this playground.
I think this is a smart approach to this kind of problem but, as everything, it has pros and cons.
- The API consumer is guided in the construction of a correct object with no possibility to build wrong ones.
build()can only be called when the resultant object is going to be in a consistent state.
- You cannot set optional properties until mandatory ones have been set.
- There is no need of validation when calling
- The implementation of the Step Builder is not trivial. If the object to be constructed is complicated in terms of paths, it could be difficult to identify each
Stepto make a proper hierarchy balancing readibility and not repeated code.
- It needs fine adjusting to get it perfect in terms of encapsulation, and it could be a bit difficult to read.
In summary, the Step Builder comes to fill the lacks of the common Builder pattern in terms of valid construction chain and readability. It could be a good new resource in your toolbox.
If you are interested in reading more, here there are some resources:
When building an API, you should always think about who is going to use it. When the API is simply and clear to use…www.javacodegeeks.com
I have recently decided to use Amazon SES API in order to be able to send emails to my Microservices Weekly subscribers…www.svlada.com
Here are my notes and favourite quotes from chapter 3, "Functions", of one of my all-time favourite books: Clean Code…www.itiseezee.com
We join "The Craftsman," Robert C. Martin's series on an interstellar spacecraft where programmers hone their coding…www.informit.com
Thanks for reading and comment!