StepBuilder - Builder that guides you through the steps

StepBuilder - Builder that guides you through the steps

What is a Builder & Why?

Simply put, a Builder is a helper class whose sole purpose is to help build instances of a certain class. Now why would you need such a class? Let's see an example.


public class Server{
    final static int DEFAULT_PORT = 8080;

    private String protocol;
    private String url;
    private String ipAddress;
    private int port;
    private String description;
    private long uptime;

    public Server(String protocol, String url, String ipAddress, int port, String description, long uptime) {
        this.protocol = protocol;
        ....
    }

    public Server(String protocol, String url, int port) {
        this.protocol = protocol;
        ...
    }

    public Server(String protocol, String ipAddress, int port, String description) {
        this.protocol = protocol;
        ...
    }

    public Server(String url, int port, long uptime) {
        this.url = url;
        ...
    }

    public Server(String url, String ipAddress, String description) {
        this.url = url;
        ...
    }
}

I bet you have seen classes like this, and most probably you've written classes that had even more members and variations of the constructor. When you have 5 or more arguments in a constructor, you can easily loose track of the order of those arguments. It can become even more daunting for your teammates when they need to use this class you've written. And so many variations of the constructor can become pretty confusing for them if they don't know much about the internal implementation of this class. To make the situation worse, we might need more constructors i.e one that takes protocol, url and description as arguments. But we can't possibly have that as we already have a constructor with 3 String type arguments. So obviously there is a problem with this code. This particular issue is called Telescoping Constructor in java. And the Builder pattern can help us to solve it.

Let's see the Builder implementation for Server.


public class Server{
    ...
    private Server(Builder builder) {
        protocol = builder.protocol;
        url = builder.url;
        ...
    }
    public static final class Builder {
        private String protocol;
        private String url;
        ...

        public Builder() {
        }

        public Builder protocol(String val) {
            protocol = val;
            return this;
        }

        public Builder url(String val) {
            url = val;
            return this;
        }

        public Builder ipAddress(String val) {
            ipAddress = val;
            return this;
        }

        public Builder port(int val) {
            port = val;
            return this;
        }

        public Builder description(String val) {
            description = val;
            return this;
        }

        public Builder uptime(long val) {
            uptime = val;
            return this;
        }

        public Server build() {
            return new Server(this);
        }
    }
}

Problem solved! right? We can now build our Server whatever way we like. Just like this:


new Server.Builder()
                .ipAddress("127.0.0")
                .description("localhost")
                .port(80)
                .protocol("TCP")
                .uptime(30)
                .build();

Why StepBuilder?

What if I say there's still problems? Imagine that protocol, port, & ipAddress must be set for our implementation of the Server. What's stopping me or anyone from building the Server like below which will obviously result in a run time error.


new Server.Builder()
                .ipAddress("127.0.0")
                .description("localhost")
                .uptime(30)
                .build();

Even if everybody using such a class knows about the mandatory fields what ensures that they'll not forget to set a mandatory field (imagine having 10 mandatory fields along with 15 optional ones)? StepBuilder pattern solves this particular issue. It ensures that no one can build an instance of the class unless they have provided all the required arguments.

Let's see the implementation of the StepBuilder pattern for this Server:


class Server{
    ...

    private Server(Builder builder) {
        protocol = builder.protocol;
        ...
    }
    //returns the 1st step, not the builder itself
    public static IProtocol builder() {
        return new Builder();
    }

    //interfaces for mandatory steps
    //3rd & last mandatory step, returns the build step
    interface IPort {
        IBuild withPort(int val);
    }
    //2nd mandatory step, returns the 3rd
    interface IIpAddress {
        IPort withIpAddress(String val);
    }
    //1st mandatory step, returns the 2nd
    interface IProtocol {
        IIpAddress withProtocol(String val);
    }

    //interface for final build step
    interface IBuild {
        //optional arguments
        IBuild withUrl(String val);

        IBuild withDescription(String val);

        IBuild withUptime(long val);

        //build step
        Server build();
    }

    public static final class Builder implements IPort, IIpAddress, IProtocol,IBuild 
    {
        private String url;
        ...

        private Builder() {
        }

        @Override
        public IBuild withPort(int val) {
            port = val;
            return this;
        }

        @Override
        public IPort withIpAddress(String val) {
            ipAddress = val;
            return this;
        }

        @Override
        public IIpAddress withProtocol(String val) {
            protocol = val;
            return this;
        }

        @Override
        public IBuild withUrl(String val) {
            url = val;
            return this;
        }

        @Override
        public IBuild withDescription(String val) {
            description = val;
            return this;
        }

        @Override
        public IBuild withUptime(long val) {
            uptime = val;
            return this;
        }

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

While using this Builder, you cannot call build() & create an instance unless you have passed through all the mandatory steps to the final build step. Your IDE will also be able to guide you through the steps with auto suggestion and will give you a compilation error if you try to skip a mandatory step.

If you like this pattern and think that it can be useful then you can also check out the Intellij IDEA plugin that I wrote for generating StepBuilder implementation for any POJO class. It also works with Android Studio.

Mehedi Hasan Khan

Mehedi Hasan Khan

Programmer, Entrepreneur, Tech enthusiast.

 

Related Post

Comments powered by Disqus