Menu Close

Object Oriented Design Pattern: Builder

digital hamburger

Explanation

The Builder design pattern is a creational pattern that helps you to build complex objects. Where the Template Method pattern focuses more on customizing steps of an algorithm, the Builder pattern focuses on object creation. The complex object that you want to create is composed out of several other simpler objects. Typically, a director class directs the construction of the complex object.

Use cases:

  • You want to maintain a clear overview when creating a complex object or objects.
  • You want more control over your object creation process.
  • You can exchange components of the main object that you want to build to get variants of the object.
  • You want to keep your class constructors short.

Pitfalls:

  • Keeping the separation of concerns for different classes, like the Director class and component classes.
  • The temptation to create more and more builders or nested builders which make the object creation complex to follow and maintain.
  • Over complicating a use case that can be solved simpler/better in another way.
Class diagram of Builder pattern, simplified.

Java Example

Let’s create a hamburger builder. You can read the code from top to bottom and hopefully the comments make sense enough to understand what’s going on.

package com.chosengambit.designpatterns;

import com.chosengambit.builder.Director;
import com.chosengambit.builder.Hamburger;
import com.chosengambit.builder.HamburgerBuilder;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
       
        // the director class is responsible for assembling a new hamburger
        Director hDirector = new Director();
        
        try {
            // you can create a default product or assemble a custom one
            Hamburger hamburger = hDirector.CreateDefaultBurger();
            System.out.println(hamburger.toString());     
            
            // custom burger
            HamburgerBuilder hamburgerBuilder = new HamburgerBuilder();
            hamburgerBuilder.setBread(new HamburgerBuilder.Ciabatta());
            hamburgerBuilder.addProtein(new HamburgerBuilder.Vegan());
            // triple portion of red onion
            hamburgerBuilder.addOnion(new HamburgerBuilder.RedOnion());
            hamburgerBuilder.addOnion(new HamburgerBuilder.RedOnion());
            hamburgerBuilder.addOnion(new HamburgerBuilder.RedOnion());
            
            hamburger = hDirector.CreateCustomBurger(hamburgerBuilder);
            System.out.println(hamburger.toString());       
        }
        catch (Exception e) {
            System.out.println(e.toString());
        }       
    }
}
Java

package com.chosengambit.builder;

public class Director {
   
    /**
     * Returns a custom made burger
     * @param hamburgerBuilder
     * @return Hamburger
     * @throws com.chosengambit.builder.HamburgerBuilder.ProductIncompleteException 
     */
    public Hamburger CreateCustomBurger(HamburgerBuilder hamburgerBuilder) throws HamburgerBuilder.ProductIncompleteException {
        return hamburgerBuilder.getProduct();
    }
    
    /**
     * Return the default burger of the restaurant
     * @return Hamburger
     * @throws com.chosengambit.builder.HamburgerBuilder.ProductIncompleteException 
     */
    public Hamburger CreateDefaultBurger() throws HamburgerBuilder.ProductIncompleteException {        
        HamburgerBuilder hamburgerBuilder = new HamburgerBuilder();
        hamburgerBuilder.setBread(new HamburgerBuilder.Bun())
                        .addLettuce(new HamburgerBuilder.Iceberg())
                        .addOnion(new HamburgerBuilder.YellowOnion())                        
                        .addProtein(new HamburgerBuilder.NonVegan());
        
        // add sauce
        hamburgerBuilder.addSauce(new HamburgerBuilder.Ketchup());     
        
        return hamburgerBuilder.getProduct();
    }    
}
Java

package com.chosengambit.builder;

import java.util.ArrayList;
import java.util.List;

/**
 * Builder class that can build Hamburgers
 * @author ChosenGambit
 */
public class HamburgerBuilder {
    
    // generalized product parts
    public interface IBread { }
    public interface ILettuce { }
    public interface IProtein { }
    public interface IOnion { }
    public interface ISauce { }
    
    // quick implementations of product parts
    public static class CornBread implements IBread {}
    public static class Bun implements IBread {}
    public static class Ciabatta implements IBread {}
    public static class Rucola implements ILettuce {}
    public static class Iceberg implements ILettuce {}
    public static class Vegan implements IProtein {}
    public static class NonVegan implements IProtein {}    
    public static class RedOnion implements IOnion {}
    public static class YellowOnion implements IOnion {}
    public static class Ketchup implements ISauce {}
    public static class Mayonaise implements ISauce {}
    
    public class ProductIncompleteException extends Exception {
        public ProductIncompleteException(String message) {
            super(message);
        }        
    }
    
    private Hamburger hamburger;
    
    public HamburgerBuilder() {
        this.hamburger = new Hamburger();
    }         
    
    // because "this" object is returned, you can chain the creation of hamburgers
    // I use "set" as naming convention when only one item of this kind can be added
    // we can only have one bread
    public HamburgerBuilder setBread(IBread bread) {
        this.hamburger.setBread(bread);
        return this;
    }
    
    // and "add" when multiple items can be added
    public HamburgerBuilder addLettuce(ILettuce lettuce) {
        this.hamburger.getLettuceList().add(lettuce);
        return this;
    }
    
    public HamburgerBuilder addProtein(IProtein protein) {
        this.hamburger.getProteinList().add(protein);
        return this;
    }
    
    public HamburgerBuilder addOnion(IOnion onion) {
        this.hamburger.getOnionList().add(onion);
        return this;
    }
    
    public HamburgerBuilder addSauce(ISauce sauce) {
        this.hamburger.getSauceList().add(sauce);
        return this;
    }
    
    public void reset() {
        this.hamburger = new Hamburger();
    }
    
    public Hamburger getProduct() throws ProductIncompleteException {
        if (this.hamburger.getBread() == null) {
            throw new ProductIncompleteException("No bread has been set, which is mandatory.");
        }
        
        if (this.hamburger.getProteinList().size() == 0) {
            throw new ProductIncompleteException("Set at least one kind of protein.");
        }
        
        return this.hamburger;
    }
  
}
Java

package com.chosengambit.builder;

import java.util.ArrayList;
import java.util.List;

// basically a list with ingredients that make the hamburger
// mostly getters and setters
public class Hamburger {
    
    // the ingredients
    private HamburgerBuilder.IBread bread;
    private List<HamburgerBuilder.ILettuce> lettuceList = new ArrayList<HamburgerBuilder.ILettuce>();
    private List<HamburgerBuilder.IProtein> proteinList = new ArrayList<HamburgerBuilder.IProtein>();
    private List<HamburgerBuilder.IOnion> onionList = new ArrayList<HamburgerBuilder.IOnion>();
    private List<HamburgerBuilder.ISauce> sauceList = new ArrayList<HamburgerBuilder.ISauce>();
        
    public HamburgerBuilder.IBread getBread() {
        return bread;
    }

    public void setBread(HamburgerBuilder.IBread bread) {
        this.bread = bread;
    }

    public List<HamburgerBuilder.ILettuce> getLettuceList() {
        return lettuceList;
    }

    public void setLettuceList(List<HamburgerBuilder.ILettuce> lettuceList) {
        this.lettuceList = lettuceList;
    }

    public List<HamburgerBuilder.IProtein> getProteinList() {
        return proteinList;
    }

    public void setProteinList(List<HamburgerBuilder.IProtein> proteinList) {
        this.proteinList = proteinList;
    }

    public List<HamburgerBuilder.IOnion> getOnionList() {
        return onionList;
    }

    public void setOnionList(List<HamburgerBuilder.IOnion> onionList) {
        this.onionList = onionList;
    }

    public List<HamburgerBuilder.ISauce> getSauceList() {
        return sauceList;
    }

    public void setSauceList(List<HamburgerBuilder.ISauce> sauceList) {
        this.sauceList = sauceList;
    }
    
    @Override
    public String toString() {
        StringBuilder burgerString = new StringBuilder("Burger is assembled out of: ");
        if (bread != null) burgerString.append(bread.getClass().getSimpleName());
        
        proteinList.forEach(item -> burgerString.append(" "+item.getClass().getSimpleName()));
        lettuceList.forEach(item -> burgerString.append(" "+item.getClass().getSimpleName()));
        onionList.forEach(item -> burgerString.append(" "+item.getClass().getSimpleName()));
        sauceList.forEach(item -> burgerString.append(" "+item.getClass().getSimpleName()));        
        
        return burgerString.toString();
    }
}
Java
Output:

Burger is assembled out of: Bun NonVegan Iceberg YellowOnion Ketchup
Burger is assembled out of: Ciabatta Vegan Vegan RedOnion RedOnion RedOnion

Python Example

class Bread():
    pass


class CornBread(Bread):
    pass


class Bun(Bread):
    pass


class Ciabatta(Bread):
    pass


class Lettuce:
    pass


class Rucola(Lettuce):
    pass


class Iceberg(Lettuce):
    pass


class Protein:
    pass


class Vegan(Protein):
    pass


class NonVegan(Protein):
    pass


class Onion:
    pass


class RedOnion(Onion):
    pass


class YellowOnion(Onion):
    pass


class Sauce:
    pass


class Ketchup(Sauce):
    pass


class Mayonnaise(Sauce):
    pass

# Hamburger class
class Hamburger:
    def __init__(self):
        self.bread = None
        self.lettuce_list = []
        self.protein_list = []
        self.onion_list = []
        self.sauce_list = []

    def __str__(self):
        return (f"Burger with bread: " + self.c_name(self.bread) + ", lettuce: " + "".join(
            [self.c_name(s) for s in self.lettuce_list]) +
                " protein: " + "".join([self.c_name(s) for s in self.protein_list]) +
                " onion: " + "".join([self.c_name(s) for s in self.onion_list]) +
                " sauce: " + "".join([self.c_name(s) for s in self.sauce_list]))

    def c_name(self, val):
        return str(val.__class__.__name__)


# Builder class
class HamburgerBuilder:
    def __init__(self):
        self.hamburger = Hamburger()

    def set_bread(self, bread):
        self.hamburger.bread = bread
        return self

    def add_lettuce(self, lettuce):
        self.hamburger.lettuce_list.append(lettuce)
        return self

    def add_onion(self, onion):
        self.hamburger.onion_list.append(onion)
        return self

    def add_protein(self, protein):
        self.hamburger.protein_list.append(protein)
        return self

    def add_sauce(self, sauce):
        self.hamburger.sauce_list.append(sauce)
        return self

    def get_product(self):
        return self.hamburger

# Director constructs a Hamburger
class Director:
    def create_custom_burger(self, hamburger_builder):
        return hamburger_builder.getProduct()

    def create_default_burger(self):
        hamburger_builder = HamburgerBuilder()
        hamburger_builder.set_bread(Bun()) \
            .add_lettuce(Iceberg()) \
            .add_onion(YellowOnion()) \
            .add_protein(NonVegan()) \
            .add_sauce(Ketchup())
        return hamburger_builder.get_product()


def main():
    director = Director()
    try:
        hamburger = director.create_default_burger()
        print(hamburger)
    except Exception as e:
        print(str(e))


if __name__ == "__main__":
    main()
Python
Output:

Burger with bread: Bun, lettuce: Iceberg protein: NonVegan onion: YellowOnion sauce: Ketchup

Conclusion

With the builder design pattern, you are able to construct complex objects which are made out of other objects. Furthermore, the add/set functions/methods in the HamburgerBuilder class all return its own instance which makes chaining the build process possible.

References

Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.

Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.

Wikipedia contributors. (2024, 10 september). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern

Related Posts