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 in 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 data for the same request.
  • Every single cached data can be encrypted with an optional encryptionKey.
  • When caching data, an optional keepAliveUntil date can be passed to mark the expiration date of the stored data.
  • When getting data, an optional ifBefore date 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:

  1. Maybe it is a problem of design. Is it desirable to allow different id or encryptionKey in the same OfflineController or is it better to instantiate a controller per id and encryptionKey and pass them to the initializer? That could be the solution but it creates another layer of complexity ir order to manage several controllers.
  2. Take a look at SRP violations. I don’t think this is the case, at least, I don’t see any.
  3. 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.
  4. 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 keepAliveUntil when executing cache but it does not makes sense when invoking get or delete?

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:

  1. It has to allow the building of cache, get, and delete actions.
  2. It has to allow to build() only when the object returned is going to be correct in terms of consistency and API consuming.
  3. Optional properties cannot be set until mandatory ones are populated.

Next, we are going to identify the path for each of the three actions:

  • cache has to be built mandatorily with request and data, and optionally with id, encryptionKey and keepAliveUntil.
  • get needs request but doesn’t need data, and optional fields are id, encryptionKey and ifBefore.
  • delete only needs request as mandatory, with id and encryptionKey as 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:

  • BuildStep speaks by itself. It is the step responsible of building the final object.
  • RequestStep is 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.
  • DataStep has to populate data and returns the next step CacheCommonsStep.
  • CommonsStep is responsible to populate id and encryptionKey.
  • CacheCommonsStep is responsible of populating keepAliveUntil if constructing a cache action.
  • GetCommonsStep is responsible of populating ifBefore if constructing a get action.

Both CacheCommonsStep and 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 CacheCommonsStep, GetCommonsStep and CommonsStep can 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 cache action.

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:

Now, the 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.

Pros

  • 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 build().

Cons

  • 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 Step to 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:

Thanks for reading and comment!