Builder Pattern

Video Materials

The first pattern we’ll look at is the builder pattern. The builder pattern is used to simplify building complex objects, where the class that needs the object shouldn’t have to include all of the code and details for how to construct that object. By decoupling the code for constructing the complex object from the classes that use it, it becomes much simpler to change the representations or usages of the complex object without changing the classes that use it, provided they all adhere to the same general API.

Builder Pattern UML Builder Pattern UML1

The UML diagram above gives one possible structure for the builder pattern. It includes a Builder interface that other objects can reference, and Builder1 is a class that implements that interface. There could be multiple builders, one for each type of object. The Builder1 class contains all of the code needed to properly construct the ComplexObject class, consisting of ProductA1 and ProductB1. If a different ComplexObject must be created, we can create another class Builder2 that also implements the Builder interface. To the Director class, both Builder1 and Builder2 implement the same interface, so they can be treated as the same type of object.

Example: Deck of Cards

A great example of this would be creating a deck of cards for various card games. There are actually many different types of card decks, depending on the game that is being played:

  • Standard 52 Cards: 2-10, J, Q, K, A in four Suits
  • Standard 52 Cards with Jokers: add one or two Jokers to a Standard 52 Card Deck
  • Pinochle Deck: 9, J, Q, K, 10, A in four suits, two of each; 48 cards total
  • Old Maid: Remove any 1 card from a Standard 52 Card Deck
  • Uno: One 0 and Two each of 1-9, Skip, Draw Two and Reverse in four colors, plus four Wild and four Wild Draw Four; 108 cards total
  • Rook: 1-14 in four colors, plus a Rook (Joker); 57 cards total

As we can see, even though each individual card is similar, constructing a deck for each of these games might be quite the complex process.

Instead, we can use the builder pattern. Let’s look a how this could work.

The Card Class

First, we’ll assume that we have a very simple Card class, consisting of three attributes:

  • SuitOrColor - the suit or color of the card. We’ll use a special color for cards that aren’t associate with a group of other cards
  • NumberOrName - the number or name of the card
  • Rank - the sorting rank of the card (lowest = 1).
public class Card{
    String suitOrColor;
    String numberOrName;
    int rank;
    
    public Card(String suit, String number, int rank) {
        this.suitOrColor = suit;
        this.numberOrName = number;
        this.rank = rank;
    }
}
class Card:
    def __init__(self, suit: str, number: str, rank: int) -> None:
        self._suit_or_color: str = suit
        self._number_or_name: str = number
        self._rank: int = rank

The Deck Class

The Deck class will only consist of an aggregation, or list, of the cards contained in the deck. So, our builder class will return an instance of the Deck object, which contains all of the cards in the deck.

The Deck class could also include generic methods to shuffle, draw, discard, and deal cards. These would work with just about any of the games listed above, regardless of the details of the deck itself.

import java.util.LinkedList;
import java.util.List;

public class Deck{
    List<Card> deck;
    
    public Deck() {
        deck = new LinkedList<>();
    }
    
    void shuffle();
    Card draw();
    void discard(Card card);
    List<List<Card>> deal(int hands, int size);
}
from typing import List


class Deck:
    def __init__(self) -> None:
        self._deck: List[Card] = list()
    
    def shuffle(self) -> None:
    def draw(self) -> Card:
    def discard(self, card: Card) -> None:
    def deal(self, hands: int, size: int) -> List[List[Card]]:

The Builder Interface

Our DeckBuilder interface will be very simple, consisting of a single method: buildDeck(). The type of the class that implements the DeckBuilder interface will determine which type of deck is created. If the decks created by the builder have additional options, we can add additional methods to our DeckBuilder interface to handle those situations.

The Builder Classes

Finally, we can create our builder classes themselves. These classes will handle actually building the different decks required for each game. First, let’s build a standard 52 card deck.

public class Standard52Builder implements DeckBuilder {
    String[] suits = {"Spades", "Hearts", "Diamonds", "Clubs"};

    public Deck buildDeck() {
        Deck deck = new Deck();
        for (String suit : suits) {
            for (int i = 2; i <= 14; i++) {
                if (i == 11) {
                    deck.add(new Card(suit, "Jack", i));
                } else if (i == 12) {
                    deck.add(new Card(suit, "Queen", i));
                } else if (i == 13) {
                    deck.add(new Card(suit, "King", i));
                }else if (i == 14) {
                    deck.add(new Card(suit, "Ace", i));
                } else {
                    deck.add(new Card(suit, "" + i, i));
                }
            }
        }
        return deck;
    }
}
from typing import List


class Standard52Builder(DeckBuilder):
    suits: List[str] = ["Spades", "Hearts", "Diamonds", "Clubs"]
    
    def build_deck(self):
        deck: Deck = Deck()
        for suit in suits:
            for i in range(2, 15):
                if i == 11:
                    deck.append(Card(suit, "Jack", i))
                elif i == 12:
                    deck.append(Card(suit, "Queen", i))
                elif i == 13:
                    deck.append(Card(suit, "King", i))
                elif i == 14:
                    deck.append(Card(suit, "Ace", i))
                else:
                    deck.append(Card(suit, str(i), i))
        return deck

As we can see, the heavy lifting of actually building the deck is offloaded to the builder class. We can easily use this same framework to create additional Builder classes for the other types of decks listed above.

Using the Builder

Finally, once we’ve created all of the builders that we’ll need, we can use them directly in our code anywhere we need them:

public class CardGame{

    public static void main(String[] args) {
        DeckBuilder builder = new Standard52Builder();
        Deck cards = builder.buildDeck();
        // game code goes here
    }

}
from typing import List


class CardGame:

    @staticmethod
    def main(args: List[str]) -> None:
        builder: DeckBuilder = Standard52Builder()
        cards: Deck = builder.build_deck()
        # game code goes here

From here, if we want to use any other decks of cards, all we have to do is switch out the single line for the type of builder we instantiate, and we’ll be good to go! This is the powerful aspect of the builder pattern - we can move all of the complex code for creating objects to a builder class, and then any class that uses it can quickly and easily construct the objects it needs in order to function.

On the next page, we’ll see how we can expand this pattern by including the factory pattern to help simplify things even further.