Wzorzec budowniczy zawsze da rade

Bob Budowniczy to bajka, którą zarówno ja, jak i wielu moich rówieśników uwielbiało oglądać w czasach, gdy na chleb mówiliśmy „bep”. Natrafiając na wzorzec budowniczy, była to pierwsza rzecz z jaką go skojarzyłem. Być może wzorzec ten nie „da rady” rozwiązać każdego problemu, niczym znany i lubiany Bob, aczkolwiek znajdzie wiele zastosowań.

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

Diagram UML klas wzorca budowniczy
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.

Cały kod z wpisu znajduje się na moim Githubie.

Źródła literaturowe:
[1] Eric Freeman, Elisabeth Freeman, Kathy Sierra, Bert Bates: Wzorce projektowe. Rusz głową!
[2] https://howtodoinjava.com/design-patterns/creational/builder-pattern-in-java/
[3] http://www.blackwasp.co.uk/Builder.aspx

5 thoughts on “Wzorzec budowniczy zawsze da rade”

  1. Thanks for one’s marvelous posting! I truly enjoyed reading it, you are a great author.I will make
    sure to bookmark your blog and will come back very
    soon. I want to encourage you continue your great posts, have a nice evening!

  2. Exceⅼlent beat ! I wish to apprentice at the same time as you amend your web site, how can i ѕubscribe for a weblog weЬsite?
    The account helped mе a acceptable deal. Ӏ were a little bit acquainted of this your broadcast provided vibrant transparent concept

Zostaw komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *