Obiekty niemutowalne
Zagadnienie, które potrafi pogrążyć sporą grupę kandydatów na naszych rozmowach o pracę. Czym są obiekty niemutowalne? Jakie mają zastosowanie? Jak je stworzyć? Na te pytania znajdziesz odpowiedź w dzisiejszym tekście.
(Nie)zmienność
Praktycznie od początku nauki programowania obiektowego uczysz się o tym jak operować na wartościach znajdujących się w obiektach utworzonych z Twoich klas. Czy to przez dostęp do pól publicznych czy też poprzez metody w klasie. Być może korzystasz z tego bardzo często. W trakcie działania aplikacji modyfikujesz wartości reagując na zdarzenia systemu lub użytkownika.
A co jeżeli powiem Ci, że powinieneś tworzyć obiekty w taki sposób aby modyfikacja ich wartości nie była możliwa? Chcąc mieć obiekt ze zmienionymi wartościami musisz utworzyć nowy obiekt? Porozmawiajmy o obiektach niemutowalnych.
O czym mówimy?
Jak już zapewne się domyśliłeś obiekty niemutowalne to takie, które nie pozwalają na zmianę przechowywanych wartości (stanu) w czasie istnienia tego obiektu. To co ustawiłeś mu przy tworzeniu to zostanie z nim do końca. Jeżeli będziesz chciał aby jakaś wartość obiektu została zmieniona będziesz musiał utworzyć nowy obiekt, któremu przekażesz aktualne dane.
Zastosowanie
Pierwszą zaletą stosowania obiektów niemutowalnych jest wyeliminowanie ryzyka, że przekazanie referencji do naszego obiektu innym klasom i funkcjom spowoduje, że stan tego obiektu zmieni się bez naszej wiedzy. Dobrym przykładem są tutaj obiekty typu DTO (Data Transfer Object). Tworząc je chcemy aby dotarły do adresata, czyli np. endpointu RESTowego, w niezmienionej formie, zachowując wszystkie początkowo ustawione wartości. To tak jak wysyłając list nie chcemy żeby ktokolwiek na poczcie mógł cokolwiek w nim zmienić, niemutowalność listu byłaby bardzo pożądana.
Kolejną ważną zaletą obiektów niemutowalnych jest możliwość ich „bezpiecznego” używania w aplikacjach wielowątkowych. Jeżeli zwykły obiekt z publicznymi seterami wpuścimy w aplikację wielowątkową to bardzo szybko może dojść do race condition bo wiele wątków będzie modyfikować wartości obiektu w tym samym czasie. Wprowadzając obiekty niezmienne zapewniamy, że każdy wątek chcący zmodyfikować przekazany obiekt będzie musiał utworzyć na jego podstawie nowy i to jemu przypisać nowe wartości. Dzięki temu referencja do oryginalnego obiektu zachowa pierwotny stan we wszystkich innych wątkach.
Ostatnim punktem jest możliwość optymalizacji aplikacji. Czy to po stronie kompilatora, który może wprowadzić pewne uproszczenia jeżeli zobaczy, że obiektu nie da się zmienić. Czy to ręcznie, kiedy możemy np. pominąć pewne sprawdzenia wiedząc, że jeżeli mamy tą samą referencję to na pewno mamy ustawione na początku wartości. Dobrym przykładem jest tutaj Angular. Jeżeli założymy, że obiekty, które przekazujemy do komponentu są immutable i jednocześnie stan komponentu zależy tylko od tych wartości to możemy zastosować prostą optymalizację. Wystarczy, że powiemy Angularowi, żeby sprawdzał stan komponentu i odświeżał jego zawartość tylko jeżeli zmieni się referencja do obiektu przekazanego jako parametr. Dzięki temu wyłączamy ten komponent z ciągłego, cyklicznego sprawdzania wszystkich wartości wszystkich zmiennych.
Jak je utworzyć?
W większości języków obiektowych stworzenie klasy, która pozwoli tworzyć obiekty niemutowalne jest proste albo nawet bardzo proste. Wystarczy zrobić prywatne pola, których wartości będą ustawiane przez konstruktor, a następnie dodać do nich (w razie potrzeby oczywiście!) publiczny getter.
W języku C#, który posiada wygodne gettery i settery sprawa jest jeszcze prostsza:
class MyClass { public MyClass(string name) { Name = name; } public string Name {get; private set;} }
W nowszych standardach tego języka możemy nawet pominąć prywatny setter, który od tej pory jest domyślnie dodawany:
class MyClass { public MyClass(string name) { Name = name; } public string Name {get;} }
I tyle. Chcąc zmienić wartość Name musimy utworzyć nowy obiekt.
Leave a Comment