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 ...