Nie jestem w stanie znaleźć scenariuszy, w których potrzebujemy niezmiennej klasy.
Czy kiedykolwiek spotkałeś się z takim wymaganiem? czy możesz nam podać prawdziwy przykład, w którym powinniśmy użyć tego wzoru.
java
design-patterns
immutability
Rakesh Juyal
źródło
źródło
Odpowiedzi:
Inne odpowiedzi wydają się zbyt skoncentrowane na wyjaśnianiu, dlaczego niezmienność jest dobra. Jest bardzo dobry i używam go, kiedy tylko jest to możliwe. Jednak to nie jest twoje pytanie . Postaram się odpowiedzieć na Twoje pytanie punkt po punkcie, aby upewnić się, że otrzymujesz odpowiedzi i przykłady, których potrzebujesz.
„Potrzeba” jest tutaj pojęciem względnym. Niezmienne klasy to wzorzec projektowy, który podobnie jak każdy paradygmat / wzorzec / narzędzie ma na celu ułatwienie tworzenia oprogramowania. Podobnie, wiele kodu zostało napisanych przed pojawieniem się paradygmatu OO, ale zalicz mnie do programistów, którzy „potrzebują” OO. Niezmienne klasy, takie jak OO, nie są ściśle potrzebne , ale będę zachowywał się tak, jakbym ich potrzebował.
Jeśli nie patrzysz na obiekty w domenie problemowej z właściwej perspektywy, możesz nie widzieć wymagania dotyczącego niezmiennego obiektu. Możesz łatwo pomyśleć, że problematyczna domena nie wymaga żadnych niezmiennych klas, jeśli nie wiesz, kiedy ich używać.
Często używam niezmiennych klas, w których myślę o danym obiekcie w mojej domenie problemowej jako o wartości lub stałej instancji . Pojęcie to jest czasami zależne od perspektywy lub punktu widzenia, ale idealnie byłoby łatwo przełączyć się na właściwą perspektywę, aby zidentyfikować dobre obiekty kandydujące.
Możesz lepiej zrozumieć, gdzie niezmienne obiekty są naprawdę przydatne (jeśli nie są absolutnie konieczne), czytając różne książki / artykuły online, aby zrozumieć, jak myśleć o niezmiennych klasach. Jednym z dobrych artykułów na początek jest teoria i praktyka Javy: mutować czy nie mutować?
Spróbuję podać kilka przykładów poniżej, jak można zobaczyć obiekty z różnych perspektyw (zmienne vs niezmienne), aby wyjaśnić, co rozumiem przez perspektywę.
Ponieważ poprosiłeś o prawdziwe przykłady, podam ci kilka, ale najpierw zacznijmy od klasycznych przykładów.
Klasyczne obiekty wartości
Łańcuchy i liczby całkowite są często traktowane jako wartości. Dlatego nie jest zaskakujące, że klasa String i klasa opakowania typu Integer (a także inne klasy opakowania) są niezmienne w Javie. Kolor jest zwykle traktowany jako wartość, stąd niezmienna klasa Color.
Przeciwprzykład
W przeciwieństwie do tego, samochód zwykle nie jest postrzegany jako obiekt wartościowy. Modelowanie samochodu zwykle oznacza tworzenie klasy, której stan zmienia się (licznik kilometrów, prędkość, poziom paliwa itp.). Istnieją jednak domeny, w których samochód może być obiektem wartości. Na przykład samochód (a konkretnie model samochodu) może być traktowany jako obiekt wartościowy w aplikacji do wyszukiwania odpowiedniego oleju silnikowego do danego pojazdu.
Grać w karty
Napisałeś kiedyś program do gry w karty? Zrobiłem. Mogłem przedstawić kartę do gry jako zmienny obiekt o zmiennym kolorze i randze. Ręka w pokerze draw-poker może mieć 5 stałych przypadków, w których zastąpienie piątej karty w moim ręku oznaczałoby mutację piątej instancji karty gry na nową kartę poprzez zmianę jej koloru i rangi ivars.
Jednak myślę o karcie do gry jako o niezmiennym przedmiocie, który po utworzeniu ma stały, niezmienny kolor i rangę. Mój układ draw poker miałby 5 instancji, a zastąpienie karty w mojej ręce wymagałoby odrzucenia jednej z tych instancji i dodania nowej losowej instancji do mojej ręki.
Rzutowanie mapy
Ostatni przykład to praca nad kodem mapy, w którym mapa mogłaby się wyświetlać w różnych rzutach . W oryginalnym kodzie mapa używała stałej, ale modyfikowalnej instancji projekcji (tak jak powyższa mutowalna karta do gry). Zmiana odwzorowania mapy oznaczała zmianę elementów ivar instancji odwzorowania mapy (typ odwzorowania, punkt środkowy, powiększenie itp.).
Jednak czułem, że projekt był prostszy, gdy pomyślałem o projekcji jako o niezmiennej wartości lub stałym wystąpieniu. Zmiana odwzorowania mapy oznaczała, że mapa odnosi się do innej instancji odwzorowania, zamiast modyfikować stałą instancję odwzorowania mapy. Ułatwiło to również przechwytywanie nazwanych projekcji, takich jak
MERCATOR_WORLD_VIEW
.źródło
Niezmienne klasy są generalnie znacznie prostsze w projektowaniu, implementowaniu i używaniu poprawnie . Przykładem jest String: implementacja
java.lang.String
jest znacznie prostsza niżstd::string
w C ++, głównie ze względu na niezmienność.Jednym szczególnym obszarem, w którym niezmienność robi szczególnie dużą różnicę, jest współbieżność: niezmienne obiekty mogą być bezpiecznie współużytkowane przez wiele wątków , podczas gdy zmienne obiekty muszą być bezpieczne dla wątków poprzez staranny projekt i implementację - zwykle nie jest to trywialne zadanie.
Aktualizacja: Effective Java 2nd Edition szczegółowo omawia ten problem - zobacz punkt 15: Minimalizuj zmienność .
Zobacz także te powiązane posty:
źródło
Efektywna Java autorstwa Joshua Blocha przedstawia kilka powodów, dla których warto pisać niezmienne klasy:
Ogólnie rzecz biorąc, dobrą praktyką jest uczynienie obiektu niezmiennym, chyba że w rezultacie wystąpią poważne problemy z wydajnością. W takich okolicznościach mutowalne obiekty builder mogą być używane do budowania niezmiennych obiektów, np. StringBuilder
źródło
Hashmapy to klasyczny przykład. Klucz do mapy musi być niezmienny. Jeśli klucz nie jest niezmienny i zmienisz wartość w kluczu tak, że funkcja hashCode () dałaby nową wartość, mapa jest teraz zepsuta (klucz znajduje się teraz w niewłaściwym miejscu w tabeli skrótów).
źródło
Java to praktycznie jedna i wszystkie referencje. Czasami odwołuje się do instancji wiele razy. Jeśli zmienisz taką instancję, zostanie to odzwierciedlone we wszystkich jej odniesieniach. Czasami po prostu nie chcesz tego mieć, aby poprawić solidność i bezpieczeństwo wątków. Wtedy niezmienna klasa jest przydatna, więc trzeba utworzyć nową instancję i ponownie przypisać ją do bieżącego odwołania. W ten sposób oryginalne wystąpienie innych odniesień pozostaje nietknięte.
Wyobraź sobie, jak wyglądałaby Java, gdyby
String
była zmienna.źródło
Date
iCalendar
były zmienne. Och, czekaj, oni są, OH SHString
jest zmienna! (wskazówka: niektóre starsze wersje JRockita). Wywołanie string.trim () spowodowało obcięcie oryginalnego ciąguNie potrzebujemy niezmiennych klas jako takich, ale z pewnością mogą one ułatwić niektóre zadania programistyczne, zwłaszcza gdy zaangażowanych jest wiele wątków. Nie musisz wykonywać żadnego blokowania, aby uzyskać dostęp do niezmiennego obiektu, a wszelkie fakty dotyczące takiego obiektu, które już ustaliłeś, będą nadal aktualne w przyszłości.
źródło
Weźmy skrajny przypadek: stałe całkowite. Jeśli napiszę takie stwierdzenie, jak „x = x + 1”, chcę być w 100% przekonany, że liczba „1” w jakiś sposób nie stanie się 2, bez względu na to, co stanie się gdziekolwiek indziej w programie.
No dobra, stałe całkowite nie są klasą, ale koncepcja jest taka sama. Załóżmy, że napiszę:
Wygląda dość prosto. Gdyby jednak ciągi znaków nie były niezmienne, musiałbym wziąć pod uwagę możliwość, że getCustomerName może zmienić customerId, tak że gdy wywołuję getCustomerBalance, otrzymuję saldo dla innego klienta. Teraz możesz powiedzieć: „Dlaczego na świecie ktoś piszący funkcję getCustomerName powoduje zmianę identyfikatora? To nie miałoby sensu”. Ale właśnie tam możesz wpaść w kłopoty. Osoba pisząca powyższy kod może uznać za oczywiste, że funkcje nie zmienią parametru. Następnie pojawia się ktoś, kto musi zmodyfikować inne użycie tej funkcji, aby obsłużyć przypadek, w którym klient ma wiele kont o tej samej nazwie. A on mówi: „Och, oto przydatna funkcja getCustomer name, która już wyszukuje nazwę.
Niezmienność oznacza po prostu, że pewna klasa obiektów jest stałymi i możemy je traktować jako stałe.
(Oczywiście użytkownik może przypisać inny "stały obiekt" do zmiennej. Ktoś może napisać String s = "cześć"; a później napisać s = "do widzenia"; O ile zmienna nie zostanie ostateczna, nie mam pewności że nie jest zmieniany w moim własnym bloku kodu. Tak jak stałe całkowite zapewniają mnie, że „1” jest zawsze tą samą liczbą, ale nie, że „x = 1” nigdy nie zostanie zmienione przez napisanie „x = 2”. Ale ja można ufać, że jeśli mam uchwyt do niezmiennego obiektu, to żadna funkcja, do której go przekazuję, nie może go zmienić na mnie, lub że jeśli wykonam jego dwie kopie, to zmiana zmiennej przechowującej jedną kopię nie zmieni inne itp.
źródło
Istnieje wiele powodów niezmienności:
String
. Klasa.Tak więc, jeśli chcesz przesyłać dane przez usługę sieciową i chcesz mieć pewność, że uzyskasz wynik dokładnie taki sam, jak wysłany, ustaw go jako niezmienny.
źródło
final
w Javie są niezmienne i nie wszystkie niezmienne klasy są oflagowanefinal
.Zaatakuję to z innej perspektywy. Uważam, że niezmienne obiekty ułatwiają mi życie podczas czytania kodu.
Jeśli mam zmienny obiekt, nigdy nie jestem pewien, jaka jest jego wartość, jeśli kiedykolwiek zostanie użyty poza moim bezpośrednim zakresem. Powiedzmy, że tworzę
MyMutableObject
zmienne lokalne metody, wypełniam je wartościami, a następnie przekazuję do pięciu innych metod. KAŻDA z tych metod może zmienić stan mojego obiektu, więc musi wystąpić jedna z dwóch rzeczy:Pierwsza utrudnia rozumowanie na temat mojego kodu. Drugi sprawia, że mój kod jest obciążony wydajnością - i tak naśladuję niezmienny obiekt z semantyką kopiowania przy zapisie, ale robię to cały czas, niezależnie od tego, czy wywołane metody faktycznie modyfikują stan mojego obiektu.
Jeśli zamiast tego użyję
MyImmutableObject
, mogę mieć pewność, że ustalę wartości, które będą obowiązywać przez cały okres mojej metody. Nie ma „upiornej akcji na odległość”, która zmieniłaby to spod mnie i nie ma potrzeby, abym robił obronne kopie mojego obiektu przed wywołaniem pięciu innych metod. Jeśli inne metody chcą zmienić coś dla swoich celów , muszą wykonać kopię - ale robią to tylko wtedy, gdy naprawdę muszą wykonać kopię (w przeciwieństwie do mojego robienia tego przed każdym wywołaniem metody zewnętrznej). Oszczędzam sobie zasobów umysłowych związanych z śledzeniem metod, których może nawet nie być w moim obecnym pliku źródłowym, i oszczędzam systemowi narzutu na niekończące się tworzenie niepotrzebnych kopii obronnych na wszelki wypadek.(Jeśli wyjdę poza świat Javy i wejdę między innymi w świat C ++, mogę być jeszcze trudniejszy. Mogę sprawić, że obiekty będą wyglądać tak, jakby były zmienne, ale za kulisami sprawię, że będą przezroczyste klonować na dowolnym rodzaj zmiany stanu - to jest kopiowanie przy zapisie - nikt nie jest mądrzejszy).
źródło
Moje 2 centy dla przyszłych gości:
2 scenariusze, w których niezmienne obiekty są dobrym wyborem, to:
W wielowątkowości
Problemy ze współbieżnością w środowisku wielowątkowym można bardzo dobrze rozwiązać przez synchronizację, ale synchronizacja jest kosztowna (nie zagłębiałbym się tutaj w „dlaczego”), więc jeśli używasz niezmiennych obiektów, nie ma synchronizacji w celu rozwiązania problemu współbieżności, ponieważ stan niezmiennych obiektów nie można zmieniać, a jeśli nie można zmienić stanu, wszystkie wątki mogą bezproblemowo uzyskać dostęp do obiektu. Tak więc niezmienne obiekty stanowią doskonały wybór w przypadku obiektów współdzielonych w środowisku wielowątkowym.
Jako klucz dla kolekcji opartych na skrótach
Jedną z najważniejszych rzeczy, na które należy zwrócić uwagę podczas pracy z kolekcją opartą na skrótach, jest to, że klucz powinien być taki,
hashCode()
aby zawsze zwracał tę samą wartość przez cały okres istnienia obiektu, ponieważ jeśli ta wartość zostanie zmieniona, to stary wpis wprowadzony do kolekcji opartej na skrótach użycie tego obiektu nie może zostać odzyskane, dlatego spowodowałoby to wyciek pamięci. Ponieważ stanu niezmiennych obiektów nie można zmienić, więc stanowią doskonały wybór jako klucz w kolekcji opartej na skrótach. Tak więc, jeśli używasz niezmiennego obiektu jako klucza do kolekcji opartej na skrótach, możesz być pewien, że nie będzie z tego powodu wycieku pamięci (oczywiście nadal może wystąpić wyciek pamięci, gdy obiekt używany jako klucz nie jest nigdzie przywoływany inaczej, ale nie o to tutaj chodzi).źródło
Niezmienne obiekty to instancje, których stan nie zmienia się po zainicjowaniu. Użycie takich obiektów jest specyficzne dla wymagań.
Niezmienna klasa jest dobra do buforowania i jest bezpieczna wątkowo.
źródło
Dzięki niezmienności możesz być pewien, że zachowanie / stan podstawowego niezmiennego obiektu nie ulegnie zmianie, dzięki czemu uzyskasz dodatkową zaletę wykonywania dodatkowych operacji:
Możesz z łatwością korzystać z wielu rdzeni / przetwarzania (przetwarzanie współbieżne / równoległe ) (ponieważ kolejność operacji nie będzie już miała znaczenia).
Potrafi buforować kosztowne operacje (ponieważ jesteś pewien tego samego
wyniku).
Potrafi z łatwością debugować (ponieważ historia uruchomienia nie będzie
już problemem )
źródło
Użycie końcowego słowa kluczowego niekoniecznie sprawia, że coś jest niezmienne:
public class Scratchpad { public static void main(String[] args) throws Exception { SomeData sd = new SomeData("foo"); System.out.println(sd.data); //prints "foo" voodoo(sd, "data", "bar"); System.out.println(sd.data); //prints "bar" } private static void voodoo(Object obj, String fieldName, Object value) throws Exception { Field f = SomeData.class.getDeclaredField("data"); f.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); f.set(obj, "bar"); } } class SomeData { final String data; SomeData(String data) { this.data = data; } }
To tylko przykład pokazujący, że słowo kluczowe „final” służy do zapobiegania błędom programisty i niewiele więcej. Podczas gdy ponowne przypisanie wartości bez końcowego słowa kluczowego może łatwo nastąpić przez przypadek, przejście do tej długości w celu zmiany wartości musiałoby być wykonane celowo. Jest tam dla dokumentacji i aby zapobiec błędom programisty.
źródło
Niezmienne struktury danych mogą również pomóc podczas kodowania algorytmów rekurencyjnych. Załóżmy na przykład, że próbujesz rozwiązać problem 3SAT . Jednym ze sposobów jest wykonanie następujących czynności:
Jeśli masz zmienną strukturę reprezentującą problem, to kiedy upraszczasz instancję w gałęzi TRUE, będziesz musiał:
Jeśli jednak zakodujesz to w sprytny sposób, możesz mieć niezmienną strukturę, w której każda operacja zwraca zaktualizowaną (ale wciąż niezmienną) wersję problemu (podobnie do
String.replace
- nie zastępuje ciągu, a po prostu daje nową ). Naiwnym sposobem implementacji tego jest posiadanie "niezmiennej" struktury po prostu skopiowanie i utworzenie nowej dla dowolnej modyfikacji, redukując ją do drugiego rozwiązania, gdy masz zmienną, z całym tym narzutem, ale możesz to zrobić w bardziej skuteczny sposób.źródło
Jednym z powodów „potrzeby” niezmiennych klas jest połączenie przekazywania wszystkiego przez referencję i braku obsługi widoków obiektu tylko do odczytu (tj. C ++
const
).Rozważmy prosty przypadek klasy obsługującej wzorzec obserwatora:
class Person { public string getName() { ... } public void registerForNameChange(NameChangedObserver o) { ... } }
Gdyby
string
nie były niezmienne, nie byłoby możliwe poprawnePerson
zaimplementowanie klasyregisterForNameChange()
, ponieważ ktoś mógłby napisać następujący tekst, skutecznie modyfikując nazwisko osoby bez wywoływania powiadomienia.void foo(Person p) { p.getName().prepend("Mr. "); }
W C ++
getName()
zwracanie aconst std::string&
powoduje zwrócenie przez odwołanie i uniemożliwienie dostępu do mutatorów, co oznacza, że niezmienne klasy nie są konieczne w tym kontekście.źródło
Dają nam również gwarancję. Gwarancja niezmienności oznacza, że możemy je rozwijać i tworzyć nowe wzorce wydajności, które inaczej nie byłyby możliwe.
http://en.wikipedia.org/wiki/Singleton_pattern
źródło
Jedna z cech niezmiennych klas, która nie została jeszcze wywołana: przechowywanie odwołania do głęboko niezmiennego obiektu klasy jest skutecznym sposobem przechowywania całego stanu w nim zawartego. Załóżmy, że mam zmienny obiekt, który używa głęboko niezmiennego obiektu do przechowywania 50 000 informacji o stanie. Załóżmy ponadto, że chciałbym 25 razy wykonać „kopię” mojego oryginalnego (zmiennego) obiektu (np. Dla bufora „cofnij”); stan może się zmieniać między operacjami kopiowania, ale zwykle tak się nie dzieje. Wykonanie „kopii” zmiennego obiektu wymagałoby po prostu skopiowania odniesienia do jego niezmiennego stanu, więc 20 kopii to po prostu 20 odniesień. Z drugiej strony, jeśli stan byłby przechowywany w wartościach 50 000 obiektów podlegających zmianom, każda z 25 operacji kopiowania musiałaby stworzyć własną kopię danych o wartości 50 000; przechowywanie wszystkich 25 kopii wymagałoby przechowywania ponad megaplikacji w większości zduplikowanych danych. Chociaż pierwsza operacja kopiowania utworzyłaby kopię danych, które nigdy się nie zmienią, a pozostałe 24 operacje mogłyby w teorii po prostu odwołać się do niej, w większości implementacji nie byłoby możliwości, aby drugi obiekt prosił o kopię informacje, aby wiedzieć, że niezmienna kopia już istnieje (*).
(*) Jeden wzorzec, który czasami może być przydatny, polega na tym, że zmienne obiekty mają dwa pola do przechowywania swojego stanu - jedno w postaci zmiennej i jedno w niezmiennej formie. Obiekty mogą być kopiowane jako zmienne lub niezmienne i zaczęłyby istnieć z jednym lub drugim zestawem odniesień. Gdy tylko obiekt chce zmienić swój stan, kopiuje niezmienne odniesienie do mutowalnego (jeśli jeszcze nie zostało to zrobione) i unieważnia niezmienne. Gdy obiekt jest kopiowany jako niezmienny, jeśli jego niezmienne odwołanie nie jest ustawione, zostanie utworzona niezmienna kopia, a niezmienne odwołanie wskaże na to. Takie podejście będzie wymagało kilku operacji kopiowania więcej niż "pełnoprawna kopia przy zapisie" (np. Prośba o skopiowanie obiektu, który został zmutowany od czasu ostatniej kopii, wymagałby operacji kopiowania,
źródło
Po utworzeniu instancji obiektu nie można zmienić jego stanu w okresie istnienia. Co również sprawia, że jest bezpieczny.
Oczywiście String, Integer i BigDecimal itp. Po utworzeniu tych wartości nie można ich zmienić w trakcie życia.
źródło
z Effective Java; Niezmienna klasa to po prostu klasa, której instancji nie można modyfikować. Wszystkie informacje zawarte w każdej instancji są dostarczane podczas jej tworzenia i są ustalane przez cały okres istnienia obiektu. Biblioteki platformy Java zawierają wiele niezmiennych klas, w tym String, zamknięte klasy pierwotne oraz BigInte- ger i BigDecimal. Jest ku temu wiele dobrych powodów: niezmienne klasy są łatwiejsze do zaprojektowania, zaimplementowania i używania niż klasy zmienne. Są mniej podatne na błędy i bezpieczniejsze.
źródło