I like builders. If you’ve ever seen a constructor with ten parameters, eight of which can be null, you probably like builders, too. While this pattern is quite verbose, it is elegant.

After doing some work with builders, I found myself wondering. Why are getters ever used in these? From Single Responsibility Principle point of view, a builder’s only purpose is to ease creation of a complex object.

Giving access to its data is definitely not the purpose of a builder. It allows the builder to be passed around instead of the (immutable) object itself. However, until built, builder is still invalid and incomplete and definitely not a data object.

I came up with a custom pattern of my own. The builder is in an inner class, the object has a single constructor that takes the builder as its only parameter. Judging by my experience so far, it was a good decision. It also goes well with my no getters approach.

Using a simple immutable class Product, the object-builder duo can look like this:

class Product {
    private final int price;
    private final List<Photo> photos;

    private Product(final Builder builder) {
        this.price = builder.price;
        this.photos = builder.photos;
    }

    public builder() {
        return new Builder(this);
    }

    // equals, hashCode, toString, getters

    public static class Builder {
        private int price;
        private List<Photo> photos;

        public Builder() {
        }

        private Builder(final Product entity) {
            this.price = entity.price;
            this.photos = entity.photos;
        }

        public Builder withPrice(final int price) {
            this.price = price;
            return this;
        }

        public Builder withPhotos(final Collection<Photo> photos) {
            this.photos = Collections.unmodifiableList(
                    new ArrayList(photos));
            return this;
        }

        public Product build() {
            this.validate();
            return new Product(this);
        }

        private void validate() {
            if (photos == null {
                throw new IllegalStateException("photos is null");
            }
        }
    }
}

As you can see, the object constructor is private, as it is only called from the inner class. Another nice thing about it is the fact it only has one parameter – the builder.

To ensure a list is immutable, a defensive copy is needed. It is usually done in constructor of the object. I chose the setter instead, as it is the only entry point where a mutable list can come from. Also, if you use Builder to change Product’s price, you can reuse photos without copying.

Method validate throws an IllegalStateException whenever anything makes it impossible to build correctly. (Your conventions may differ). I used simple if/throw in the example, however, an utility class can shorten it significantly.

Now, imagine you had an (abstract) super-class for your Product object. I’ve read that you need getters in this case, as you cannot access the (private) fields of the superclass. It would be true, provided that you had to pass the individual parameters to the constructor. But if you pass the builder instead, it is actually easy without them:

class Product extends NamedItem { ...
    private Product(ProductBuilder builder) {
        super(builder);
        ...
    }
    ...
    public static class Builder extends NamedItem.Builder {
        ...
        protected void validate() {
            super.validate();
            ...
        }
    }
}

No getters needed either. So I still haven’t encountered a builder problem that actually requires getters. Have you?

Comments