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!