Welcome back! In this lecture, we’re diving into the realm of the Builder design pattern in the context of functional programming. The Builder pattern is a powerful tool that provides flexibility in the creation of complex objects. In situations where objects require numerous arguments for construction or a plethora of setters, the Builder pattern emerges as a flexible solution. Furthermore, it ensures the immutability of objects after their creation, enhancing code robustness.
Table of contents
Open Table of contents
Sections
The Essence of the Builder Pattern
The Builder pattern brings simplicity and control to the process of object creation. It’s particularly useful when dealing with complex object structures and a multitude of construction parameters. This pattern tackles the challenges of managing constructors overloaded with different parameter combinations or managing excessive setter methods, while also enforcing immutability once the object is constructed.
In the world of functional programming, we harness the power of higher-order functions and lambdas to elegantly implement the Builder pattern. By doing so, we adhere to the principles of functional programming, such as immutability and declarative programming.
Deciphering the UML Diagram
Before we delve into the functional implementation of the Builder pattern, let’s take a moment to revisit the UML diagram that represents the pattern:
In this diagram, the “Product” signifies the complex object that will be created. The “Builder” defines the steps required to correctly construct the product. There can be multiple concrete builders, each designed to create a particular variant of the complex product. The “Director” class orchestrates the creation algorithm, specifying the concrete builder to be used and invoking the necessary methods in the correct sequence. The final product is then obtained by invoking the “getCompleteObject” (or “getCompleteProduct”) method.
Functional Implementation of the Builder Pattern
Now, let’s put theory into practice and explore a functional programming example of the Builder pattern. Imagine we’re constructing mobile devices, each with various attributes such as RAM, storage, battery, camera, processor, and screen size. To maintain immutability and streamline the object creation process, we’ll employ the Builder pattern.
Here’s the code example:
class Mobile {
private final String RAM;
private final String storage;
private final String battery;
private final String camera;
private final String processor;
private final String screenSize;
public Mobile(MobileBuilder builder) {
this.RAM = builder.RAM;
this.storage = builder.storage;
this.battery = builder.battery;
this.camera = builder.camera;
this.processor = builder.processor;
this.screenSize = builder.screenSize;
}
// Getters and toString method
}
class MobileBuilder {
String RAM = "2GB"; // Default value
String storage = "16GB"; // Default value
String battery = "3000mAh"; // Default value
String camera = "8MP"; // Default value
String processor = "Snapdragon"; // Default value
String screenSize = "5 inches"; // Default value
public MobileBuilder with(Consumer<MobileBuilder> buildFields) {
buildFields.accept(this);
return this;
}
public Mobile createMobile() {
return new Mobile(this);
}
}
public class BuilderPatternDemo {
public static void main(String[] args) {
MobileBuilder builder = new MobileBuilder();
Mobile myMobile = builder
.with(mb -> {
mb.RAM = "4GB";
mb.battery = "4000mAh";
mb.processor = "A12 Bionic";
})
.createMobile();
System.out.println(myMobile);
}
}
In this example, the Mobile class represents our product, with its attributes defined in a constructor. We’ve made the attributes final to ensure immutability. The MobileBuilder class encapsulates the creation process. Using a higher-order function called with, we set the values of the attributes provided by the client. The createMobile method constructs the final mobile object.
Wrapping Up
The Builder pattern offers a graceful solution to the challenges of object creation, especially in the presence of numerous attributes or constructor variations. In the functional programming paradigm, we’ve harnessed the power of higher-order functions to implement the Builder pattern elegantly and immutably. This pattern not only ensures code readability but also enhances the maintainability and scalability of applications.
In our next session, we’ll explore another intriguing pattern in functional programming. Until then, keep coding and experimenting!
You can find the repo for this section of the course Here