Widzę korzyści płynące z uczynienia obiektów w moim programie niezmiennymi. Kiedy naprawdę głęboko zastanawiam się nad dobrym projektem mojej aplikacji, często naturalnie dochodzę do tego, że wiele moich obiektów jest niezmiennych. Często dochodzi do tego, że chciałbym, aby wszystkie moje obiekty były niezmienne.
To pytanie dotyczy tego samego pomysłu, ale żadna odpowiedź nie sugeruje, jakie jest dobre podejście do niezmienności i kiedy z niego skorzystać. Czy istnieją jakieś niezmienne niezmienne wzory? Ogólną ideą wydaje się być „uczynienie obiektów niezmiennymi, chyba że absolutnie potrzebujesz ich do zmiany”, co w praktyce jest bezużyteczne.
Z mojego doświadczenia wynika, że niezmienność coraz bardziej popycha mój kod do paradygmatu funkcjonalnego i ten postęp zawsze ma miejsce:
- Zaczynam potrzebować trwałych (w sensie funkcjonalnym) struktur danych, takich jak listy, mapy itp.
- Niezwykle niewygodna jest praca z odsyłaczami (np. Węzeł drzewa odwołujący się do swoich dzieci, podczas gdy dzieci odwołują się do swoich rodziców), co sprawia, że w ogóle nie używam odsyłaczy, co ponownie sprawia, że moje struktury danych i kod są bardziej funkcjonalne.
- Dziedziczenie przestaje mieć sens i zamiast tego zaczynam używać kompozycji.
- Całe podstawowe idee OOP, takie jak enkapsulacja, zaczynają się rozpadać, a moje obiekty zaczynają wyglądać jak funkcje.
W tym momencie praktycznie nie używam już nic z paradygmatu OOP i mogę po prostu przejść do czysto funkcjonalnego języka. Zatem moje pytanie: czy istnieje spójne podejście do dobrego niezmiennego projektu OOP, czy też zawsze jest tak, że kiedy wykorzystasz niezmienny pomysł do jego pełnego potencjału, zawsze kończysz programowanie w funkcjonalnym języku, który nie potrzebuje już niczego ze świata OOP? Czy istnieją jakieś dobre wytyczne, aby zdecydować, które klasy powinny być niezmienne, a które powinny być zmienne, aby zapewnić, że OOP się nie rozpadnie?
Dla wygody podam przykład. Zróbmy ChessBoard
niezmienną kolekcję niezmiennych szachów (rozszerzenie klasy abstrakcyjnejPiece
). Z punktu widzenia OOP, pionek jest odpowiedzialny za generowanie prawidłowych ruchów ze swojej pozycji na planszy. Ale aby wygenerować ruchy, element potrzebuje odniesienia do swojej planszy, podczas gdy plansza musi mieć odniesienie do swoich elementów. Cóż, istnieją pewne sztuczki, aby stworzyć te niezmienne powiązania w zależności od języka OOP, ale zarządzanie nimi jest uciążliwe, lepiej nie mieć elementu, który mógłby odwoływać się do jego tablicy. Ale wtedy element nie może wygenerować ruchów, ponieważ nie zna stanu planszy. Następnie kawałek staje się po prostu strukturą danych zawierającą typ elementu i jego pozycję. Następnie można użyć funkcji polimorficznej do generowania ruchów dla wszystkich rodzajów elementów. Jest to doskonale osiągalne w programowaniu funkcjonalnym, ale prawie niemożliwe w OOP bez sprawdzania typu środowiska wykonawczego i innych złych praktyk OOP ...
Odpowiedzi:
Nie rozumiem dlaczego nie. Robiłem to od lat, zanim Java 8 i tak stała się funkcjonalna. Słyszałeś kiedyś o Strunach? Ładne i niezmienne od samego początku.
Też cały czas ich potrzebowałem. Unieważnianie moich iteratorów, ponieważ zmutowałeś kolekcję podczas czytania, jest to po prostu niegrzeczne.
Okrągłe odniesienia to szczególny rodzaj piekła. Niezmienność cię przed tym nie uratuje.
Cóż, jestem z tobą, ale nie widzę, co to ma wspólnego z niezmiennością. Powodem, dla którego lubię kompozycję, nie jest to, że uwielbiam dynamiczny wzorzec strategii, to dlatego, że pozwala mi zmieniać poziom abstrakcji.
Drżę, by pomyśleć, jaki jest twój pomysł na „enkapsulację OOP”. Jeśli dotyczy to programów pobierających i ustawiających, przestań nazywać to enkapsulacją, ponieważ tak nie jest. Nigdy tak nie było. To ręczne programowanie zorientowane na aspekt. Szansa na sprawdzenie i miejsce na punkt przerwania jest dobra, ale nie jest hermetyzacją. Kapsułkowanie zachowuje moje prawo do niewiedzy i dbania o to, co dzieje się w środku.
Twoje obiekty powinny wyglądać jak funkcje. To mnóstwo funkcji. Są to zestawy funkcji, które poruszają się razem i wspólnie się na nowo definiują.
W tej chwili modne jest programowanie funkcjonalne, a ludzie obawiają się pewnych nieporozumień na temat OOP. Nie pozwól, aby to pomieszało cię w przekonaniu, że to koniec OOP. Funkcjonalne i OOP mogą żyć całkiem nieźle.
Programowanie funkcjonalne jest formalnie związane z zadaniami.
OOP formalnie określa wskaźniki funkcji.
Naprawdę to. Dykstra powiedział nam, że
goto
jest szkodliwy, więc oficjalnie o tym poprosiliśmy i stworzyliśmy programowanie strukturalne. Właśnie tak, te dwa paradygmaty dotyczą znalezienia sposobów na załatwienie sprawy, unikając pułapek wynikających z robienia tych kłopotliwych rzeczy od niechcenia.Pokażę ci coś:
f n (x)
To jest funkcja. To właściwie kontinuum funkcji:
f 1 (x)
f 2 (x)
...
f n (x)
Zgadnij, jak to wyrażamy w językach OOP?
n.f(x)
To niewiele
n
decyduje o tym, która implementacjaf
jest używana ORAZ decyduje, jakie są niektóre ze stałych używanych w tej funkcji (co szczerze mówiąc to to samo). Na przykład:f 1 (x) = x + 1
f 2 (x) = x + 2
To jest to samo, co zapewniają zamknięcia. Tam, gdzie zamknięcia odnoszą się do ich zakresu, metody obiektowe odnoszą się do stanu ich instancji. Obiekty mogą zrobić zamknięcia o jeden lepiej. Zamknięcie to pojedyncza funkcja zwrócona z innej funkcji. Konstruktor zwraca odwołanie do całego zestawu funkcji:
g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2
Zgadłeś:
n.g(x)
f i g to funkcje, które zmieniają się razem i poruszają razem. Więc wkładamy je do tej samej torby. Tak naprawdę jest przedmiotem. Trzymanie
n
stałej (niezmiennej) oznacza po prostu, że łatwiej jest przewidzieć, co zrobią, gdy do nich zadzwonisz.To tylko struktura. Sposób, w jaki myślę o OOP, to kilka małych rzeczy, które mówią do innych małych rzeczy. Mam nadzieję, że do małej wybranej grupy drobiazgów. Kiedy koduję, wyobrażam sobie siebie jako obiekt. Patrzę na rzeczy z punktu widzenia obiektu. Staram się być leniwy, żeby nie przerabiać obiektu. Przyjmuję proste wiadomości, trochę nad nimi pracuję i wysyłam proste wiadomości tylko do moich najlepszych przyjaciół. Kiedy skończę z tym obiektem, wskakuję na inny i patrzę na rzeczy z jego perspektywy.
Karty odpowiedzialności klasowej jako pierwsze nauczyły mnie myśleć w ten sposób. Człowieku, byłem wtedy zdezorientowany, ale cholera, jeśli nadal nie są aktualne.
Arg! Ponownie z niepotrzebnymi okólnikami.
Co powiesz na: A
ChessBoardDataStructure
zamienia sznurki xy w odniesienia do elementów. Te elementy mają metodę, która bierze x, y i konkretną,ChessBoardDataStructure
i zamienia ją w kolekcję brandów, na której pojawiają się noweChessBoardDataStructure
. Następnie wpycha to w coś, co może wybrać najlepszy ruch. TerazChessBoardDataStructure
mogą być niezmienne, podobnie jak kawałki. W ten sposób masz tylko jeden biały pionek w pamięci. Istnieje tylko kilka odniesień do tego w odpowiednich lokalizacjach XY. Obiektowy, funkcjonalny i niezmienny.Zaraz, czy nie rozmawialiśmy już o szachach?
źródło
Moim zdaniem najbardziej użytecznymi pojęciami wprowadzonymi do głównego nurtu przez OOP są:
Wszystkie te korzyści można również zrealizować bez tradycyjnych szczegółów implementacji, takich jak dziedziczenie, a nawet klasy. Oryginalny pomysł Alana Kay na „system obiektowy” wykorzystywał „wiadomości” zamiast „metod” i był bliższy Erlangowi niż np. C ++. Spójrz na Go, która eliminuje wiele tradycyjnych szczegółów implementacji OOP, ale nadal wydaje się być dość zorientowana obiektowo.
Jeśli używasz niezmiennych obiektów, nadal możesz korzystać z większości tradycyjnych dodatków OOP: interfejsów, dynamicznej wysyłki, enkapsulacji. Nie potrzebujesz seterów i często nie potrzebujesz nawet getterów dla prostszych obiektów. Możesz także czerpać korzyści z niezmienności: zawsze masz pewność, że w międzyczasie obiekt się nie zmienił, nie będzie kopiowania obronnego, żadnych wyścigów danych, a metody są czyste.
Zobacz, jak Scala próbuje połączyć niezmienność i podejście FP z OOP. Trzeba przyznać, że nie jest to najprostszy i najbardziej elegancki język. Jednak jest praktycznie praktycznie udany. Zobacz także Kotlin, który oferuje wiele narzędzi i podejść do podobnego miksu.
Zwykłym problemem związanym z wypróbowaniem innego podejścia niż to, o którym twórcy języka mieli na myśli N lata temu, jest „niedopasowanie impedancji” ze standardową biblioteką. OTOH zarówno ekosystemy Java, jak i .NET mają obecnie rozsądną obsługę standardowej biblioteki dla niezmiennych struktur danych; oczywiście istnieją również biblioteki innych firm.
źródło