Uwielbiam niezmienny „wzorzec” ze względu na jego mocne strony, aw przeszłości uważałem za korzystne projektowanie systemów z niezmiennymi typami danych (niektóre, większość lub nawet wszystkie). Często kiedy to robię, piszę mniej błędów, a debugowanie jest znacznie łatwiejsze.
Jednak moi rówieśnicy w ogóle unikają niezmienności. Nie są wcale niedoświadczeni (z dala od tego), ale piszą obiekty danych w klasyczny sposób - prywatni członkowie z getterem i setersem dla każdego członka. Wtedy zwykle ich konstruktorzy nie przyjmują argumentów, a może po prostu biorą argumenty dla wygody. Tak często tworzenie obiektu wygląda następująco:
Foo a = new Foo();
a.setProperty1("asdf");
a.setProperty2("bcde");
Może robią to wszędzie. Może nawet nie definiują konstruktora, który bierze te dwa ciągi, bez względu na to, jak ważne są. I może nie zmieniają później wartości tych ciągów i nigdy nie muszą tego robić. Oczywiście, jeśli te rzeczy są prawdziwe, obiekt byłby lepiej zaprojektowany jako niezmienny, prawda? (konstruktor przyjmuje dwie właściwości, w ogóle nie ma seterów).
Jak decydujesz, czy typ obiektu powinien zostać zaprojektowany jako niezmienny? Czy istnieje dobry zestaw kryteriów do oceny?
Obecnie zastanawiam się, czy zmienić kilka typów danych w moim projekcie na niezmienne, ale musiałbym uzasadnić to moim rówieśnikom, a dane w tych typach mogą (BARDZO rzadko) ulec zmianie - w którym momencie możesz oczywiście zmienić to niezmienny sposób (utwórz nowy, kopiując właściwości ze starego obiektu, z wyjątkiem tych, które chcesz zmienić). Ale nie jestem pewien, czy to tylko moja miłość do przejawiania się niezmienności, czy też istnieje rzeczywista potrzeba / korzyść z nich.
źródło
Odpowiedzi:
Wygląda na to, że zbliżasz się do niego wstecz. Należy domyślnie niezmienny. Zmodyfikuj obiekt tylko wtedy, gdy absolutnie musisz / po prostu nie możesz sprawić, aby działał jako obiekt niezmienny.
źródło
Podstawową zaletą niezmiennych obiektów jest zagwarantowanie bezpieczeństwa nici. W świecie, w którym wiele rdzeni i nici jest normą, ta korzyść stała się bardzo ważna.
Ale korzystanie ze zmiennych obiektów jest bardzo wygodne. Działają dobrze i dopóki nie modyfikujesz ich z oddzielnych wątków i dobrze rozumiesz, co robisz, są one dość niezawodne.
źródło
Istnieją dwa główne sposoby decydowania, czy obiekt jest niezmienny.
a) W oparciu o naturę obiektu
Łatwo jest uchwycić te sytuacje, ponieważ wiemy, że te obiekty nie zmienią się po zbudowaniu. Na przykład, jeśli masz
RequestHistory
encję i z natury rzeczy encje nie zmieniają się po jej zbudowaniu. Obiekty te można od razu zaprojektować jako niezmienne klasy. Należy pamiętać, że obiekt żądania jest zmienny, ponieważ może zmieniać swój stan i do kogo jest przypisany itp., Ale historia żądań nie ulega zmianie. Na przykład w zeszłym tygodniu utworzono element historii, który przeniósł się ze stanu przesłanego do przypisanego ORAZ ten podmiot historii nigdy się nie zmieni. Jest to więc klasyczna niezmienna obudowa.b) W oparciu o wybór projektu czynniki zewnętrzne
Jest to podobne do przykładu java.lang.String. Ciągi mogą się zmieniać w czasie, ale z założenia sprawiły, że są niezmienne ze względu na buforowanie / pulę ciągów / czynniki współbieżności. Podobnie buforowanie / współbieżność itp. Może odgrywać dobrą rolę w uczynieniu obiektu odpornym na ataki, jeśli buforowanie / współbieżność i związana z nim wydajność są niezbędne w aplikacji. Ale decyzję tę należy podjąć bardzo ostrożnie po uprzednim przeanalizowaniu wszystkich skutków.
Główną zaletą niezmiennych obiektów jest to, że nie są poddawane wzorom chwastowania. Obiekt nie wykryje żadnych zmian w czasie życia, a to bardzo ułatwia kodowanie i konserwację.
źródło
Minimalizacja stanu programu jest bardzo korzystna.
Zapytaj ich, czy chcą użyć zmiennego typu wartości w jednej z twoich klas do tymczasowego przechowywania z klasy klienta.
Jeśli powiedzą tak, zapytaj dlaczego? Stan zmienny nie należy do takiego scenariusza. Zmuszenie ich do utworzenia stanu, w którym faktycznie należy, i uczynienie stanu typów danych tak wyraźnymi, jak to możliwe, to doskonałe powody.
źródło
Odpowiedź jest nieco zależna od języka. Twój kod wygląda jak Java, gdzie ten problem jest tak trudny, jak to możliwe. W Javie obiekty mogą być przekazywane tylko przez odniesienie, a klon jest całkowicie uszkodzony.
Nie ma prostej odpowiedzi, ale na pewno chcesz uczynić obiekty o małej wartości niezmiennymi. Java poprawnie spowodowała, że ciągi są niezmienne, ale niepoprawnie zmieniono datę i kalendarz.
Zdecydowanie więc unieważnij obiekty o małej wartości i zaimplementuj konstruktor kopii. Zapomnij o Cloneable, jest tak źle zaprojektowany, że jest bezużyteczny.
W przypadku obiektów o większej wartości, jeśli niewygodne jest uczynienie ich niezmiennymi, należy je łatwo skopiować.
źródło
Wręcz przeciwnie intuicyjnie, to, że nigdy nie trzeba później zmieniać łańcuchów, jest całkiem dobrym argumentem, że nie ma znaczenia, czy obiekty są niezmienne, czy nie. Programista traktuje je już jako niezmienne, niezależnie od tego, czy kompilator to wymusza, czy nie.
Niezmienność zwykle nie boli, ale też nie zawsze pomaga. Najłatwiejszym sposobem stwierdzenia, czy twój obiekt może skorzystać z niezmienności, jest to, że kiedykolwiek będziesz musiał wykonać kopię obiektu lub nabyć muteks przed jego zmianą . Jeśli to się nigdy nie zmienia, to niezmienność tak naprawdę niczego nie kupuje, a czasem komplikuje sprawę.
Masz rację, jeśli chodzi o ryzyko konstruowania obiektu w nieprawidłowym stanie, ale to naprawdę kwestia odrębna od niezmienności. Obiekt może być zarówno zmienny, jak i zawsze w poprawnym stanie po zakończeniu budowy.
Wyjątkiem od tej reguły jest to, że ponieważ Java nie obsługuje ani nazwanych parametrów, ani parametrów domyślnych, czasami może być nieporęczne zaprojektowanie klasy, która gwarantuje prawidłowy obiekt za pomocą przeciążonych konstruktorów. Nie tyle w przypadku dwóch własności, ale jest też coś, co należy powiedzieć o spójności, jeśli ten wzorzec jest częsty z podobnymi, ale większymi klasami w innych częściach twojego kodu.
źródło
Object
funkcji”, a następnie bezpośrednia zmiana obiektu nie byłaby semantyczna niczym nadpisanie odwołania odniesieniem do nowego obiektu, który był identyczne, ale dla wskazanej zmiany. Jedną z największych słabości semantycznych w Javie, IMHO, jest to, że nie ma środków, za pomocą których kod może wskazywać, że zmienna powinna być jedynym nie efemerycznym odniesieniem do czegoś; referencje powinny być możliwe do przekazania tylko do metod, które nie mogą zachować kopii po powrocie.Mogę mieć zbyt niski widok tego i prawdopodobnie dlatego, że używam C i C ++, co nie sprawia, że jest to tak proste, aby wszystko było niezmienne, ale widzę niezmienne typy danych jako szczegół optymalizacji w celu napisania więcej wydajne funkcje pozbawione efektów ubocznych i bardzo łatwo udostępniać funkcje takie jak cofanie systemów i nieniszcząca edycja.
Na przykład może to być niezwykle kosztowne:
... jeśli
Mesh
nie został zaprojektowany jako trwała struktura danych, a zamiast tego był typem danych, który wymagał pełnego skopiowania (który może obejmować gigabajty w niektórych scenariuszach), nawet jeśli wszystko, co będziemy robić, to zmienić część (jak w powyższym scenariuszu, w którym modyfikujemy tylko pozycje wierzchołków).Właśnie wtedy sięgam po niezmienność i projektuję strukturę danych, aby umożliwić niezmodyfikowane części jej płytkiego kopiowania i zliczania referencji, aby umożliwić powyższą funkcję w sposób względnie wydajny bez konieczności głębokiego kopiowania całych siatek wokół, a jednocześnie móc pisać funkcja jest wolna od skutków ubocznych, co znacznie upraszcza bezpieczeństwo wątków, bezpieczeństwo wyjątków, możliwość cofnięcia operacji, zastosowania jej w sposób nieniszczący itp.
W moim przypadku jest to zbyt kosztowne (przynajmniej z punktu widzenia produktywności), aby uczynić wszystko niezmiennym, więc zapisuję to dla klas, które są zbyt drogie, aby je w całości skopiować. Te klasy są zwykle mocnymi strukturami danych, takimi jak siatki i obrazy, i generalnie używam modyfikowalnego interfejsu, aby wyrazić zmiany w nich poprzez obiekt „konstruktora”, aby uzyskać nową niezmienną kopię. I nie robię tego tak bardzo, aby uzyskać niezmienne gwarancje na centralnym poziomie klasy, ale pomagając mi korzystać z klasy w funkcjach, które mogą być wolne od skutków ubocznych. Moje pragnienie, aby
Mesh
powyższe było niezmienne, nie polega bezpośrednio na tym, aby siatki były niezmienne, ale aby umożliwić łatwe pisanie funkcji wolnych od efektów ubocznych, które wprowadzają siatkę i generują nową bez płacenia ogromnej pamięci i kosztów obliczeniowych w zamian.W rezultacie mam tylko 4 niezmienne typy danych w całej mojej bazie kodu i wszystkie one są potężnymi strukturami danych, ale używam ich bardzo, aby pomóc mi pisać funkcje wolne od skutków ubocznych. Ta odpowiedź może mieć zastosowanie, jeśli, tak jak ja, pracujesz w języku, który nie sprawia, że wszystko staje się niezmienne. W takim przypadku możesz skupić się na tym, aby większość funkcji unikała skutków ubocznych, w którym to momencie możesz chcieć uczynić wybrane struktury danych niezmiennymi, typy PDS, jako szczegół optymalizacji, aby uniknąć kosztownych pełnych kopii. Tymczasem kiedy mam taką funkcję:
... nie mam więc pokusy, aby wektory były niezmienne, ponieważ są wystarczająco tanie, aby po prostu skopiować je w całości. Nie można uzyskać żadnej korzyści w zakresie wydajności, zmieniając wektory w niezmienne struktury, które mogą płytko kopiować niezmodyfikowane części. Takie koszty przeważałyby nad kosztem kopiowania całego wektora.
źródło
Jeśli Foo ma tylko dwie właściwości, łatwo jest napisać konstruktor, który przyjmuje dwa argumenty. Załóżmy jednak, że dodajesz inną właściwość. Następnie musisz dodać kolejny konstruktor:
W tym momencie jest to nadal możliwe do zarządzania. Ale co jeśli jest 10 nieruchomości? Nie chcesz konstruktora z 10 argumentami. Za pomocą wzorca konstruktora można konstruować instancje, ale to zwiększa złożoność. Do tego czasu będziesz myślał „dlaczego nie dodałem seterów dla wszystkich właściwości, tak jak robią to zwykli ludzie”?
źródło