Pochodzę z OOP (Java), uczę się Scali na własną rękę. Chociaż łatwo dostrzegam zalety korzystania z niezmiennych obiektów indywidualnie, trudno mi jest dostrzec, jak można zaprojektować taką aplikację. Podam przykład:
Powiedzmy, że mam przedmioty reprezentujące „materiały” i ich właściwości (projektuję grę, więc naprawdę mam ten problem), takie jak woda i lód. Miałbym „menedżera”, który jest właścicielem wszystkich takich instancji materiałów. Jedną właściwością byłby punkt zamarzania i topnienia oraz to, do czego materiał zamarza lub topi się.
[EDYCJA] Wszystkie materialne wystąpienia są „singleton”, podobnie jak Java Enum.
Chcę, żeby „woda” powiedziała, że zamarza do „lodu” w 0 ° C, a „lód”, by powiedzieć, że topi się do „wody” w 1 ° C. Ale jeśli woda i lód są niezmienne, nie mogą uzyskać odniesienia do siebie jako parametrów konstruktora, ponieważ jeden z nich musi zostać utworzony jako pierwszy i nie można uzyskać odniesienia do nieistniejącego jeszcze innego jako parametru konstruktora. Mógłbym rozwiązać ten problem, podając im zarówno odwołanie do kierownika, aby mogli zapytać go, aby znaleźć inną istotną instancję, której potrzebują za każdym razem, gdy są proszeni o ich właściwości zamrażania / topienia, ale wtedy mam ten sam problem między kierownikiem i materiały, które potrzebują odniesienia do siebie, ale można je podać tylko w konstruktorze dla jednego z nich, więc ani menedżer, ani materiał nie mogą być niezmienne.
Czy po prostu nie można obejść tego problemu, czy też muszę użyć „funkcjonalnych” technik programowania lub innego wzoru, aby go rozwiązać?
źródło
h2o
materiałOdpowiedzi:
Rozwiązaniem jest trochę oszukać. Konkretnie:
Utwórz A, ale pozostaw jego odwołanie do B niezainicjowane (ponieważ B jeszcze nie istnieje).
Utwórz B i poprowadź go do A.
Zaktualizuj A, aby wskazywał na B. Nie aktualizuj ani A ani B po tym.
Można to zrobić jawnie (przykład w C ++):
lub pośrednio (przykład w Haskell):
Przykład Haskella wykorzystuje leniwą ocenę, aby uzyskać iluzję wzajemnie niezmiennych wartości. Wartości zaczynają się od:
a
ib
oba są niezależnie ważnymi formami głowy-normalności . Każdy minus może być skonstruowany bez potrzeby uzyskiwania końcowej wartości drugiej zmiennej. Podczas oceny thunk wskaże te sameb
punkty danych do.Dlatego jeśli chcesz, aby dwie niezmienne wartości wskazywały na siebie, albo musisz zaktualizować pierwszą po zbudowaniu drugiej, albo użyć mechanizmu wyższego poziomu, aby zrobić to samo.
W twoim konkretnym przykładzie mogę wyrazić to w Haskell jako:
Jednak omijam ten problem. Wyobrażam sobie, że w podejściu obiektowym, w którym
setTemperature
metoda jest dołączana do wyniku każdegoMaterial
konstruktora, musielibyście mieć konstruktory skierowane do siebie. Jeśli konstruktory są traktowane jako niezmienne wartości, możesz użyć podejścia opisanego powyżej.źródło
W twoim przykładzie zastosujesz transformację do obiektu, więc użyłbym czegoś takiego jak
ApplyTransform()
metoda, która zwraca,BlockBase
zamiast próbować zmienić bieżący obiekt.Na przykład, aby zmienić IceBlock na WaterBlock przez zastosowanie ciepła, nazwałbym coś takiego
a
IceBlock.ApplyTemperature()
metoda wyglądałaby mniej więcej tak:źródło
BlockList.WaterBlock
zamiast tworzenia nowego bloku?BlockList
to tylkostatic
klasa odpowiedzialna za pojedyncze wystąpienia każdego bloku, więc nie musisz tworzyć wystąpieniaBlockList
(jestem przyzwyczajony do C #)Innym sposobem na przerwanie cyklu jest rozdzielenie problemów związanych z materiałem i transmutacją w niektórych wymyślonych językach:
źródło
Jeśli zamierzasz używać funkcjonalnego języka i chcesz zdać sobie sprawę z korzyści niezmienności, powinieneś podejść do problemu z tą myślą. Próbujesz zdefiniować obiekt typu „lód” lub „woda”, który może obsługiwać szereg temperatur - aby zachować niezmienność, będziesz musiał utworzyć nowy obiekt za każdym razem, gdy zmienia się temperatura, co jest marnotrawstwem. Spróbuj więc uczynić koncepcje typu bloku i temperatury bardziej niezależnymi. Nie znam Scali (jest na mojej liście do nauki :-)), ale pożyczając od Joeya Adamsa w Haskell , sugeruję coś takiego:
albo może:
(Uwaga: nie próbowałem tego uruchamiać, a mój Haskell jest trochę zardzewiały). Teraz logika przejścia jest oddzielona od rodzaju materiału, więc nie marnuje tyle pamięci i (moim zdaniem) jest dość nieco bardziej funkcjonalnie zorientowany.
źródło