Builder

Wzorzec budowniczy (ang. builder) należy do grupy wzorców konstrukcyjnych. Używa się go do hermetyzowania tworzenia produktu oraz w celu umożliwienia jego wieloetapowego inicjowania. Jego diagram klas prezentuje się następująco:

Rys. 1 Diagram UML klas wzorca budowniczy

Abstrakcyjna klasa Builder odpowiedzialna jest za dostarczenie interfejsu wymaganego do poprawnego utworzenia obiektu klasy Product. Klasa (lub klasy) ConcreteBuilder implementują abstrakcyjny interfejs i tworzą właściwe produkty. Opcjonalnie, uzupełnieniem struktury wzorca jest Director, czyli kierownik. Odpowiada on za zlecanie konstrukcji produktów wykorzystując do tego obiekt Builder. Kontroluje także algorytm odpowiedzialny za utworzenie utworzenie obiektu finalnego produktu.

Jako że jestem wielkim fanem pizzy, to przykład będzie dotyczył właśnie niej. Jest wiele odmian tego prostego i lubianego dania. Utworzymy dwie różniące się dodatkami pizze. Skorzystamy także z odmiany wzorca budowniczy bez klasy kierownika.

Zacznijmy od utworzenia klasy Pizza, czyli naszego produktu. Dla uproszczenia jej atrybuty będą typu String. Nic nie stoi jednak na przeszkodzie, by były to obiekty:

public class Pizza {
    String dough;
    String cheese;
    String meat;
    String mushrooms;
    String sauce;
 
    public String toString() {
        return  "pizza ingredients:\nDough - " + this.dough + ",\nSauce: " + this.sauce + ",\nCheese: " + this.cheese + ",\nMeat: " + this.meat+"," + "\n" + "Mushrooms: " + this.mushrooms;
    }
}

Kolejny krokiem będzie utworzenie interfejsu budowniczego:

public interface AbstractBuilder {
    public void addDough(String dough);
    public void addCheese(String cheese);
    public void addMeat(String meat);
    public void addMushrooms(String mushrooms);
    public void addSauce(String sauce);
    public Pizza makePizza();
}

oraz rzeczywistej klasy budowniczego:

public class PizzaBuilder implements AbstractBuilder {
    Pizza pizza = new Pizza();
 
    public void addDough(String dough) {
        pizza.dough = dough;
    }
 
    public void addCheese(String cheese) {
        pizza.cheese = cheese;
    }
 
    public void addMeat(String meat) {
        pizza.meat = meat;
    }
 
    public void addMushrooms(String mushrooms) {
        pizza.mushrooms = mushrooms;
    }
 
    public void addSauce(String sauce) {
        pizza.sauce = sauce;
    }
 
    public Pizza makePizza() {
        return pizza;
    }
}

Teraz nasz budowniczy gotowy jest do tworzenia nowych obiektów klasy Pizza. Przetestujmy nasze rozwiązanie poprzez utworzenie kilku rodzajów pysznej pizzy:

public class PizzaBuilderTester {
 
    public static void main(String[] args) {
 
        AbstractBuilder italianPizzaBuilder = new PizzaBuilder();
        italianPizzaBuilder.addDough("italian");
        italianPizzaBuilder.addSauce("Marinara");
        italianPizzaBuilder.addCheese("Reggiano");
        italianPizzaBuilder.addMeat("chorizo");
        Pizza italianPizza = italianPizzaBuilder.makePizza();
 
        AbstractBuilder americanPizzaBuilder = new PizzaBuilder();
        americanPizzaBuilder.addDough("american");
        americanPizzaBuilder.addSauce("plum tomato");
        americanPizzaBuilder.addCheese("Mozzarella");
        americanPizzaBuilder.addMeat("ham");
        americanPizzaBuilder.addMushrooms("champaignon");
        Pizza americanPizza = americanPizzaBuilder.makePizza();
 
        System.out.println("Italian " + italianPizza);
        System.out.println("\nAmerican " + americanPizza);
    }
}

Wynik wykonania kodu jest następujący:

Italian pizza ingredients:
Dough - italian,
Sauce: Marinara,
Cheese: Reggiano,
Meat: chorizo,
Mushrooms: null

American pizza ingredients:
Dough - american,
Sauce: plum tomato,
Cheese: Mozzarella,
Meat: ham,
Mushrooms: champaignon

Powyższy przykład pozwala dostrzec zalety płynące ze stosowania wzorca budowniczy. Są to przede wszystkim:

  • tworzenie obiektów w procedurze wielokrokowej i nie narzucanie tej procedury (w odróżnieniu od wzorców z grupy Fabryka)
  • hermetyzacja operacji koniecznych do utworzenia złożonego obiektu
  • możliwość wymiany implementacji produktów, dzięki zastosowaniu abstrakcyjnego interfejsu

W praktyce wzorzec ten często stosuje się do budowania struktur kompozytowych.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *