Lipiec 25, 2018

Wzorce projektowe Java – Builder

Wzorzec projektowe – Builder

Możliwe, że słyszałeś już gdzieś pojęcie wzorców projektowych lub design patternów. Wzorce są to tak naprawdę uniwersalne rozwiązania konkretnych, często pojawiających się problemów. Wzorzec to nic innego jak opis rozwiązania, a nie konkretna implementacja w danym języku – dlatego wzorce projektowe można zastosować w dowolnym języku obiektowym.

Jaki mamy problem…

Skoro wzorce projektowe opisują rozwiązanie danego problemu, to jaki tak naprawdę mamy problem skoro chcemy użyć Buildera? Już spieszę z wyjaśnieniami, na początku będziemy potrzebować krótką klasę do naszych doświadczeń.

Prosta klasa

Padło na prostą klasę User, która przechowuje podstawowe informacje o użytkowniku (np. portalu, forum) takich jakich: imię, nazwisko, login, hasło, email, płeć oraz wiek. Trochę tych informacji jest – w klasie będą tylko wymienione pola oraz konstruktor, dzięki któremu będziemy mogli je wszystkie zainicjalizować.

public class User {
    private String login;
    private String name;
    private String lastname;
    private String email;
    private String password;
    private int age;
    private Gender gender;

    public User(String login, String name, String lastname, String email, String password, int age, Gender gender) {
        this.login=login;
        this.name = name;
        this.lastname = lastname;
        this.email = email;
        this.password = password;
        this.age = age;
        this.gender = gender;
    }
}

Pojawiło się jeszcze pole Gender – jest to przeze mnie stworzony typ wyliczeniowy.

public enum Gender {
    MALE, FEMALE;
}

Dzięki prostemu enum mogę przypisać do Usera płeć – mógłbym co prawda użyć typu bool, jednak to rozwiązanie jest dużo czytelniejsze. Jeżeli chcesz poznać siłę wykorzystania typu wyliczeniowego to zapoznaj się z tym artykułem.

Mając skonstruowaną taką klasę możemy przejść do kolejnej części.

Gdzie tkwi problem?

Może zdążyłeś już zauważyć, że w naszej klasie User konstruktor wygląda naprawdę brzydko. Ma aż 7 argumentów, z czego pięć po kolei są Stringami!

Choć w klasie może to nie wygląda, aż tak źle – to podczas tworzenia obiektu wystąpi problem.

Muszę przyznać, że przekazanie odpowiednich argumentów do konstruktora User było dla mnie niemałym wyzwaniem – jedynym potwierdzeniem dla mnie były podpowiedzi Intellij Idea – tylko wtedy miałem pewność, że podałem odpowiedni argument.

Naszym problemem do rozwiązania są właśnie konstruktory ze zbyt dużą ilością argumentów! Mówiąc zbyt duża ilość argumentów mam na myśli więcej niż 4. Konstruktor do 3 argumentów jest jak najbardziej w porządku.

Z pomocą do rozwiązania tego problemu przychodzi do nas właśnie wzorzec projektowy Builder.

Builder

Builder jak sama nazwa wskazuje służy do jakiegoś tworzenia – jak łatwo wydedukować – do tworzenia obiektów. Builder musi znajdować w klasie, którą będzie budować, aby była zawarta ścisła więź między nim, a klasą budowaną.

To tyle teorii, zacznijmy od zadeklarowania Buildera w klasie – będzie to po prostu klasa wewnętrzna – najlepiej używać nazwy ze słowem Builder, aby każdy programista od razu wiedział jaki jest cel tej klasy.

public class User {
    private String login;
    private String name;
    private String lastname;
    private String email;
    private String password;
    private int age;
    private Gender gender;

    public User(String login, String name, String lastname, String email, String password, int age, Gender gender) {
        this.login=login;
        this.name = name;
        this.lastname = lastname;
        this.email = email;
        this.password = password;
        this.age = age;
        this.gender = gender;
    }
    
    public class Builder {
        
    }
}

Mając już klasę wewnetrzną czas na zrobienie prywatnego konstruktora – w końcu publiczny nie będzie już używany do tworzenia obiektów.

private User(String login, String name, String lastname, String email, String password, int age, Gender gender) {
    this.login=login;
    this.name = name;
    this.lastname = lastname;
    this.email = email;
    this.password = password;
    this.age = age;
    this.gender = gender;
}

Skoro klasa Builder jest wewnętrzna, a konstruktor jest prywatny to aktualnie mamy problem, aby dostać się do klasy Builder. Aby temu zaradzić klasa Builder musi być statyczna.

public static class Builder {

}

Dzięki temu zabiegowi możemy już powoli rozpocząć budowanie naszego obiektu:

User user = new User.Builder ...

Czas na zapewnienie potrzebnych metod i pól do zbudowania metod, w naszym Builderze musimy przechowywać również takie same pola jak w Userze. Dodatkowo będzie potrzebny nam konstruktor domyślny, lecz może być także konstruktor z wymaganymi parametrami.

public static class Builder {
    private String login;
    private String name;
    private String lastname;
    private String email;
    private String password;
    private int age;
    private Gender gender;

    public Builder(){
    }

}

Mając już wszystkie pola, czas na utworzenie metod odpowiedzialnych za inicjalizację tych pól. Pierwsza z nich będzie odpowiedzialna za login.

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

Obowiązkowo musisz zwrócić uwagę na zwracany typ – Builder. Jest to bardzo ważna część, ponieważ każda zmiana pola musi „aktualizować” nasz obiekt budujący.

Teraz tworzymy kolejne metody dla pozostałych pól.

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

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

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

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

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

public Builder setGender(Gender gender){
    this.gender=gender;
    return this;
}

Mając już te metody możemy spróbować zbudować obiekt.

User user = new User.Builder().setLogin("admin")

Mamy już dostęp do metod inicjalizujących i możemy je wywoływać dalej…

User user = new User.Builder().setLogin("admin").setName("kamil").setLastname("klimek").setEmail("admin@example.com").setGender(Gender.MALE).setAge(21).setPassword("admin")

Choć rozwiązanie jest dużo czytelniejsze, a może być jeszcze czytelniejsze łamiąc linię podczas tworzenia:

User user = new User.Builder().setLogin("admin")
        .setName("kamil")
        .setLastname("klimek")
        .setEmail("admin@example.com")
        .setGender(Gender.MALE)
        .setAge(21)
        .setPassword("admin")

To jeszcze nie mamy do końca stworzonego obiektu, ciągle zwracamy obiekt Builder.

Końcowe budowanie

Po ustawieniu wszystkich pól nadchodzi czas na finalne budowanie – do tego będziemy potrzebować metody – która zazwyczaj ma nazwę build.

Taką też stworzymy.

public User build(){
    return new User(login, name, lastname, email, password, age, gender);
}

Metoda build jest odpowiedzialna już za stworzenie obiektu User na podstawie wcześniej ustawionych danych i zwróceniem go.

Teraz możemy dokończyć nasze budowanie użytkownika.

User user = new User.Builder().setLogin("admin")
        .setName("kamil")
        .setLastname("klimek")
        .setEmail("admin@example.com")
        .setGender(Gender.MALE)
        .setAge(21)
        .setPassword("admin")
        .build();

I w ten o to sposób stworzyliśmy Buildera do naszej klasy User. Choć trzeba było się trochę namęczyć to zaoszczędzimy sobie i innym programistom nerwów i czasu podczas tworzenia obiektu User.

Podsumowanie

Czy na pewno warto znać i stosować wzorce projektowe? Moim zdaniem tak, pozwalają one rozwiązywać problemy, które mogą spowodować problemy podczas tworzenia lub działania aplikacji. Builder jest jednym z wielu design patternów, istnieją takie wzorce jak: Singleton, Fasada, Fabryka, Obserwator, o których będę pisał w kolejnych artykułach.

Na sam koniec polecam stworzyć sobię klasę z polami prywatnymi, co najmniej 4 i stwórz dla niej Builder, a następnie go przetestuj.