Wednesday, July 6, 2022

Creational Design Patterns # Builder

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.



 

Problem

Imagine a complex object that requires laborious, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried inside a monstrous constructor with lots of parameters. Or even worse: scattered all over the client code


For example, let’s think about how to create a House object. To build a simple house, you need to construct four walls and a floor, install a door, fit a pair of windows, and build a roof. But what if you want a bigger, brighter house, with a backyard and other goodies (like a heating system, plumbing, and electrical wiring)?

The simplest solution is to extend the base House class and create a set of subclasses to cover all combinations of the parameters. But eventually you’ll end up with a considerable number of subclasses. Any new parameter, such as the porch style, will require growing this hierarchy even more.

There’s another approach that doesn’t involve breeding subclasses. You can create a giant constructor right in the base House class with all possible parameters that control the house object. While this approach indeed eliminates the need for subclasses, it creates another problem.


In most cases most of the parameters will be unused, making the constructor calls pretty ugly. For instance, only a fraction of houses have swimming pools, so the parameters related to swimming pools will be useless nine times out of ten.

Solution

The Builder pattern suggests that you extract the object construction code out of its own class and move it to separate objects called builders.



The pattern organizes object construction into a set of steps (buildWallsbuildDoor, etc.). To create an object, you execute a series of these steps on a builder object. The important part is that you don’t need to call all of the steps. You can call only those steps that are necessary for producing a particular configuration of an object.

Some of the construction steps might require different implementation when you need to build various representations of the product. For example, walls of a cabin may be built of wood, but the castle walls must be built with stone.

In this case, you can create several different builder classes that implement the same set of building steps, but in a different manner. Then you can use these builders in the construction process (i.e., an ordered set of calls to the building steps) to produce different kinds of objects.



For example, imagine a builder that builds everything from wood and glass, a second one that builds everything with stone and iron and a third one that uses gold and diamonds.

By calling the same set of steps, you get a regular house from the first builder, a small castle from the second and a palace from the third. 

However, this would only work if the client code that calls the building steps is able to interact with builders using a common interface.

Structure

Pros and Cons




Here is the sample builder pattern example code where we have a Computer class and ComputerBuilder class to build it.

public class Computer { //required parameters private String HDD; private String RAM; //optional parameters private boolean isGraphicsCardEnabled; private boolean isBluetoothEnabled; public String getHDD() { return HDD; } public String getRAM() { return RAM; } public boolean isGraphicsCardEnabled() { return isGraphicsCardEnabled; } public boolean isBluetoothEnabled() { return isBluetoothEnabled; } private Computer(ComputerBuilder builder) { this.HDD=builder.HDD; this.RAM=builder.RAM; this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled; this.isBluetoothEnabled=builder.isBluetoothEnabled; } //Builder Class public static class ComputerBuilder{ // required parameters private String HDD; private String RAM; // optional parameters private boolean isGraphicsCardEnabled; private boolean isBluetoothEnabled; public ComputerBuilder(String hdd, String ram){ this.HDD=hdd; this.RAM=ram; } public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) { this.isGraphicsCardEnabled = isGraphicsCardEnabled; return this; } public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) { this.isBluetoothEnabled = isBluetoothEnabled; return this; } public Computer build(){ return new Computer(this); } } }

Notice that Computer class has only getter methods and no public constructor. So the only way to get a Computer object is through the ComputerBuilder class.

Here is a builder pattern example test program showing how to use Builder class to get the object.

public class TestBuilderPattern { public static void main(String[] args) { //Using builder to get the object in a single line of code and //without any inconsistent state or arguments management issues Computer comp = new Computer.ComputerBuilder( "500 GB", "2 GB").setBluetoothEnabled(true) .setGraphicsCardEnabled(true).build(); } }

Follow on LinkedIn

You may also like

Kubernetes AWS Java Coding Question
Microservices Core Java Spring Boot
Spring Framework Kafka Miscellaneous