Recursive Builder Pattern

Hi Guys, Coders and Geeks!

We all know how handy is the builder pattern. Stop. Maybe not all? Do not worry, builder pattern is well explained in “Design Patterns” from O’Reilly. You will get it in 10 mins. The most important reason why it is good to have a builder is to avoid numerous parameters being passed to the constructor. The builder pattern has many variations, but for the sake of this tutorial, we will use the simplest possible form – static inner class. Take a look:

package com.public_class.snippets.self_mimick_builder;

// Kubanino @ https://public-class.com/
public class Classic
{
    private String name;
    private String surname;

    private Classic(final String name, final String surname)
    {
        this.name = name;
        this.surname = surname;
    }

    public String getName()
    {
        return name;
    }

    public String getSurname()
    {
        return surname;
    }

    static class Builder
    {
        private String name;
        private String surname;

        public Builder()
        {
        }

        public Builder withName(final String name)
        {
            this.name = name;
            return this;
        }

        public Builder withSurname(final String surname)
        {
            this.surname = surname;
            return this;
        }

        public Classic build()
        {
            return new Classic(name, surname);
        }
    }
}
You can create specialized builders for different applicable solutions – to ensure immutability, to synchronize, to have some default values (prototype pattern). Try to investigate builder pattern for yourself – it is worth it!

Recursive Builder is needed

Going back to the recursive builder. Imagine, that You have a class, which has already a builder. For some reasons, You want to extend it. What now? What will happen to the builder? Well, it is still there, ready to be used in the context of the superclass.

Probably, You would like to have another builder, which could set all the parameters of the child class and the superclass. If you write that builder, it will be verbose and basically, You will duplicate the code from the super class’s builder. The answer would be to create a builder, which utilize a self pointer. In Java, we have keywords such as super and this, but there is no self! Fortunately, we can mimic self with generics. To put things a little more complicated, we will have 3 levels in our example hierarchy:

  • Tool – general concept,
  • Hammer – still general, but more problem oriented concept,
  • ArtisticHammer – ready to solve artistic challenge!

Top level class for Recursive Builder

First, let’s take a look at the upper-level superclass:

package com.public_class.snippets.self_mimick_builder;

// Kubanino @ https://public-class.com/
public abstract class Tool
{
    public static final Double DEFAULT_WEIGHT = 12D;

    private Double weight;

    Tool(final Builder<?> builder)
    {
        weight = builder.weight;
    }

    public Double getWeight()
    {
        return weight;
    }

    // "T" could be the lowest level ArtisticHammer.Builder
    abstract static class Builder<T extends Builder<T>>
    {
        Double weight = DEFAULT_WEIGHT;

        public T withWeight(final Double weight)
        {
            this.weight = weight;
            return self();
        }

        abstract Tool build();

        protected abstract T self();
    }
}

Worth to notice:

  • Recursive declaration abstract static class Builder<T extends Builder<T>>ensures, that whatever is represented by Tis actually extending the Builder’s class.
  • Abstract self()method is parametrized with  T ensuring, that we can return “self” in all superclass Builder’s methods.

Middle level class – abstract Hammer

Let’s proceed with the Hammer, middle-level class, which for some reason is also abstract (we don’t want to have primitive hammers in our toolbox!):

package com.public_class.snippets.self_mimick_builder;

// Kubanino @ https://public-class.com/
public abstract class Hammer extends Tool
{
    public static final int DEFAULT_LENGTH = 13;

    private int length;

    Hammer(final Hammer.Builder<?> builder)
    {
        super(builder);
        length = builder.length;
    }

    public int getLength()
    {
        return length;
    }

    // Hammer Builder accepts "T" of type "ArtisticHammer.Builder" and passes again the type to Tool.Builder
    public abstract static class Builder<T extends Hammer.Builder<T>> extends Tool.Builder<T>
    {
        int length = DEFAULT_LENGTH;

        public T withLength(int length)
        {
            this.length = length;
            return self();
        }
    }
}

Worth to notice:

  • Take a note how we can acomplish the chain of calls (fluent) from whatever will extend the Hammer, to the top level Tool (line #22). When we are setting another parameter, instead of returning pointer to the current level’s builder, we should call the self().

Something ready to use and build – ArtisticHammer

And finally constructing the ArtisticHammer:

package com.public_class.snippets.self_mimick_builder;

// Kubanino @ https://public-class.com/
public class ArtisticHammer extends Hammer
{
    public static final String DEFAULT_COLOR = "Purple";

    private String color;

    ArtisticHammer(final ArtisticHammer.Builder builder)
    {
        super(builder);
        this.color = builder.color;
    }

    // ArtisticHammer.Builder extends upper class's builder, passing his own type
    public static class Builder extends Hammer.Builder<ArtisticHammer.Builder>
    {
        String color = DEFAULT_COLOR;

        public ArtisticHammer.Builder withColor(final String color)
        {
            this.color = color;
            return self();
        }

        @Override
        public ArtisticHammer build()
        {
            return new ArtisticHammer(this);
        }

        @Override
        protected ArtisticHammer.Builder self()
        {
            return this;
        }
    }

    public String getColor()
    {
        return color;
    }
}

Worth to notice:

  • To emphasize what is going on, we are using full names for class blueprints.
  • self() method is overridden, and now it returns the type of ArtisticHammer.Builder– note how covariant return type is definied

ArtisticHammer in action

Let’s do some quick tests:

package com.public_class.snippets.self_mimick_builder;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;

import static com.public_class.snippets.self_mimick_builder.ArtisticHammer.DEFAULT_COLOR;
import static com.public_class.snippets.self_mimick_builder.Hammer.DEFAULT_LENGTH;
import static com.public_class.snippets.self_mimick_builder.Tool.DEFAULT_WEIGHT;
import static org.junit.Assert.assertEquals;

// Kubanino @ https://public-class.com/
@RunWith(MockitoJUnitRunner.class)
public class ArtisticHammerTest
{
    private static final int LENGTH = 20;
    private static final String COLOR = "GREEN";
    private static final Double WEIGHT = 44D;

    @Test
    public void shouldBeAbleToCreateArtisticHammerWithAllParametersWhenUsingLowestLevelBuilder()
    {
        // when
        final ArtisticHammer result = new ArtisticHammer.Builder().
                withColor(COLOR).
                withLength(LENGTH).
                withWeight(WEIGHT).
                build();

        // then
        assertEquals(LENGTH, result.getLength());
        assertEquals(WEIGHT, result.getWeight());
        assertEquals(COLOR, result.getColor());
    }

    @Test
    public void shouldBeAbleToCreateArtisticHammerWhenBuilderIsCalledWithDifferentOrder()
    {
        // when
        final ArtisticHammer result = new ArtisticHammer.Builder().
                withWeight(WEIGHT).
                withColor(COLOR).
                withLength(LENGTH).
                build();

        // then
        assertEquals(LENGTH, result.getLength());
        assertEquals(WEIGHT, result.getWeight());
        assertEquals(COLOR, result.getColor());
    }

    @Test
    public void shouldBuildArtisticHammerWithDefaultParametersWhenPassingNoParametersUsingBuilderMethods()
    {
        // when
        final ArtisticHammer result = new ArtisticHammer.Builder().build();

        // then
        assertEquals(DEFAULT_LENGTH, result.getLength());
        assertEquals(DEFAULT_WEIGHT, result.getWeight());
        assertEquals(DEFAULT_COLOR, result.getColor());
    }

}

You can see, the builder pattern is extremally useful. Recursive one is even better! Think about possibilities and grab a beer, You have just learned something useful.

Cheers!

Facebook Comments