Obiekty zmienne a niezmienne

173

Próbuję poradzić sobie z obiektami zmiennymi i niezmiennymi. Korzystanie z obiektów zmiennoprzecinkowych jest bardzo źle odbierane (np. Zwracanie tablicy ciągów z metody), ale mam problem ze zrozumieniem, jakie są tego negatywne skutki. Jakie są najlepsze praktyki dotyczące używania obiektów mutowalnych? Czy należy ich unikać, kiedy tylko jest to możliwe?

Alex Angas
źródło
stringjest niezmienna, przynajmniej w .NET, i myślę, że w wielu innych współczesnych językach.
Domenic
4
zależy to od tego, czym naprawdę jest String w języku - erlang „łańcuchy” to po prostu tablica int, a „łańcuchy” haskell to tablice znaków.
Chii,
4
Łańcuchy Ruby to zmienna tablica bajtów. Absolutnie doprowadza mnie do szału, że możesz je zmienić na miejscu.
Daniel Spiewak
6
Daniel - dlaczego? Czy zdarza Ci się, że robisz to przez przypadek?
5
@DanielSpiewak Rozwiązanie dla bycia napędzanym orzechami, które można zmienić w miejscu, jest proste: po prostu tego nie rób. Rozwiązanie problemu bycia nakręconym, ponieważ nie można zmienić strun w miejscu, nie jest takie proste.
Kaz

Odpowiedzi:

162

Cóż, jest kilka aspektów tego.

  1. Zmienne obiekty bez tożsamości referencyjnej mogą powodować błędy w dziwnych momentach. Na przykład rozważmy Personfasolę z equalsmetodą opartą na wartościach :

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    PersonInstancja zostanie „stracony” na mapie, gdy używany jako klucz, ponieważ jej hashCodei równość były oparte na wartościach modyfikowalnych. Te wartości zmieniły się poza mapą, a cały hasz stał się przestarzały. Teoretycy lubią harfować na ten temat, ale w praktyce nie wydaje mi się to zbyt dużym problemem.

  2. Innym aspektem jest logiczna „sensowność” twojego kodu. Jest to trudny do zdefiniowania termin, obejmujący wszystko, od czytelności po przepływ. Ogólnie rzecz biorąc, powinieneś być w stanie spojrzeć na fragment kodu i łatwo zrozumieć, co robi. Ale co ważniejsze, powinieneś być w stanie przekonać siebie, że robi to, co robi poprawnie . Kiedy obiekty mogą zmieniać się niezależnie w różnych „domenach” kodu, czasami trudno jest śledzić, co jest, gdzie i dlaczego („ upiorne działanie na odległość ”). Jest to koncepcja trudniejsza do zilustrowania, ale często spotykana w większych, bardziej złożonych architekturach.

  3. Wreszcie, zmienne obiekty są zabójcze w współbieżnych sytuacjach. Zawsze, gdy uzyskujesz dostęp do zmiennego obiektu z oddzielnych wątków, musisz poradzić sobie z blokowaniem. Zmniejsza to przepustowość i znacznie utrudnia utrzymanie kodu . Wystarczająco skomplikowany system eliminuje ten problem tak daleko, że jego utrzymanie jest prawie niemożliwe (nawet dla ekspertów od współbieżności).

Niezmienne obiekty (a dokładniej niezmienne kolekcje) pozwalają uniknąć wszystkich tych problemów. Kiedy już zrozumiesz, jak działają, Twój kod rozwinie się w coś, co będzie łatwiejsze do odczytania, łatwiejsze w utrzymaniu i rzadziej zawiedzie w dziwny i nieprzewidywalny sposób. Niezmienne obiekty są jeszcze łatwiejsze do testowania, nie tylko ze względu na ich łatwą możliwość makietowania, ale także wzorce kodu, które zwykle narzucają. Krótko mówiąc, wszędzie są dobrą praktyką!

Powiedziawszy to, nie jestem fanatykiem w tej sprawie. Niektóre problemy po prostu nie wyglądają ładnie, gdy wszystko jest niezmienne. Ale myślę, że powinieneś spróbować popchnąć jak najwięcej swojego kodu w tym kierunku, zakładając oczywiście, że używasz języka, który sprawia, że ​​jest to możliwa do utrzymania opinia (C / C ++ bardzo to utrudnia, podobnie jak Java) . Krótko mówiąc: korzyści zależą w pewnym stopniu od twojego problemu, ale wolałbym niezmienność.

Daniel Śpiewak
źródło
11
Świetna odpowiedź. Jedno małe pytanie: czy C ++ nie ma dobrego wsparcia dla niezmienności? Czy funkcja stałej poprawności nie jest wystarczająca?
Dimitri C.
1
@DimitriC .: C ++ faktycznie ma kilka bardziej fundamentalnych cech, w szczególności lepsze rozróżnienie między lokalizacjami pamięci, które mają hermetyzować stan niewspółdzielony, od tych, które hermetyzują tożsamość obiektu.
supercat
2
W przypadku programowania w Javie Joshua Bloch dobrze wyjaśnia ten temat w swojej słynnej książce Efektywna Java (pozycja 15)
dMathieuD
27

Niezmienne obiekty a niezmienne kolekcje

Jednym z lepszych punktów w debacie na temat obiektów zmiennych i niezmiennych jest możliwość rozszerzenia pojęcia niezmienności na kolekcje. Niezmienny obiekt to obiekt, który często reprezentuje pojedynczą logiczną strukturę danych (na przykład niezmienny ciąg znaków). Gdy masz odwołanie do niezmiennego obiektu, zawartość obiektu nie ulegnie zmianie.

Niezmienna kolekcja to kolekcja, która nigdy się nie zmienia.

Kiedy wykonuję operację na mutowalnej kolekcji, zmieniam kolekcję w miejscu, a wszystkie jednostki, które mają odwołania do kolekcji, zobaczą zmianę.

Kiedy wykonuję operację na niezmiennej kolekcji, odwołanie jest zwracane do nowej kolekcji odzwierciedlającej zmianę. Wszystkie jednostki, które mają odniesienia do poprzednich wersji kolekcji, nie zobaczą zmiany.

Sprytne implementacje niekoniecznie muszą kopiować (klonować) całą kolekcję, aby zapewnić tę niezmienność. Najprostszym przykładem jest stos zaimplementowany jako pojedynczo połączona lista i operacje push / pop. Możesz ponownie wykorzystać wszystkie węzły z poprzedniej kolekcji w nowej kolekcji, dodając tylko jeden węzeł do wypychania i nie klonując żadnych węzłów do wyskakiwania. Z drugiej strony operacja push_tail na pojedynczo połączonej liście nie jest tak prosta ani wydajna.

Niezmienne a zmienne zmienne / odwołania

Niektóre języki funkcjonalne przyjmują koncepcję niezmienności do samych odniesień do obiektów, zezwalając tylko na przypisanie pojedynczego odniesienia.

  • W Erlang jest to prawdą dla wszystkich „zmiennych”. Obiekty mogę przypisać do odniesienia tylko raz. Gdybym miał operować na kolekcji, nie byłbym w stanie ponownie przypisać nowej kolekcji do starego odniesienia (nazwa zmiennej).
  • Scala wbudowuje to również w język, a wszystkie odniesienia są zadeklarowane za pomocą var lub val , przy czym vals są tylko pojedynczym przypisaniem i promują styl funkcjonalny, ale vars pozwalają na strukturę programu podobną do C lub Javy.
  • Wymagana jest deklaracja var / val, podczas gdy wiele tradycyjnych języków używa opcjonalnych modyfikatorów, takich jak final w java i const w C.

Łatwość tworzenia a wydajność

Niemal zawsze powodem używania niezmiennego obiektu jest promowanie wolnego od efektów ubocznych programowania i prostego rozumowania kodu (szczególnie w środowisku wysoce współbieżnym / równoległym). Nie musisz się martwić, że dane źródłowe zostaną zmienione przez inną jednostkę, jeśli obiekt jest niezmienny.

Główną wadą jest wydajność. Oto opis prostego testu, który przeprowadziłem w Javie, porównując niektóre niezmienne i zmienne obiekty w problemie z zabawkami.

Problemy z wydajnością są dyskusyjne w wielu aplikacjach, ale nie we wszystkich, dlatego wiele dużych pakietów numerycznych, takich jak klasa Numpy Array w Pythonie, pozwala na aktualizacje w miejscu dużych tablic. Byłoby to ważne dla obszarów zastosowań, które wykorzystują duże operacje na macierzach i wektorach. Te duże, równoległe do danych i wymagające dużej mocy obliczeniowej problemy osiągają ogromne przyspieszenie dzięki działaniu w miejscu.

Ben Jackson
źródło
12

Sprawdź ten wpis na blogu: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Wyjaśnia, dlaczego niezmienne obiekty są lepsze niż zmienne. W skrócie:

  • niezmienne obiekty są prostsze do konstruowania, testowania i używania
  • prawdziwie niezmienne obiekty są zawsze bezpieczne dla wątków
  • pomagają uniknąć czasowego sprzężenia
  • ich użycie jest wolne od skutków ubocznych (brak kopii obronnych)
  • unika się problemu zmienności tożsamości
  • zawsze mają atomowość niepowodzenia
  • są znacznie łatwiejsze do buforowania
yegor256
źródło
10

Niezmienne obiekty to bardzo potężna koncepcja. Zdejmują wiele obowiązków związanych z utrzymaniem spójności obiektów / zmiennych dla wszystkich klientów.

Można ich używać do obiektów niskopoziomowych, niepolimorficznych - takich jak klasa CPoint - które są używane głównie z semantyką wartości.

Lub możesz ich użyć do wysokopoziomowych, polimorficznych interfejsów - takich jak IFunction reprezentujący funkcję matematyczną - która jest używana wyłącznie z semantyką obiektów.

Największa zaleta: niezmienność + semantyka obiektu + inteligentne wskaźniki sprawiają, że własność obiektu nie jest problemem, wszyscy klienci obiektu mają domyślnie własną prywatną kopię. W sposób dorozumiany oznacza to również deterministyczne zachowanie w obecności współbieżności.

Wada: gdy jest używany z obiektami zawierającymi dużo danych, zużycie pamięci może stać się problemem. Rozwiązaniem tego problemu mogłoby być zachowanie symboliki operacji na obiekcie i wykonanie leniwej oceny. Może to jednak prowadzić do łańcuchów obliczeń symbolicznych, które mogą negatywnie wpłynąć na wydajność, jeśli interfejs nie jest zaprojektowany do obsługi operacji symbolicznych. Coś, czego zdecydowanie należy unikać w tym przypadku, to zwracanie ogromnych fragmentów pamięci z metody. W połączeniu z połączonymi operacjami symbolicznymi może to prowadzić do ogromnego zużycia pamięci i obniżenia wydajności.

Tak więc niezmienne obiekty są zdecydowanie moim podstawowym sposobem myślenia o projektowaniu obiektowym, ale nie są dogmatem. Rozwiązują wiele problemów dla klientów obiektów, ale też tworzą wiele, szczególnie dla wykonawców.

QBziZ
źródło
Wydaje mi się, że źle zrozumiałem Sekcję 4 ze względu na największą zaletę: niezmienność + semantyka obiektu + inteligentne wskaźniki sprawiają, że własność obiektu jest kwestią sporną. Więc to dyskusyjne? Myślę, że niepoprawnie używasz terminu „dyskusja”… Ponieważ w kolejnym zdaniu obiekt sugeruje „deterministyczne zachowanie” z jego „kontrowersyjnego” zachowania.
Benjamin
Masz rację, niepoprawnie użyłem słowa „dyskusja”. Zastanów się, jak to się zmieniło :)
QBziZ
6

Powinieneś określić, o jakim języku mówisz. W przypadku języków niskiego poziomu, takich jak C lub C ++, wolę używać obiektów mutowalnych, aby zaoszczędzić miejsce i zmniejszyć utratę pamięci. W językach wyższego poziomu niezmienne obiekty ułatwiają wnioskowanie o zachowaniu kodu (zwłaszcza kodu wielowątkowego), ponieważ nie ma „upiornej akcji na odległość”.

John Millikin
źródło
Sugerujesz, że nici są splątane kwantowo? To trochę naciągane :) Nici są właściwie, jeśli się nad tym zastanowić, są bardzo bliskie splątania. Zmiana, którą wprowadza jeden wątek, wpływa na drugi. +1
ndrewxie
4

Zmienny obiekt to po prostu obiekt, który można zmodyfikować po utworzeniu / utworzeniu instancji, w porównaniu z niezmiennym obiektem, którego nie można zmodyfikować (zobacz stronę Wikipedii na ten temat). Przykładem tego w języku programowania są listy i krotki Pythona. Listy można modyfikować (np. Można dodawać nowe elementy po ich utworzeniu), podczas gdy krotki nie.

Nie sądzę, aby istniała jednoznaczna odpowiedź, która z nich jest lepsza w każdej sytuacji. Oboje mają swoje miejsca.

willurd
źródło
1

Jeśli typ klasy jest zmienny, zmienna tego typu klasy może mieć wiele różnych znaczeń. Na przykład załóżmy, że obiekt fooma pole int[] arri zawiera odniesienie do int[3]zbioru liczb {5, 7, 9}. Mimo że typ pola jest znany, istnieją co najmniej cztery różne rzeczy, które może reprezentować:

  • Potencjalnie współdzielone odniesienie, którego posiadacze dbają tylko o to, aby hermetyzować wartości 5, 7 i 9. Jeśli foochce arrsię hermetyzować różne wartości, musi zastąpić je inną tablicą zawierającą żądane wartości. Chcąc wykonać kopię foo, można podać kopię albo odniesienie do, arralbo nową tablicę zawierającą wartości {1, 2, 3}, w zależności od tego, która z tych opcji jest wygodniejsza.

  • Jedyne odniesienie, gdziekolwiek we wszechświecie, do tablicy zawierającej wartości 5, 7 i 9. zbiór trzech miejsc przechowywania, które w tej chwili przechowują wartości 5, 7 i 9; jeśli foochce, aby hermetyzował wartości 5, 8 i 9, może albo zmienić drugą pozycję w tej tablicy, albo utworzyć nową tablicę zawierającą wartości 5, 8 i 9 i porzucić starą. Zauważ, że jeśli ktoś chciałby wykonać kopię foo, należy w kopii zastąpić arrodwołaniem do nowej tablicy, foo.arraby pozostać jedynym odnośnikiem do tej tablicy w dowolnym miejscu we wszechświecie.

  • Odniesienie do tablicy, której właścicielem jest inny obiekt, który z foojakiegoś powodu ją wystawił (np. Być może chce tam fooprzechowywać jakieś dane). W tym scenariuszu arrnie hermetyzuje zawartości tablicy, ale raczej jej tożsamość . Ponieważ zastąpienie arrodwołaniem do nowej tablicy całkowicie zmieniłoby jej znaczenie, kopia foopowinna zawierać odniesienie do tej samej tablicy.

  • Odniesienie do tablicy, której foojedynym właścicielem jest, ale do której z jakiegoś powodu odwołuje się inny obiekt (np. Chce, aby inny obiekt przechowywał tam dane - druga strona poprzedniego przypadku). W tym scenariuszu arrhermetyzuje zarówno tożsamość tablicy, jak i jej zawartość. Zastąpienie arrodwołaniem do nowej tablicy całkowicie zmieniłoby jej znaczenie, ale posiadanie arrodwołania do klona foo.arrnaruszyłoby założenie, że foojest jedynym właścicielem. Nie ma więc możliwości kopiowania foo.

W teorii int[]powinien być ładnym, prostym, dobrze zdefiniowanym typem, ale ma cztery bardzo różne znaczenia. Natomiast odniesienie do niezmiennego obiektu (np. String) Generalnie ma tylko jedno znaczenie. Duża część „mocy” niezmiennych obiektów wynika z tego faktu.

superkat
źródło
1

Zmienne instancje są przekazywane przez odwołanie.

Niezmienne wystąpienia są przekazywane przez wartość.

Abstrakcyjny przykład. Załóżmy, że na moim dysku twardym istnieje plik o nazwie txtfile . Teraz, kiedy poprosisz mnie o txtfile , mogę go zwrócić w dwóch trybach:

  1. Utwórz skrót do txtfile i podaj skrót do Ciebie lub
  2. Weź kopię do pliku txt i przekaż kopię do siebie.

W pierwszym trybie zwrócony plik txt jest plikiem modyfikowalnym , ponieważ kiedy robisz zmiany w pliku skrótu, robisz również zmiany w oryginalnym pliku. Zaletą tego trybu jest to, że każdy zwrócony skrót wymagał mniejszej ilości pamięci (na RAM lub na dysku twardym), a wadą jest to, że wszyscy (nie tylko ja, właściciel) mają uprawnienia do modyfikowania zawartości pliku.

W drugim trybie zwrócony plik txtfile jest niezmiennym plikiem, ponieważ wszystkie zmiany w otrzymanym pliku nie odnoszą się do oryginalnego pliku. Zaletą tego trybu jest to, że tylko ja (właściciel) mogę modyfikować oryginalny plik, a wadą jest to, że każda zwracana kopia wymaga pamięci (w RAM lub na HDD).

Teodora
źródło
To nie jest do końca prawdą. Niezmienne instancje rzeczywiście mogą być przekazywane przez odwołanie i często tak jest. Kopiowanie odbywa się głównie wtedy, gdy trzeba zaktualizować obiekt lub strukturę danych.
Jamon Holmgren
0

Jeśli zwrócisz odwołania do tablicy lub ciągu znaków, świat zewnętrzny może zmodyfikować zawartość tego obiektu, a tym samym uczynić go zmiennym (modyfikowalnym) obiektem.

user1923551
źródło
0

Niezmiennych środków nie można zmienić, a zmienne oznaczają, że można się zmienić.

Obiekty różnią się od prymitywów w Javie. Prymitywy są wbudowane w typy (boolean, int itp.), A obiekty (klasy) są typami utworzonymi przez użytkownika.

Prymitywy i obiekty mogą być modyfikowalne lub niezmienne, jeśli są zdefiniowane jako zmienne składowe w ramach implementacji klasy.

Wiele osób uważa, że ​​prymitywy i zmienne obiektowe mające przed sobą końcowy modyfikator są niezmienne, jednak nie jest to do końca prawdą. Więc finał prawie nie oznacza niezmienności dla zmiennych. Zobacz przykład tutaj
http://www.siteconsortium.com/h/D0000F.php .

JTHouseCat
źródło
0

Immutable object- to stan obiektu, którego po utworzeniu nie można zmienić. Obiekt jest niezmienny, gdy wszystkie jego pola są niezmienne

Bezpieczny wątek

Główną zaletą obiektu Immutable jest to, że jest on naturalnym środowiskiem współbieżnym. Największym problemem we współbieżności jest shared resourceto, że można zmienić dowolny wątek. Ale jeśli obiekt jest niezmienny, jest read-onlyto operacja bezpieczna wątkowo. Każda modyfikacja oryginalnego niezmiennego obiektu zwraca kopię

wolne od skutków ubocznych

Jako programista masz całkowitą pewność, że stanu niezmiennego obiektu nie można zmienić z dowolnego miejsca (celowo lub nie)

kompilacja optymalizacji

Polepszyć wydajność

Niekorzyść:

Kopiowanie obiektu jest operacją cięższą niż zmiana obiektu zmiennego, dlatego ma pewien wpływ na wydajność

Aby stworzyć immutableobiekt należy użyć:

  1. Poziom języka. Każdy język zawiera narzędzia, które Ci w tym pomogą. Na przykład Java ma finali primitives, Swift ma leti struct[About] . Język definiuje typ zmiennej. Na przykład Java ma primitivei referencewpisz, Swift ma valuei referencewpisz [About] . Dla niezmiennych obiektów wygodniejsze jest primitivesi valuewpisywanie, które domyślnie tworzy kopię. Jeśli chodzi o referencetyp, to jest to trudniejsze (ponieważ możesz z niego zmienić stan obiektu), ale możliwe. Na przykład możesz użyć clonewzorca na poziomie programisty

  2. Poziom programisty. Jako programista nie powinieneś udostępniać interfejsu do zmiany stanu

yoAlex5
źródło