Builder Design Pattern
Design created using M365 Designer

Builder Design Pattern

The builder pattern is a popular design pattern that is used to separate the construction of an object from its representation. It is particularly useful when the construction process is complex, or when there are multiple ways to create an object. This can be the case when dealing with complex objects that have a large number of attributes or configurations. Consider an example of a party planning application where users can plan their new year parties. Now all parties have some common attributes - location, time and occasion. However some parties can have additional attributes like theme, dresscode, food options, *cough* booze options *cough* etc. In such a scenario, it is important to have a way to create these complex objects in an organized and flexible way. The builder pattern can help by providing a clear and concise way to construct the object, allowing for better code reuse and flexibility.

Approach #1

One way to create the party planning application is to create a base class that represents a party, and then make different classes like BirthdayPartyForKids, BirthdayParty, NewYearPartyWithBooze, NewYearPartyWithoutBooze, BreakupParty, etc. extend this base class. We can then instantiate one of these classes as needed. However, this approach has the potential to quickly become unmanageable, as the number of subclasses can rapidly increase. The total number of sub-classes required would be 2^n (n is the number of optional attributes). For example, if there are four optional attributes, there could be as many as 16 subclasses. This approach may work for small applications, but it can become difficult to maintain as the number of subclasses grows.

No alt text provided for this image
Code snippet for approach #1. Please read the comments.

Approach #2

Another way to create the party planning application is to create a common class and then use a parameter-heavy constructor that takes all the possible attributes as parameters. For the parameters that are not relevant, we can pass null. However, this approach can make the code difficult to read and understand because most of the parameters will often be unused, making it hard to determine which ones are relevant and which ones are not.

No alt text provided for this image
Code snippet for approach #2. Please read the comments.

Approach #3

Another approach we can take is to create different constructors that take in different numbers of arguments. For example, we might have one constructor for BirthdayParties that takes only the location, time, occasion and mascot attributes, and another constructor for new year parties with booze that also takes the booze attribute. However, this approach also has its drawbacks. One issue is that there will be a large number of constructors catering to all the different combinations of the common attributes. Additionally, if two attributes have the same data type, it is not possible to create two constructors that take only one of those attributes as a parameter. This is because two methods with the same signature (i.e., the same number and type of parameters) cannot coexist in the same class, as there will be no way for the compiler to determine which one to choose.

No alt text provided for this image
Code snippet for approach #3. Please read the comments.

Approach #4

We can also create a Party class and then use setters to set the required attributes. This approach is feasible, but it requires us to make the objects mutable in order to use the setters. One issue with this approach is that it can make the code less thread-safe. In general, making objects immutable is one way to ensure that code is thread-safe, because it prevents multiple threads from modifying the object at the same time. There are other approaches to achieving thread safety, such as using synchronization or using thread-safe data structures, but making objects immutable is often considered the easiest and most straightforward option.

No alt text provided for this image
Code snippet for approach #4. Please read the comments.

Approach #5:

The builder pattern can be a useful solution in this case. With this approach, we create a class called the builder class, which is responsible for creating an object with the required parameters. The builder class has constructors for the required parameters, and setter-like methods for the optional parameters. These setter-like methods allow the developer to specify the values for the optional parameters, and the build method combines all the specified parameters to create the final object. Let's now look into the code:

No alt text provided for this image
Code snippet for approach #5. Please read the comments.

Builder pattern highlights

  1. The constructor of the Party class is private, so it is only possible to create objects of the Party class using the PartyBuilder.
  2. The constructor of the Party class takes in an instance of the PartyBuilder class to populate its attributes.
  3. The PartyBuilder is a static inner class, which means it can be instantiated without requiring an instance of the outer class.
  4. The PartyBuilder uses setter-like methods to set the optional attributes of the Party object. All of these setter-like methods return the PartyBuilder instance, which allows them to be chained together.
  5. It is also possible to implement input validation in the PartyBuilder.build() method. This way, the Party objects will only be created when none of the attributes have optional values, ensuring that the object is always valid.

Food for thought

Would this pattern still make sense if Java had named parameters?

Advantages

  1. This method avoids the need for creating a large number of subclasses for all the combinations of optional attributes.
  2. This method avoids the problem of having a constructor with a large number of parameters, which is often considered a code smell.
  3. The builder pattern also avoids the telescopic constructor anti-pattern and the creation of too many constructors, which can make coding and maintenance difficult.
  4. The builder pattern also makes it easier to implement thread safety, because the objects can be made immutable.

Disadvantages

  1. The use of the builder pattern can make the code more verbose and harder to read, because it involves creating separate classes for the builder and the object being built.
  2. The builder pattern is most commonly used to create immutable objects.

When to use this pattern?

  1. Use the builder pattern to avoid creating a large number of subclasses, to avoid having a constructor with a large number of attributes, to avoid the telescopic constructor anti-pattern, and to avoid creating too many constructors.
  2. Use the builder pattern when you are using the composite pattern, as the builder pattern can help simplify the process of creating complex objects that are built using recursive steps.
  3. Use the builder pattern when you need to vary a product's internal representation, as the builder pattern allows you to create objects with different combinations of parameters in a flexible and reusable way.
  4. Use the builder pattern to create complex objects that have many optional parameters, as the builder pattern allows you to specify only the parameters that you need, without having to provide values for all possible parameters.
  5. Use the builder pattern to create objects that are designed to be immutable, as the builder pattern allows you to create the object in a single step, which can make it easier to ensure that the object's state is consistent and cannot be modified after it is created.

References

I took help from the following sources to write this article:

  1. https://refactoring.guru/design-patterns/builder
  2. https://refactoring.guru/smells/long-parameter-list
  3. https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java
  4. https://springframework.guru/gang-of-four-design-patterns/builder-pattern/
  5. https://www.geeksforgeeks.org/builder-pattern-in-java/
  6. https://stackoverflow.com/questions/70324/java-inner-class-and-static-nested-class
  7. https://cs.nyu.edu/~jcf/classes/g22.3033-007_sp01/handouts/g22_3033_h54.htm
  8. https://www.tutorialspoint.com/what-are-named-parameters-in-chash

Vyankatesh Kulkarni

R&D Engineer-II @Nokia | Core Java | Spring Core | Spring Boot | Microservices | DSA | Docker | Kubernetes | Kafka | Contributing for building best software

2 个月

Explained very well. Easy to understand

回复
Raj Soni

Founder of DearDiary |Software Engineer | MERN Stack Developer | C++ | JavaScript | Python | Continuous Learner | Innovative Application Builder | DSA

1 年

thanks for this Article Prateek M. . For last one day i was struggling to find the answer that i found after understanding builder pattern that why can't i make setters instead of creating all this complex builder class and director class , and i found the answer from you thanks :))

回复
Prateek Mishra

Software Engineer at Google | ex. Microsoft, Walmart | Gold Medalist, IIIT Allahabad | XAT'24: 99.8

1 年

Insightful : This is an article on the builder design pattern from the "Effective Java, 3rd Edition" by Joshua Bloch and is different from the builder design pattern given in the "Design Patterns: Elements of Reusable Object-Oriented Software" by Richard et al. Let's reserve a discussion on that for some other day.

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了