Wzorzec projektowy budowniczy

Przykład zastosowania

Załóżmy że tworzymy projekt w oparciu o spring boot i w naszej aplikacji wykorzystujemy FreeMarker. Jednak nasz projekt jest tworzony przez dwie osoby. Pierwszą jesteśmy my sami i odpowiadamy za przygotowanie back end a druga tworzy projekt w FreeMarker i odpowiada za front end. Naszym zadaniem jest przekazanie dla tej drugiej osoby klasy modelu z którego FreeMarker będzie pobierał dane do wyświetlania. Oczywiście można zastosować zwykłą klasę która przekażemy do front endu ale ma to jeden wielki minus. Przez pomyłkę ta druga osoba może nadpisać nam dane, a znalezienie takiego błędy często graniczy z cudem. Dlatego w takim przypadku najlepszym rozwiązaniem jest wzorzec projektowy budowniczy.

Ktoś jednak powie jaki ma to sens skoro możemy przekazać dane w konstruktorze i nie tworzyć seterów. Ja również się z tym zgadzam. Jeżeli mam do przekazania dwie zmienne, to nie ma sensu tworzyć wzorca budowniczy. Inaczej sprawa wygląda gdy mamy do przekazania więcej niż dziesięć zmiennych. Ja osobiście spotkałem się z taką sytuacją gdy tworzyłem formularz do rekrutacji. Kandydat musiał zamieścić 20 różnego rodzaju dokumentów potwierdzających prawo do wyjazdu na Erasmus. W takiej sytuacji wykorzystałem wzorzec, aby poprawić czytelność kodu. Ponieważ bardzo łatwo można się zorientować czy na pewno prawidłowo zostały przekazane pliki do back endu.

Wzorzec projektowy budowniczy – budowa

Stwórzmy więc nasza pierwsza klasę, jednak nie będziemy jej za bardzo rozbudowywać aby łatwiej można było o co chodzi. W pierwszej kolejności tworzymy zwykłą klasę w której jednak nie będzie setterów.

public class Book {

    private String title;
    private String author;
    private int year;
    private String publisher;

    public Campaign(String title, String author, int year, String publisher) {
        this.title = title;
        this.author = author;
        this.year = year;
        this.publisher = publisher;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public int getYear() {
        return year;
    }

    public String getPublisher() {
        return publisher;
    }
}

Czyli co mamy. Po pierwsze tworzymy klasę Book a następnie w tej klasie cztery zmienne prywatne. Dlaczego prywatne aby nie było do nich dostępu z poza klasy. Dostęp będzie dostępny przez metody get których jest również cztery. Tak właśnie wygląda zwykła klasa. Brakuje jedynie metod set dzięki którym można ustawić dane. Jeżeli nie chcemy zmieniać wartości zmiennych po utworzeniu obiektu możemy zastosować właśnie taką konstrukcję a dane ustawiać przy tworzeniu obiektu przez podanie danych startowych w konstruktorze. Tak właśnie jest obecnie zbudowana ta klasa.

Jednak aby ta klasa implementowała wzorzec budowniczy musimy dodać jeszcze jedną metodę:

    public static final class Builder {
        private String title;
        private String author;
        private int year;
        private String publisher;

        private Builder() {
        }

        public Builder title(String title) {
            this.title = title;
            return this;
        }

        public Builder author(String author) {
            this.author = author;
            return this;
        }

        public Builder year(int year) {
            this.year = year;
            return this;
        }

        public Builder publisher(String publisher) {
            this.publisher = publisher;
            return this;
        }

        public Book build() {
            return new Book(this);
        }
    }

Dzięki zastosowaniu tej metody klasa obecnie zasługuje na nazwę budowniczy.

Wzorzec projektowy budowniczy – zastosowanie

Teraz pokażmy po co tak na prawdę ta cała zabawa. Przecież możemy do tego celu wykorzystać zwykły konstruktor. Więc zobaczmy najpierw jak wygląda tworzenie projektu przy wykorzystaniu konstruktora

String title = "Wzorce Projektowe";
String author = "Grzegorz Kossakowski";
int year = 2020;
String publisher = "programowanie.lomza.pl";
Book book = new Book(title, author, year, publisher);

Tak wygląda wykorzystanie konstruktora do budowy obiektu, teraz zajmiemy się tworzeniem obiektu przy zastosowaniu wzorca budowniczy.

String title = "Wzorce Projektowe";
String author = "Grzegorz Kossakowski";
int year = 2020;
String publisher = "programowanie.lomza.pl";

Book book = Book.newBuilder()
    .title(title)
    .author(author)
    .year(year)
    .publisher(publisher)
    .build();

Teraz można wreszcie przyjrzeć się w różnicy implementacji obydwu metod. W tym przypadku może tego tak nie widać ponieważ przekazujemy tylko cztery zmienne ale co by było gdyby było ich na przykład czternaście. Wtedy zalety stosowania wzorca budowniczy będzie bardziej oczywiste.

Wzorzec projektowy budowniczy – cała klasa

public class Book {

    private Book(Builder builder) {
        title = builder.title;
        author = builder.author;
        year = builder.year;
        publisher = builder.publisher;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    private String title;
    private String author;
    private int year;
    private String publisher;

    public Campaign(String title, String author, int year, String publisher) {
        this.title = title;
        this.author = author;
        this.year = year;
        this.publisher = publisher;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public int getYear() {
        return year;
    }

    public String getPublisher() {
        return publisher;
    }

    public static final class Builder {
        private String title;
        private String author;
        private int year;
        private String publisher;

        private Builder() {
        }

        public Builder title(String title) {
            this.title = title;
            return this;
        }

        public Builder author(String author) {
            this.author = author;
            return this;
        }

        public Builder year(int year) {
            this.year = year;
            return this;
        }

        public Builder publisher(String publisher) {
            this.publisher = publisher;
            return this;
        }

        public Book build() {
            return new Book(this);
        }
    }
}