Applying the Builder Pattern

The Builder Pattern is a design pattern used to separate the construction of a complex object from its representation, so that the same construction process can be used to create different representations. [1]

Usage
The Builder pattern is useful for constructing complex objects. It enables one to build a complex object without exposing the details of the object’s parts or how the object’s parts are assembled. Also, as noted previously, the Builder pattern allows for the creation of different representations of the same object via the same construction process.

A major difference between the Builder pattern and the widely used Factory pattern is that the Builder pattern builds up an object in a step by step manner. Then, as a final explicit step, the builder returns the object. Objects created using the Factory pattern are not built in a step wise manner, instead an object is returned immediately upon creation.

Components
The Builder pattern contains the following components:

  • Builder – Defines the interface used to build a Product
  • Concrete Builder – Constructs, assembles, a maintains a representation of a Product
  • Director – Builds a Product using the Builder
  • Product – the object that is created by the Builder

Applying the Builder Pattern

Let’s look at how we can use the Builder Pattern to create various representations of a Tree. To do so, we will create a few classes:

  • TreeBuilder – interface for building Trees
  • BinaryTreeBuilder – a concrete builder used to create Binary Trees
  • Tree – the object created by TreeBuilder
  • BinaryTree – a specialized Tree, Binary tree

Using the components described above, we will implement a simple binary tree Builder using a slightly altered Builder pattern (one that veers slightly from that described by the Gang of Four). Below, you will find implementations of the classes required to implement our binary tree builder. Once all of our builder components are in place, we will use our builder to construct a binary tree via code similar to the following:


    TreeBuilder b = BinaryTreeBuilder.getBuilder();
    // Build the tree
    b = b.root("root",
                b.inner("level_1_left",
                        b.inner("level_2_left",
                                b.leaf("level_3_left_leaf"),
                                b.inner("level_3_right",
                                        b.leaf("level_4_left_leaf"),
                                        b.leaf("level_4_right_leaf"))),
                        b.leaf("level_2_right_leaf")),
                b.leaf("level_1_right_leaf"));
    // Create the tree
    Tree tree = b.build();

The above code corresponds to our Director.
The above builder is also quite neat! Not only are we able to build a variety of binary tree representations, but we are also able to visualize our tree simply by looking at the builder code that defines it. Neat! Of course, with larger trees, this visualization quickly loses its effectiveness, but for the purpose of this blog entry, our builder provides a fun example 😀

Okay, so we’ve agreed that this example is a bit contrived, but it is clear from the example that the Builder pattern provides great flexibility when building and constructing complex objects, like our Tree.

With that said, let’s start implementing our builder.
First, our Builder interface TreeBuilder:

public interface TreeBuilder<T> {
    public TreeBuilder<T> root(T data, List<TreeBuilder<T>> builders);
    public TreeBuilder<T> inner(T data, List<TreeBuilder<T>> builders);
    public TreeBuilder<T> leaf(T data, List<TreeBuilder<T>> builders);
}

Next, we implement a Concrete Builder for Binary Trees, BinaryTreeBuilder.


public class BinaryTreeBuilder implements TreeBuilder {
    private Node root;
    private final Stack<Node> nodes = new Stack<>();

    public static BinaryTreeBuilder getBuilder() {
        return new BinaryTreeBuilder();
    }

    public <T> TreeBuilder root(T data, TreeBuilder... builders) {
        return inner(data, builders);
    }

    public <T> TreeBuilder inner(T data, TreeBuilder... builders) {
        if (builders.length != 2) {
            throw new IllegalArgumentException("Must specify builders for both child nodes");
        }

        Node node = new Node(data);
        List<Node> children = new ArrayList<>();
        children.add(nodes.pop()); // right
        children.add(nodes.pop()); // left
        node.setChildren(children);

        nodes.push(node);
        root = node;
        return this;
    }

    public <T> TreeBuilder leaf(T data) {
        nodes.push(new Node(data));
        return this;
    }

    public TreeBuilder nil() {
        return null;
    }

    public Tree build() {
        return new Tree(root);
    }

    @Override
    public String toString() {
        if (root == null) {
            return nodes.toString();
        }
        return "root: "+root.getData();
    }

    private BinaryTreeBuilder() {
    }

Finally, our we implement our Product component, the Tree.

public class Tree {

	private final Node root;
	Tree(Node root) {
		this.root = root;
	}

	public Node getRoot() {
		return root;
	}
}

Thank You!

References:
[1] Design Patterns Elements of Reusable Object-Oriented Software – Gang of Four – Addison-Wesley

You may also like...

Leave a Reply