Monday, March 10, 2014

Leave the construction to builder!

Why do we need builders? Lets say we have a class Pizza

public Pizza {
  private String flour;
  private String type;
  private String topping;
  private String seasoning;
}

Let's say the topping and seasoning are optional. You are a merciless person if you have constructors like these to construct a Pizza.

public Pizza(String flour, String type, String topping, String seasoning) {
}

public Pizza(String flour, String type, String topping) {
}

public Pizza(String flour, String type, String seasoning) {
}

public Pizza(String flour, String type) {
}
I would end up constructing an "Oregono pizza topped with wheat flour with a tomato seasoning". This might be the worst pizza you could ever have.

A builder would've been handy here. The top reasons we should go for a builder are,
  • Ambiguous class construction. Similar typed fields in Pizza class make its construction ambiguous for the clients.
  • If you want to avoid setters and make your objects thread safe especially when there are mandatory and optional fields in your class.
  • Too many fields and you don't want to make your constructor look like a cargo train.
Let's rewrite the Pizza class with a builder.

public class Pizza {
  private final String flour;
  private final String type;
  private final String topping;
  private final String seasoning;

  private Pizza(PizzaBuilder builder) {
    ValidateNotNull(builder.flour); 
    ValidateNotNull(builder.type);
    this.flour = builder.flour;
    this.type = builder.type;
    this.topping = builder.topping;
    this.seasoning = builder.seasoning;
  }
  
  // have getters for fields

  pubic static PizzaBuilder getPizzaBuilder() {
     return new PizzaBuilder();
  }

  private class PizzaBuilder {
    private String flour;
    private String type;
    private String topping;
    private String seasoning;
    
    public void withFlour(String flour) {
 this.flour = flour;
        return this;
    }
    public PizzaBuilder withType(String type) {
 this.type = type; 
        return this;
    }
    public PizzaBuilder withTopping(String topping) {
 this.topping = topping;
        return this;
    }
    public PizzaBuilder withSeasoning(String seasoning) {
 this.seasoning = seasoning;
        return this;
    }

    public Pizza build() {
        return new Pizza(this);
    }
  } 
}
Now the clients can construct a pizza like,

Pizza myFavoritePizza = Pizza.getPizzaBuilder().withFlour("Wheat").withType("Cheese").withSeasoning("Oregano").build();
This looks way better than our first approach. Isn't it? No other thread can mess with myFavoritePizza :)

~Surya