The builder pattern
Unlike the abstract factory pattern, which will produce parts of products of the same family, the builder pattern will help us build the finalized product that consists of several parts.
Roles
The main purpose of the builder pattern is to abstract the building of complex objects from its actual construction. Having the same construction process can create different representations of the product.
This pattern can be used when:
- A client needs to construct complex objects without having to know its implementation
- A client needs to construct complex objects that need to have several implementations or representations
Design
The following figure shows the generic UML class diagram of the builder pattern:
Participants
This pattern is quite simple as it has only a few participants:
Director
: This class constructs the product using the interface of theAbstractBuilder
class.AbstractBuilder
: This class defines the method signature that allows the construction of all the parts of the product, and it contains a signature of a method that returns the product once this is built.ConcreteBuilder
: This is theConcrete
class that implements the method of theAbstractBuilder
class.Product
: This is the finalized product. The product contains all the parts of the watch.
Collaborations
The client creates the ConcreteBuilder
and Director
classes. The Director
class will then build an object if the client asks him to do so by invoking the constructor and returns the finalized product to the client.
Illustration
Using the AbstractFactory
method, we can use the builder pattern to build a watch. As we've seen that a watch has several parts: a dial and band. A watch can have two sizes too, and as we have already seen, the representation of the dial or band depends on the size of the watch too.
Implementation
If we want to build some watches that are represented with a dial and band, we will define a Director
class that will define the construction order of all the parts of our watches and return the finalized watch to the client.
The Director
class will call all the constructors who are in charge to construct one part of the watch. To implement this, we will reuse the existing code of the abstract factory pattern and add the following code.
Open the Builder.playground
file in Xcode to see the added code at the bottom of the file:
//Our builder1 class BuilderGoldMilanese38mmWatch: AbstractWatchBuilder { override func buildDial() { watch.band = MilaneseBand(size: BandSize.SM) } override func buildBand() { watch.dial = GoldDial(size: WatchSize._38mm) } } //Our builder2 class BuilderAluminiumSportand42mmWatch:AbstractWatchBuilder { override func buildDial() { watch.band = SportBand(size: BandSize.ML) } override func buildBand() { watch.dial = AluminiumDial(size: WatchSize._42mm) } } //our Director class class Director { var builder: AbstractWatchBuilder? init(){ } func buildWatch(builder: AbstractWatchBuilder){ builder.buildBand() builder.buildDial() } }
Usage
To simulate our client, we will tell our director to create two watches:
- A 42 mm aluminium dial with a sports band
- A 38 mm gold dial with a milanese band
The code for the example is as follows:
//We will build 2 Watches : //First is the Aluminium Dial of 42mm with Sport Band let director = Director() var b1 = BuilderAluminiumSportand42mmWatch() director.buildWatch(b1) // our watch 1 var w1 = b1.getResult() w1.band?.color w1.band?.type.rawValue w1.band?.size.rawValue w1.dial?.size.rawValue w1.dial?.material.rawValue //Our 2nd watch is a Gold 38mm Dial with Milanese Band var b2 = BuilderGoldMilanese38mmWatch () director.buildWatch(b2) // Our watch 1 var w2 = b2.getResult() w2.band?.color w2.band?.type.rawValue w2.band?.size.rawValue w2.dial?.size.rawValue w2.dial?.material.rawValue
The result is shown in Playground like this:
Note
Swift allows the use of closure that simplifies the creation of our complex objects. Regarding the example that we provided earlier, we can write the following code to build our two watches.
Implementation using closures
Here, we don't need to use the Director
and ConcreteBuilder
classes. Instead, we will tell our Watch
class that the builder will be in the closure.
In the previous example, remove the Director
, AbstractBuilder
, and ConcreteBuilder
classes.
We just need to write the Watch
class, as shown in the following code (you can find the following code in the BuilderClosures.playground
file accompanying this chapter):
//our Product Class : a Watch //The builder will be in the closure class Watch{ var dial:IWatchDial? var band:IWatchBand? typealias buildWatchClosure = (Watch) -> Void init(build:buildWatchClosure){ build(self) } }
Then, to simulate our client, we can write the following code which will call the appropriate constructor assigned to the band or dial property of the Watch
object:
//Simulate our clients let Gold42mmMilaneseWatch = Watch(build: { $0.band = MilaneseBand(size: BandSize.ML) $0.dial = GoldDial(size: WatchSize._42mm) })
The result is as follows: