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.
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.
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!
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.
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.)