More on Builders, or, favor immutability

A while back I posted a basic Builder example on Stack Overflow. User Marcel Stör complained:

Problem with this approach is that you need to define all the members twice, once in the builder and once in the actual class. [This answer] shows how this can be avoided. With that other approach, however, you cannot have final members although the object itself is still immutable.

Marcel Stör

Well, I looked at that answer, from user Yishai, and I didn’t like it.

I don’t know, I’m not a fan of that approach — the builder’s not reusable, it’s not thread-safe, and you can’t design the build product class to guarantee it’s fully initialized at construction. If I was really worried about DRY, I’d probably use an immutable (copy-on-write) builder and make the last builder instance double as the build product’s immutable data.

David Moles

After I wrote that, I thought I should put some code where my mouth was. So: here’s the original version (more or less), with the fields declared in both the Builder and its product, the Widget:

public class Widget implements IWidget
{
    public static class Builder implements IWidgetBuilder<Widget>
    {
        private String name;
        private String model;
        private String upc;
        private double price;
        private Manufacturer manufacturer;

        public Builder ( String name, double price )
        {
            this.name = name;
            this.price = price;
        }

        @Override
        public Widget build ()
        {
            Widget product = new Widget( this );
            validate( product );
            return product;
        }

        @Override
        public Builder manufacturer ( Manufacturer value )
        {
            this.manufacturer = value;
            return this;
        }

        @Override
        public Builder upc ( String value )
        {
            this.upc = value;
            return this;
        }

        @Override
        public Builder model ( String value )
        {
            this.model = value;
            return this;
        }

        private static void validate ( Widget product )
        {
            assert product.getName() != null : "Product must have a name";
            assert product.getPrice() >= 0 : "Product price cannot be negative";
            Manufacturer mfgr = product.getManufacturer();
            if ( mfgr != null )
            {
                String upc = product.getUpc();
                assert upc != null;
                assert upc.startsWith( mfgr.getCode() ) : "Product UPC does not match manufacturer";
            }
        }
    }

    private final String name;
    private final String model;
    private final String upc;
    private final double price;
    private final Manufacturer manufacturer;

    /**
     * Creates an immutable widget instance.
     */
    private Widget ( Builder b )
    {
        this.name = b.name;
        this.price = b.price;
        this.model = b.model;
        this.upc = b.upc;
        this.manufacturer = b.manufacturer;
    }

    public Manufacturer getManufacturer ()
    {
        return manufacturer;
    }

    public String getModel ()
    {
        return model;
    }

    public String getName ()
    {
        return name;
    }

    public double getPrice ()
    {
        return price;
    }

    public String getUpc ()
    {
        return upc;
    }
}

And here’s the new version: the fields are only declared in the Builder, and they’re always final. The Builder itself is immutable, using a copy-on-write pattern, and the last Builder instance becomes the state of the Widget it produces.

public class Widget2 implements IWidget
{
    public static class Builder implements IWidgetBuilder<Widget2>
    {
        private final String name;
        private final String model;
        private final String upc;
        private final double price;
        private final Manufacturer manufacturer;

        public Builder ( String name, double price )
        {
            this( name, null, null, price, null );
        }

        private Builder ( String name, String model, String upc, double price, Manufacturer manufacturer )
        {
            this.manufacturer = manufacturer;
            this.model = model;
            this.name = name;
            this.price = price;
            this.upc = upc;
        }

        public Widget2 build ()
        {
            Widget2 product = new Widget2( this );
            validate( product );
            return product;
        }

        public Builder manufacturer ( Manufacturer value )
        {
            return new Builder( this.name, this.model, this.upc, this.price, value );
        }

        public Builder upc ( String value )
        {
            return new Builder( this.name, this.model, value, this.price, this.manufacturer );
        }

        public Builder model ( String value )
        {
            return new Builder( this.name, value, this.upc, this.price, this.manufacturer );
        }

        private static void validate ( Widget2 product )
        {
            assert product.getName() != null : "Product must have a name";
            assert product.getPrice() >= 0 : "Product price cannot be negative";
            Manufacturer mfgr = product.getManufacturer();
            if ( mfgr != null )
            {
                String upc = product.getUpc();
                assert upc != null;
                assert upc.startsWith( mfgr.getCode() ) : "Product UPC does not match manufacturer";
            }
        }

    }

    private final Builder b;

    /**
     * Creates an immutable widget instance.
     */
    private Widget2 ( Builder b )
    {
        this.b = b;
    }

    @Override
    public Manufacturer getManufacturer ()
    {
        return b.manufacturer;
    }

    @Override
    public String getModel ()
    {
        return b.model;
    }

    @Override
    public String getName ()
    {
        return b.name;
    }

    @Override
    public double getPrice ()
    {
        return b.price;
    }

    @Override
    public String getUpc ()
    {
        return b.upc;
    }
}

Voilà, DRY. Sadly, it only ends up one line shorter (thanks to the extra Builder constructor), but at least it doesn’t define anything twice!

2 thoughts on “More on Builders, or, favor immutability

  1. Well, making builder immutable is nice, but in this case
    do you really need Builder?

    I guess NO as you could make the class immutable same way without using builder, just copy on write every time when you need modify the object. I would use Builder only in case you provide methods with more complex building process.

  2. Sure, I mean, obviously this is a toy case. As far as I’m concerned Builder is mostly a hacky workaround for Java’s lack of named parameters, and for this few parameters you probably don’t need names. (Besides which in a real world app they probably wouldn’t all be raw Strings.)

Leave a comment