The builder pattern also relies on constructing the object step-by-step, by method calls. Consider making a pizza for example. You first pour the tomato sauce on the dough, then you add cheese, pepperoni and olive oil. Then you bake it. Can you see the steps? If we were to create a pizza with a single constructor, we'd have something like this:
public Pizza(Dough dough, TomatoSauce sauce, Cheese cheese, Pepperoni pepperoni, OliveOil oil)
What if we'd actually want different toppings on the pizza? For example, instead of pepperoni, I'd like to have some sausage on top of the cheese, and I'm not a fan of olive oil. We'd then create another constructor overload for my pizza, right?
public Pizza(Dough dough, TomatoSauce sauce, Cheese cheese, Sausage sausage)
But now I want pepperoni, sausage AND some mushrooms! I think you can already see where this is going: we'd have a different constructor for each set of toppings we'd like on our pizza.
The pizza example is a classic one. With the builder pattern, using a builder object, I can make a pizza with as many toppings as I want! I can also chose whether I want olive oil on it or not.
We then have our pizza, which now has a list of toppings, instead of individual properties for each one of them:
public class Pizza { public Pizza() { Toppings = new List<Topping>(); } public Dough Dough { get; set; } public TomatoSauce TomatoSauce { get; set; } public List<Topping> Toppings { get; set; } public bool HasOliveOil { get; set; } }
Now we can easily implement our builder in a way that it'll be able to make whatever pizza we want:
public class PizzaBuilder { private Pizza Pizza { get; set; } public PizzaBuilder() { Pizza = new Pizza(); } public PizzaBuilder WithThinDough() { Pizza.Dough = new Dough { Type = DoughType.Thin }; return this; } public PizzaBuilder WithThickDough() { Pizza.Dough = new Dough { Type = DoughType.Thick }; return this; } public PizzaBuilder WithFlavoredTomatoSauce() { Pizza.TomatoSauce = new TomatoSauce { Type = TomatoSauceType.Flavored }; return this; } public PizzaBuilder WithTomatoOnlyTomatoSauce() { Pizza.TomatoSauce = new TomatoSauce { Type = TomatoSauceType.TomatoOnly }; return this; } public PizzaBuilder WithOliveOil() { Pizza.HasOliveOil = true; return this; } public PizzaBuilder WithTopping(Topping topping) { Pizza.Toppings.Add(topping); return this; } public Pizza Bake() { return Pizza; } }
Notice that I left the whole responsibility of creating the tomato sauce and dough to the builder, as oppose to how I implemented how the toppings are added. I also created a property for the two first instead of creating sub-types for them. There's no real reason for this, they are just implementation details.
Just imagine now that for each one of the toppings we'd have a subclass. So we'd for example have a class called Sausage which extends the Topping class.
class Sausage : Topping { // [...] }
To get the toppings, I'll just create a class called Toppings which has a set of properties representing each one of the toppings. We could even apply the factory pattern in it.
After having all of these classes, we could then use the builder like this:
var pizzaBuilder = new PizzaBuilder(); var pizza = pizzaBuilder.WithThickDough() .WithFlavoredTomatoSauce() .WithTopping(Toppings.Mozzarella) .WithTopping(Toppings.Sausage) .WithTopping(Toppings.Onion) .WithTopping(Toppings.Mushroom) .WithOliveOil() .Bake();
It's really easier to make pizza now, huh?
Now to forget about pizza and get back to our world, just imagine yourself building a complex XML document by taking advantage of the builder pattern. I bet it wouldn't be that much of a hard task.
No comments:
Post a Comment