🏠JavaBuilder Design Pattern

Builder Design Pattern

In this post, We will take a look at Builder Design Pattern in Java with a real-world example. It is a creational design pattern that allows to create objects with variations.

Definition

As per the book from the gang of four on design patterns, The builder design pattern separates the construction of a complex object from its representation. This way, The same construction process can create different representations.

class diagram for builder pattern

With the Builder pattern, every product has its relevant builder. These builders take care of creating the products instead of directly using constructors.

In the builder design pattern, we have four major participants. You can see them in the image shown here. Let’s go through them one by one.

  1. Director: The one who needs the product.
  2. Builder: An interface that detaches Director from ConcreteBuilder
  3. ConcreteBuilder: Has the steps to build a complex representation of a Product.
  4. Product: The object that is in need by the Director/Client.

Why use Builder Design Pattern?

When you have a large object to create, you would probably use a constructor. But as the object grows, the constructor becomes hard to maintain. For example, Take a look at a hypothetical constructor for Pizza.

public Pizza(Size size, Crust crust, Sauce sauce, boolean extraCheese, boolean jalapeno, boolean pepper, boolean corn, boolean tomato, boolean peperoni, boolean pineapple, boolean seasoning) { this.size = size; this.crust = crust; this.sauce = sauce; this.extraCheese = extraCheese; this.jalapeno = jalapeno; this.pepper = pepper; this.corn = corn; this.tomato = tomato; this.peperoni = peperoni; this.pineapple = pineapple; this.seasoning= seasoning; }
Code language: Java (java)

First, The constructor has close to 10 parameters. From a code smell perspective, this is bad. Also, who can memorize these many parameters anyway? Even with proper documentation, the constructor is confusing. And most importantly, you need to send all the necessary flags to the constructor.

Secondly, The implication of modifying this constructor. As you need to go through a lot of refactoring, large constructors become serious technical debt. We could easily solve these problems using the Builder pattern. Let’s try implementing it with an example in java.

Implementation of Builder Pattern in Java

As you seen before in the class diagram, A client should use a Builder interface to create products. In this case, We will create a PizzaBuilder that deals with creating pizzas.

public interface PizzaBuilder { PizzaBuilder withCrust(Crust crust); PizzaBuilder withSauce(Sauce sauce); PizzaBuilder ofSize(Size size); Pizza build(); }
Code language: Java (java)

Note that the crust, size and sauce are preferences for every pizza. So the builder methods try to capture them. But the recipe for each type of pizzas is always the same. For example, Here is a MargaritaPizzaBuilder.

public class MargaritaPizzaBuilder implements PizzaBuilder { private Crust crust = Crust.HAND_TOSSED; private Sauce sauce = Sauce.TOMATO; private Size size = Size.REGULAR; @Override public PizzaBuilder withCrust(Crust crust) { this.crust = crust; return this; } @Override public PizzaBuilder withSauce(Sauce sauce) { this.sauce = sauce; return this; } @Override public PizzaBuilder ofSize(Size size) { this.size = size; return this; } @Override public Pizza build() { return new Pizza(size, crust, sauce, true, false, false, false, false, false, false, true); } }
Code language: Java (java)

Now that we have implemented the builder design pattern in java, We just need to call the build method of the MargaritaPizzaBuilder. The composition is taken care of by the builder java class. Similarly, you could write other builders like PeperoniPizzaBuilder and PineapplePizzaBuilder

Calling builder methods

Once we have our builders in place, we could call their methods as per our need. Note that our examples use default values for crust, sauce and size. These values then can be overridden using builder methods. For example, Take a look at this snippet below.

public class BuilderPatternTest { public static void main(String[] args) { //this main class acts as the pizza director Pizza regularPanMargarita = new MargaritaPizzaBuilder() .withCrust(Crust.PAN) .ofSize(Size.REGULAR) .withSauce(Sauce.TOMATO) .build(); Pizza peperoniLarge = new PeperoniPizzaBuilder().build(); Pizza pineapplePizza = new PineapplePizzaBuilder().withSauce(Sauce.PESTO).build(); System.out.println(regularPanMargarita); System.out.println(peperoniLarge); System.out.println(pineapplePizza); } }
Code language: Java (java)

Here, We use all three customizations to create regularPanMargarita Pizza. However, We created the peperoniLarge pizza with default values. Run this snippet and you would get a similar output as shown here.

Advantages Of Builder Pattern

There are couple of advantages that comes with builder pattern.

  1. As the constructor is encapsulated by the builder methods. This way, we only need to change the build() methods.
  2. Each builder is unique thus it is easy to remember. At the same time, you get all customizations through the withProperty builder methods.
  3. Less code smell as you don’t have to use large constructors everywhere.

Disadvantages of Builder Pattern

Even though builder pattern goes great with many situations, there are few limitations to implementing it.

  1. You need to implement a builder class for each type. In this case, a builder for Each type of pizzas.
  2. The builder class is against dependency injection as the builder methods control the dependencies.
  3. The builders must be mutable.

Summary

To sum it up, We learned what is builder design pattern and how to implement it in Java with an example. You can find all of these examples in our design patterns GitHub repository.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *