Builder Pattern with a Twist: Design Patterns
Builder pattern as we see is one of the most powerful creational of design patterns of all times. It is used in many instances where factory pattern does not suffice the purpose. This pattern has many advantages over the factory and the abstract factory pattern. Yet I always thought there could be more with this beautiful pattern.
One such thing I encountered that the builder pattern works pretty smooth in most of the places but what if we need to have a systematic flow of attributes?
What if we can eliminate the use of if-else
conditions by simply tweaking the way this pattern is constructed.
Well, I could somehow by references and trials could find the answer to these questions upto some level and hope to find better solutions soon.
What drawbacks can we see with Builder pattern ?
- All the mandatory attributes are prescribed to be passed into the constructor. This thought may tend to increase the constructor size while creating an instance of the Builder from the client code.
- There is no hierarchy of attribute addition when it comes to mandatory or optional attributes.
- We do not any diversion or nested condition that can be used over this existing design and thus eliminate need of if-else condition.
Keeping these conditions in mind, let us try to design the same problem again as we discussed in our Basic Builder pattern post.
The Problem:
We are at our favourite fast food centre, Subway. They serve delicious sub with amazing veggies and flavoured sauces. Let us look at the problem closely and understand how they serve their customers and how the new Builder pattern with the Twist can be beneficial in this scenario.
The attendant asks you fixed set of basic questions in order:
- Bread (which one)
- Toast (yes or no)
- Type (Veg or NonVeg)
Next based on the Type you just said it asks the following:
- If NonVeg chosen:
-
- Chicken / Mutton
- Vegetables (Onion / Tomato/ Olives)
- If Veg Chosen:
-
- Vegetables (Onion / Tomato/ Olives)
Finally he asks if you want to pamper your tastebuds with cheese. And we usually reply with a yes/no and also tell the type of cheese you want.
The problem now has become really interesting. We have a set of mandatory attributes that is absolutely necessary to start preparing the sub. Next we have optional items that will be added to the dish but at the same time can have single or multiple options.
For example:
If we opt for a NonVeg sub, then we can add either chicken or mutton to it but not both. Moreover after adding our favourite NonVeg add-on, we can add a series of veggies on top of it.
But at the same time, if we opt for a Veg sub, then only the veggies should be added and not the NonVeg add-ons.
With our traditional design of Builder, we did not have the separation of concern at compile time for these scenarios. We could do the separation by creating two builders, one for Veg and one for NonVeg. If we stick with both in one builder then we could add chicken to a veg sub as well, which would be wrong.
To solve this conflict under the same builder, we can design our code in the following way where we can separate out the Mandatory, Conditional, Optional elements or attributes from our builder.
To our advantage, this new design will ensure that you never make any mistakes in coding on the client code because the flow if broken, the code will not compile and give you time to fix it thus avoiding any major crash that may occur at runtime.
Implementation in Java:
We will try to code this new design twist in Java and call it simply in a client code. The code is self explanatory. If you do not understand it, please refer to the basic Builder pattern here.
Segregation of helper functions by interfaces
We will need some interfaces with the functions used in our Builder to add the attributes.
Each function will return the instance of the same or next sequenced interface depending on the flow and optional attributes required to build the object.
Helper Interaces:
Builder class:
These interfaces would be implemented by our Builder class and would behave the exact same way it was as the basic Builder pattern.
Client Code:
Now finally our Client code (Main function) can call and construct this Builder in the following ways:
All the project files are available in the GitHub repository here.
Try this simple example in any smart IDE (IntelliJ IDEA) where we get autocomplete and suggestions while building the object. We can observe that on each attribute added, we see only limited functions that can be called that are appropriate at that point of construction.
Any method that does not follow the flow will give a compilation error directly.
Let us look at some screenshots taken while coding in IntelliJ IDEA.
The client side code is being written to construct the Builder Subway object. Notice there are only few functions that you can call at each instance of the building cycle. Also based on the function that we actually called in the builder, the next set of available functions pops up thus making a systematic flow. The behaviour mimics the real world scenario of ordered question answer pattern. This helps to create much efficient and bug free client side code.