
Czy wieloparametrowe konstruktory są poprawne?
Wieloparametrowe konstruktory pod względem składni języka są poprawne, za to występują z nimi inne problemy.
W przypadku, gdy mamy konstruktor z większą ilością parametrów np. cztery oraz większość parametrów jest tego samego typu to zaczynają się problemy. Stwórzmy prosty przykład, najzwyklejsza POJO klasa Usera z 6 polami:
package org.blog; public class User { private Long id; private String name; private String lastName; private String login; private String email; private String password; public User(Long id, String name, String lastName, String login, String email, String password) { this.id = id; this.name = name; this.lastName = lastName; this.login = login; this.email = email; this.password = password; } }
Od razu w oczy rzuca się to, że aż 5 pól jest typu String. Przejdźmy do inicjalizacji obiekty typu User:
User user = new User(1L, "Pablo", "Escabo", "Admin", "pablo@example.com", "admin");
Muszę, że poszło mi to nie najgorzej, ale tylko dlatego, że bardzo pomogło mi IDE – IntelliJ Idea. Bez jego pomocy prawdopodobnie pomyliłbym się w kolejności argumentów, albo musiałbym co chwilę sprawdzać w kolejność.
Builder
Najlepszym rozwiązaniem na powyższy problem jest wzorzec projektowy Builder. Nie będę się tutaj rozpisywał jak go implementować, pokażę tylko jego użycie na klasie User. W skrócie polega to na ciągłym budowaniu obiektu poprzez wywoływanie metod z wcześniej utworzonego buildera – w momencie, gdy “ułożymy” wszystkie klocki dochodzi do finalnego zbudowania obiektu. Zazwyczaj oznacza to wywołanie metody build.
package org.blog; public class User { private Long id; private String name; private String lastName; private String login; private String email; private String password; private User(Long id, String name, String lastName, String login, String email, String password) { this.id = id; this.name = name; this.lastName = lastName; this.login = login; this.email = email; this.password = password; } public static class Builder { private Long id; private String name; private String lastName; private String login; private String email; private String password; public Builder id(Long id) { this.id = id; return this; } public Builder name(String name) { this.name = name; return this; } public Builder lastName(String lastName) { this.lastName = lastName; return this; } public Builder login(String login) { this.login = login; return this; } public Builder email(String email) { this.email = email; return this; } public Builder password(String password) { this.password = password; return this; } public User build() { return new User(id, name, lastName, login, email, password); } } }
Oczywiście tworzenie obiektu wygląda teraz całkiem inaczej – na pewno czytelniej.
User user = new User.Builder() .id(1L) .name("Pablo") .lastName("Escabo") .login("admin") .email("pablo@example.com") .password("admin") .build();
Jedynym problemem jest oczywiście utrzymywanie klasy Builder – wraz ze zmianą pól w klasie User musimy zmieniać również strukturę klasy Builder. Nie ma się co martwić i na to jest rozwiązanie – wystarczy skorzystać z tzw. Lomboka. Lombok jest biblioteką, która robi za nas wiele podając nad klasą tylko odpowiednią adnotację. Generuje np.:
- settery i gettery,
- konstruktory,
- hashCode i equals,
- toString,
- builder.
Czyli te rzeczy, których nie chcemy robić ręcznie. My chcemy pisać logikę biznesową. 😉
Podsumowując najlepiej jest tworzyć konstruktory maksymalnie z 3 parametrami – w przeciwnym wypadku warto korzystać ze wzorca projektowego budowiczny. Oczywiście ta zasada nie dotyczy tzw. beanów w Springu, które są zarządzane przez Spring Container – czyli nie są tworzone poprzez operator new.
Warto wspomnieć, że chcąc używać Lomboka w IntelliJ warto zainstalować sobie plugin do lomboka, abyśmy mogli od razu otrzymywać podpowiedzi o generowanych metodach.
Więcej podobnych zasad czystego kodu możesz znaleźć w książce “Czysty Kod” Roberta C. Martina.