Niezmienne obiekty – po co nam one?

O ile pojęcie niezmiennych obiektów nie brzmi zbyt znajomo, to o tyle na pewno słyszałeś o Immutable Object. I o tym właśnie będzie ten wpis.

Czym jest Immutable Object?

Tutaj nie ma żadnego haczyka i nazwa dokładnie wskazuje o czym mówimy. Immutable object jest to niezmienny obiekt, czyli taki, którego stan nie może zostać zmieniony cały okres życia obiektu. Czyli po prostu tworzymy nasz obiekt wraz ze wszystkimi wymaganymi atrybutami i żadnego z nich nie możemy zmienić. Przynajmniej w teorii :)… ale zmiany poprzez refleksje się nie liczą, więc uznajemy, że nie mamy możliwości zmiany wartości tych atrybutów 😉

Jakie są zalety i wady tego podejścia?

Niezmienne obiekty, jak wszystko ;), mają zalety i wady. Do najważniejszych zalet według mnie należą:

  • Są łatwiejsze w użyciu i testowaniu
  • Można je bezpiecznie używać w Setach lub jako klucz w Mapach
  • Mogą być łatwo cachowane
  • Immutable object mogą być bezpiecznie używane w programowaniu wielowątkowym. Stan tych obiektów nie może ulec zmianie, więc mamy pewność, że każdy wątek widzi aktualny stan obiektu 😉

Wady Immutable Object:

  • Nadmiarowy kod – musimy dopisać kilka finali, ale za to pozbywamy się setterów 😉
  • Inicjalizacja wszystkich pól przez konstruktory. Ponieważ wszystkie nasze pola są oznaczone jako final, muszą więc zostać zainicjalizowane w konstruktorze. A co jak nie chcemy zawsze podawać wartości wszystkich parametrów, tylko użyć domyślnych wartości dla niektórych pól? Wtedy musimy stworzyć osobny konstruktor dla każdej kombinacji pól, którą chcemy użyć.
  • Problem z wydajnością – za każdym razem, gdy chcemy wprowadzić zmianę w naszym obiekcie to sprowadza się to do utworzenia nowego obiektu. Może to być odczuwalne zarówno w czasie działania aplikacji, jak i zużyciu pamięci.

Kiedy ich używać?

Jest na pewno kilka podstawowych use-caseów, kiedy powinniśmy rozważyć użycie niezmiennych obiektów:

  • programowanie wielowątkowe – jeżeli mamy obiekt, który ma być współdzielony pomiędzy wątkami to zdecydowanie warto rozważyć użycie immutable object
  • obiekt używany jako klucz (np. w mapach) – mamy wtedy pewność, że klucz nie zostanie zmieniony kiedy jest już w użyciu i nie będzie kolizji
  • obiekt ma być typowym ‚value object’ – wydaje się oczywiste 😉

Z drugiej strony powinniśmy skłaniać się ku ‚standardowym obiektom’, kiedy mamy do czynienia:

  • z dużymi obiektami, których tworzenie zajmie dużo czasu i/lub pamięci
  • obiektami, które posiadają ‚tożsaność’, tzn. reprezentują osoby/rzeczy, dla których zmiana pewnych parametrów jest naturalna np. samochód, dla którego naturalnymi jest zmiana takich parametrów jak prędkość czy poziom paliwa.

Implementacja Immutable Object w Javie

Stworzenie Immutable Object w Javie jest dosyć proste. Wystarczy przestrzegać kilku wskazówek 🙂

  1. Wszystkie pola powinny posiadać modyfikatory private i final.
  2. Nie tworzymy setterów (wynika to zresztą z użycia modyfikatora final przy polach).
  3. Musimy zabezpieczyć naszą klasę, żeby nie można było po niej dziedziczyć.
  4. Jeżeli pola naszej klasy zawierają mutable object, wtedy musimy zabezpieczyć te obiekty przed zmianą.

Pora na przykład, który zobrazuje nam powyższe zasady.

Trochę optymalizacji

Jeżeli nasz niezmienny object będzie na prawdę często używany w naszej aplikacji możemy rozważyć pewne optymalizacje.

Jedną z technik, którą możemy użyć jest Pooling. Jest on np. użyty w JVM w przypadku Stringów. Poniżej przedstawię bardzo prymitywną implementacje tego podejścia.

Na początku stwórzmy jeszcze nasz immutable object, którego użycie będizemy chcieli zoptymalizować.

I teraz prosty mechanizm poolingu:

Ogólnie mówiąc ideą jest powtórne użycie wcześniej utworzonego obiektu, jeżeli jest taki sam jak byśmy chcieli stworzyć.

Przy używaniu poolingu powinniśmy mieć na uwadze, że o ile w środowisku jednowątkowym powinien sprawdzić się całkiem nieźle, o tyle przy wielu wątkach narzut na synchronizacje może być znaczący.

Podsumowanie

We wpisie postarałem przedstawić się ideę niezmiennych obiektów, ich wady oraz zalety. Pokazałem również jak zaimplementować taki obiekt w Javie oraz zaproponowałem pewną optymalizacje, którą możemy użyć, aby zwiększyć wydajność naszej aplikacji w przypadku częstego korzystania z naszego immutable object.

UPDATE 14.05.2017

Kolega zwrócił mi uwagę na błąd, który był w listingu z implementacją niezmiennej klasy w Javie – teraz wszystko powinno być ok 🙂

Zwrócił również uwagę, że warto dodać wzmiankę o poolingu w środowisku wielowątkowym – dodane 🙂

Dzięki 🙂

2 przemyślenia nt. „Niezmienne obiekty – po co nam one?”

  1. „Kiedy ich używać?”
    Odpowiedź powinna brzmieć – kiedy tylko to możliwe.
    Ponadto, gdy pracujemy w pełni z niemutowalnymi obiektami, ich kopiowanie opiera się na tzw. „structural sharing”. Warto również podkreślić, że JVM bardzo dobrze sobie radzi z „short-lived” obiektami.

    Oczywiście, są wyjątki od tej reguły i wtedy zaczynamy myśleć o odpowiednikach mutowalnych, ale dopiero gdy jesteśmy przekonani, że to stanowi bottleneck aplikacji.

    Wydaje mi się również, że taka dyskusja (mutability/immutability) jest ciekawsza z punktu widzenia persystentnych struktur danych aniżeli czystych Plain Objectów.

    Niemniej jednak, post warty uwagi dla wielu osób niezaznajomionych z tematem (immutability jest rdzeniem paradygmatu funkcyjnego).

    1. Zgadzam się w pełni – powinniśmy używać kiedy to możliwe. Przynajmniej w idealnym świecie 😉

Dodaj komentarz

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