Klasy typu utility / helper

Czyli klasy nieinstancjowalne. Lepiej ich unikać?


Ten wpis jest częścią serii (nowy wpis co sobotę), w której tworzę wpisy w formie notatki z wybranego tematu z książki Effective Java (3rd edition 2018), której autorem jest Joshua Blosch. Jest to uaktualnione wydanie pod Jave 9 jednej z najlepszych książek o Javie. Nie ograniczam się jednak tylko do książki, więc czasem temat będzie rozbudowany i trafią się informacje z innych źródeł na ten sam temat.

Ten wpis nawiązuje do tematu z Item 4 z rozdziału:

Creating and Destroying Objects


Klasy nieinstancjowalne

Klasy nieinstancjowalne to takie, dla których nie możemy utworzyć instancji - czyli używamy ich w sposób statyczny (nie mam tu na myśli klas abstrakcyjnych, które też są nieinstancjowalne). Takie klasy mają dosyć złą reputację, bo często są nadużywane i stosowane tam, gdzie nie powinny. Szczególnie widoczne jest to u początkujących programistów, którzy unikają myślenia obiektowego i mają “proceduralne” nawyki. Jednak takie klasy mają też i swoje akceptowalne zastosowania.

Klasy nieinstancjowalne to zazwyczaj klasy typu utility/helper, które są zbiorem statycznych metod i pól (często operujące na prymitywach). Przykładem takich klas są np. java.lang.Math i java.util.Arrays. Innym przykładem są klasy, które zawierają static method factories dla obiektów o wspólnym interfejsie np. java.util.Collections. (Od Javy 8 można je umieszczać na interfejsie, nie ma potrzeby tworzenia do tego specjalnej klasy). Klasy nieinstancjowalne znajdziemy również w wielu bibliotekach.

Są to klasy, które z założenia nie powinny być instancjowalne - bo po prostu instancja takiej klasy nie ma sensu. Nie przechowują żadnego stanu - są tylko zbiorem często wykorzystywanych metod, które z reguły powinny być ze sobą powiązane (nie ładujemy wszyskiego do jednej klasy, np. metody operujące na stringach nie powinny się mieszać z metodami operującymi na plikach…)

Jedynym słusznym sposobem na to, by klasa była nieinstancjowalna jest zadeklarowanie dla niej prywatnego konstruktora:

// Noninstantiable utility classChapter-1
public class UtilityClass {
    private UtilityClass() {
        throw new AssertionError();
    }
}

Deklaracja klasy jako abstrakcyjnej nie jest odpowiednim sposobem, aby to osiągnąć (ani do końca nie działa). Można ją rozszerzyć i wtedy podklasa może być instancjowalna. Po drugie zachęca to do myślenia, że klasa została stworzona do rozszerzania, a tak nie jest.

Kiedy konstruktor jest prywatny, nie ma możliwości stworzenia instancji klasy poza jej wnętrzem.

throw new AssertionError(); jest tutaj opcjonalnie - zapewnia, aby konstruktor nie zostanie wywołany omyłkowo wewnątrz tej klasy lub poprzez refleksję.

Kiedy implementujemy takie klasy trzeba rozważyć następujące problemy:

Nie ma możliwości rozszerzania klasy z prywatnym konstruktorem.

Trudność w testowaniu jednostkowym.

Podczas gdy klasę utility można łatwo przetestować w izolacji, to klasy, które ją używają już nie. Klasy utility najczęściej wykorzystywane są bezpośrednio (UtilityClass.DoStuff()) co w konsekwencji utrudnia ich mockowanie. Ma to tym większe znaczenie im klasa utility wykonuje cięższe operacje (najczęsciej na , które chcielibyśmy zmockować, aby zwiększyć wydajność testu.

*W Javie istnieje narzędzie do testowania o nazwie Powermock, które to potrafi, ale uznawane jest, że jeśli twoja aplikacja potrzebuję tego narzędzia, aby coś mogło zostać przetestowane, to najprawdopodobniej twoja aplikacja jest źle zaprojektowana i na tym powinieneś się skupić.

Szczególne znaczenie ma to podczas korzystania z frameworków Dependency Injection jak np. Spring. Wtedy takie klasy powinniśmy być w stanie wstrzyknąć jako zależność, żeby później można było łatwo je zmockować. W takich przypadkach powinny to być instancjowalne klasy bez statycznych metod, które można wstrzyknąć jako Singletony.

W Springu beany są Singletonami domyślnie.

Podsumowując, klasy “statyczne” nie są czystym złem, jednak powinny być używane raczej sporadycznie. Zanim zdecydujesz się na ich użycie, upewnij się czy kolejna metoda, którą będziesz chciał dorzucić do klasy typu “helper”, nie powinna się znaleźć w bardziej odpowiedniej i konkretnej klasie.


Jeśli uważasz, że to co robię jest przydatne, polub stronę bloga na Facebooku. Wrzucam tam m.in. informacje o nowych wpisach, o promocjach dla programistów i inne.