Flutter Design Patterns: 18 — Builder
An overview of the Builder design pattern and its implementation in Dart and Flutter
Previously in the series, I have analysed a relatively complex, but very practical and useful structural design pattern — Bridge. This time I would like to represent a design pattern, which divides the construction of a complex object into several separate steps. It is a creational design pattern called Builder.
Table of Contents
- What is the Builder design pattern?
- Other articles in this series
- Your contribution
What is the Builder design pattern?
Builder is a creational design pattern, which intention in the GoF book is described like this:
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
The intention could be split into two parts:
Separate the construction of a complex object from its representation…
You should have already noticed this in the majority of the design patterns overviewed in the series, maybe in a slightly different form, e.g. separate the abstraction from its representation. In this case, one of the main purposes of the Builder design pattern is to separate the creation process (logic) of a complex object from the object (data) itself. What is a complex object? Well, there is no specific point or any criteria of the object when you could say that it is complex. Usually, an object could be considered as complex when the object’s creation does not simply end with calling its constructor — additionally, you should set some extra specific parameters, call additional methods.
Ok, at this point we have a complex object, we can create it by using some additional parameters and/or methods — why we need any additional abstraction on top of that, why we should separate this creation process from the object at all?
… so that the same construction process can create different representations.
Ahh, that’s the point! To understand it better, let’s say we are building a house. To build a house (object), the construction steps (build logic) are pretty much the same — you need the foundation, floor, some walls, doors, windows, a roof, etc. Even though the construction process of the house is the same, each of these steps could be adjusted, hence the final result would look completely different. And that is the main idea of the Builder design pattern — abstract the object’s creation process so that constructions steps could be adjusted to provide a different representation (final result).
The Builder design pattern moves the object construction code out of its own class to separate objects called builders. Each of these builders follows the same interface and implements separate object’s construction steps. That is, if you want a different object’s representation, just create a different builder class and implement these construction steps correspondingly. Also, there is one additional layer in the Builder design pattern — Director. The Director is a simple class which is aware of the Builder interface and defines the order in which to execute the building steps. This class is not mandatory, though, but it hides the details of product construction from the client code.
I know, the structure of the Builder design pattern is quite complex, so let’s move to the analysis and implementation parts to understand it better!
The general structure of the Builder design pattern looks like this:
- Builder — defines an abstract interface that is common to all types of builders for creating parts of a Product;
- Concrete Builder — provides a specific implementation of the construction steps. Also, it defines and keeps track of the Product it creates;
- Director — constructs an object using the Builder interface, defines the order in which the construction steps are called;
- Product — represents the complex object under construction, exposes interface/methods for assembling the parts into the final result;
- Client — associates the specific Builder object with the Director. Later, a Product object is created by calling the Director class instance.
The Builder design pattern should be used when you notice multiple constructors of the same class referencing each other. For instance, you have a constructor with multiple optional parameters. Some of these parameters have default values, so you create several shorter constructors with fewer parameters, but still refer to the main one. By using the Builder design pattern, you are building objects step by step only using those steps that are really needed — you do not need to cope with the problem of multiple constructors with optional parameters anymore.
As already mentioned, this pattern should be used when you want to create different representations of some product. That is, the pattern could be applied when the construction steps are similar, but they differ in the details. The builder interface defines those steps (some of them may even have the default implementation) while concrete builders implement these steps to construct a particular representation of the product.
Finally, the Builder design pattern is useful when the algorithm for creating a complex object should be independent of the parts that make up the objects and how they are assembled. In simple words, it is just a simple extraction of the object’s creation logic from its own class. Therefore, the construction algorithm could evolve separately from the actual product it provides, the modification of this process does require changing the object’s code.
This time, the implementation part is very straightforward — we will use the Builder design pattern to implement the build process of McDonald’s burgers.
As you may know, McDonald’s menu contains multiple burgers (regular burger, cheeseburger, Big Mac just to name a few). All of these burgers use the very same products, just the ingredients list is different:
- Regular burger — buns, beef patty, ketchup, mustard, grill seasoning, onions, pickle slices;
- Cheeseburger — buns, beef patty, cheese, ketchup, mustard, grill seasoning, onions, pickle slices;
- Big Mac — buns, cheese, beef patty, Big Mac sauce, grill seasoning, onions, pickle slices, shredded lettuce.
As you can see, by changing the build process of the burger (changing ingredients), we completely change the final result. Also, at any moment there could be a requirement to add a new burger to the menu. Finally, the user-friendly UI should be implemented where you can select a burger from the menu and see its price, ingredients and allergens list.
For this problem, the Builder design pattern is a great option since we can define different builder classes which build specific burgers. Also, if a new burger should be added to the menu at any point, we can simply introduce another builder class to cope with this change. Let’s check the class diagram first and then implement the pattern.
The class diagram below shows the implementation of the Builder design pattern:
The Ingredient is an abstract class which is used as a base class for all the ingredient classes. The class contains allergens and name properties as well as getAllergens() and getName() methods to return the values of these properties.
There are a lot of concrete ingredient classes: BigMacBun, RegularBun, BeefPatty, McChickenPatty, BigMacSauce, Ketchup, Mayonnaise, Mustard, Onions, PickleSlices, ShreddedLettuce, Cheese and GrillSeasoning. All of these classes represent a specific ingredient of a burger and contains a default constructor to set the allergens and name property values of the base class.
Burger is a simple class representing the product of a builder. It contains the ingredients list and price property to store the corresponding values. Also, the class contains several methods:
- addIngredient() — adds an ingredient to the burger;
- getFormattedIngredients() — returns a formatted ingredients’ list of a burger (separated by commas);
- getFormattedAllergens() — returns a formatted allergens’ list of a burger (separated by commas);
- getFormattedPrice() — returns a formatted price of a burger;
- setPrice() — sets the price for the burger.
BurgerBuilderBase is an abstract class which is used as a base class for all the burger builder classes. It contains burger and price properties to store the final product — burger — and its price correspondingly. Additionally, the class stores some methods with default implementation:
- createBurger() — initialises a Burger class object;
- getBurger() — returns the built burger result;
- setBurgerPrice() — sets the price for the burger object.
BurgerBuilderBase also contain several abstract methods which must be implemented in the specific implementation classes of the burger builder: addBuns(), addCheese(), addPatties(), addSauces(), addSeasoning() and addVegetables().
BigMacBuilder, CheeseburgerBuilder, HamburgerBuilder and McChickenBuilder are concrete builder classes which extend the abstract class BurgerBuilderBase and implement its abstract methods.
BurgerMaker is director class which manages the burger’s build process. It contains a specific builder implementation as a burgerBuilder property, prepareBurger() method to build the burger and a getBurger() method to return it. Also, the builder’s implementation could be changed using the changeBurgerBuilder() method.
BuilderExample initialises and contains the BurgerMaker class. Also, it references all the specific burger builders which could be changed at run-time using the UI dropdown selection.
An abstract class which stores the allergens, name fields and is extended by all of the ingredient classes.
All of these classes represent a specific ingredient by extending the Ingredient class and specifying an allergens’ list as well as the name value.
- Big Mac Bun
- Regular Bun
- Grill Seasoning
- Beef Patty
- McChicken Patty
- Big Mac Sauce
- Pickle Slices
- Shredded Lettuce
A simple class to store information about the burger: its price and a list of ingredients it contains. Also, class methods, such as getFormattedIngredients(), getFormattedAllergens() and getFormattedPrice(), returns these values in human-readable format.
An abstract class which stores burger and price properties, defines some default methods to create/return the burger object and set its price. Also, the class defines several abstract methods which must be implemented by the derived burger builder classes.
- BigMacBuilder — builds a Big Mac using the following ingredients: BigMacBun, Cheese, BeefPatty, BigMacSauce, GrillSeasoning, Onions, PickleSlices and ShreddedLettuce.
- CheeseburgerBuilder — builds a cheeseburger using the following ingredients: RegularBun, Cheese, BeefPatty, Ketchup, Mustard, GrillSeasoning, Onions and PickleSlices.
- HamburgerBuilder — builds a cheeseburger using the following ingredients: RegularBun, BeefPatty, Ketchup, Mustard, GrillSeasoning, Onions and PickleSlices. AddCheese() method is not relevant for this builder, hence the implementation is not provided (skipped).
- McChickenBuilder — builds a cheeseburger using the following ingredients: RegularBun, McChickenPatty, Mayonnaise and ShreddedLettuce. AddCheese() and addSeasoning() methods are not relevant for this builder, hence the implementation is not provided (skipped).
A director class which manages the burger’s build process and returns the build result. A specific implementation of the builder is injected into the class via constructor.
First of all, a markdown file is prepared and provided as a pattern’s description:
BuilderExample initialises and contains the BurgerMaker class object. Also, it contains a list of BurgerMenuItem objects/selection items which is used to select the specific builder using UI.
The director class BurgerMaker does not care about the specific implementation of the builder — the specific implementation could be changed at run-time, hence providing a different result. Also, this kind of implementation allows easily adding a new builder (as long as it extends the BurgerBuilderBase class) to provide another different product’s representation without breaking the existing code.
As you can see in the example when a specific builder is selected from the dropdown list, a new product (burger) is created and its information is provided in the UI — price, ingredients and allergens.
All of the code changes for the Builder design pattern and its example implementation could be found here.
Other articles in this series
👏 Press the clap button below to show your support and motivate me to write better!
💬 Leave a response to this article by providing your insights, comments or wishes for the series.
📢 Share this article with your friends, colleagues in social media.
➕ Follow me on Medium.
⭐ Star the Github repository.