Mam kilka dużych (więcej niż 3 pola) obiektów, które mogą i powinny być niezmienne. Za każdym razem, gdy napotykam taki przypadek, zwykle tworzę abominacje konstruktorów z długimi listami parametrów.
Wydaje się, że nie jest w porządku, jest trudny w użyciu, a czytelność cierpi.
Jeszcze gorzej jest, jeśli pola są pewnego rodzaju zbiorami, takimi jak listy. Prosty addSibling(S s)
bardzo ułatwiłby tworzenie obiektu, ale czyni obiekt zmiennym.
Czego używacie w takich przypadkach?
Jestem na Scali i Javie, ale myślę, że problem leży po stronie języka, o ile jest zorientowany obiektowo.
Rozwiązania, które przychodzą mi do głowy:
- „Obrzydliwości konstruktorów z długimi listami parametrów”
- Wzorzec konstruktora
java
oop
scala
immutability
Malax
źródło
źródło
Odpowiedzi:
Czy chcesz, aby po utworzeniu obiekt był zarówno łatwiejszy do odczytania, jak i niezmienny?
Myślę, że płynny interfejs POPRAWNIE WYKONANY byłby pomocny.
Wyglądałoby to tak (przykład czysto zmyślony):
Napisałem „PRAWIDŁOWO ZROBIONE” pogrubioną czcionką, ponieważ większość programistów Javy źle rozumie płynne interfejsy i zanieczyszcza swój obiekt metodą potrzebną do zbudowania obiektu, co jest oczywiście całkowicie błędne.
Sztuczka polega na tym, że tylko metoda build () faktycznie tworzy Foo (stąd Foo może być niezmienne).
FooFactory.create () , gdzieXXX (..) i withXXX (..) tworzą „coś innego”.
To coś innego może być FooFactory, oto jeden sposób, aby to zrobić ....
You FooFactory wyglądałby tak:
źródło
Foo
obiekcie, zamiast oddzielnegoFooFactory
.FooImpl
konstruktora z 8 parametrami. Jaka jest poprawa?immutable
i bałbym się, że ludzie będą ponownie używać obiektów fabrycznych, ponieważ tak myślą. Mam na myśli:FooFactory people = FooFactory.create().withType("person"); Foo women = people.withGender("female").build(); Foo talls = people.tallerThan("180m").build();
gdzietalls
teraz będą tylko kobiety. Nie powinno to mieć miejsca w przypadku niezmiennego interfejsu API.W Scali 2.8 można było używać nazwanych i domyślnych parametrów, a także
copy
metody na klasie case. Oto przykładowy kod:źródło
Cóż, rozważ to w Scali 2.8:
To oczywiście ma swój udział w problemach. Na przykład, spróbuj
espouse
iOption[Person]
, a następnie uzyskanie dwóch małżonków do siebie. Nie mogę wymyślić sposobu rozwiązania tego problemu bez uciekania się do pomocyprivate var
i / lubprivate
konstruktora oraz fabryki.źródło
Oto kilka innych opcji:
opcja 1
Spraw, aby implementacja była zmienna, ale oddziel interfejsy, które uwidacznia, jako mutowalne i niezmienne. Jest to zaczerpnięte z projektu biblioteki Swing.
Opcja 2
Jeśli Twoja aplikacja zawiera duży, ale wstępnie zdefiniowany zestaw niezmiennych obiektów (np. Obiekty konfiguracyjne), możesz rozważyć użycie frameworka Spring .
źródło
Warto pamiętać, że istnieją różne rodzaje niezmienności . W twoim przypadku myślę, że niezmienność "popsicle" będzie działać naprawdę dobrze:
Więc inicjalizujesz swój obiekt, a następnie ustawiasz jakąś flagę "zamrożenie", wskazującą, że nie jest już zapisywalny. Najlepiej jest ukryć mutację za funkcją, aby funkcja była nadal czysta dla klientów korzystających z twojego API.
źródło
clone()
do tworzenia nowych instancji.freeze()
metodę, ale tego nie robi , sytuacja może się pogorszyć.Możesz także sprawić, by niezmienne obiekty uwidaczniały metody, które wyglądają jak mutatory (takie jak addSibling), ale pozwolą im zwrócić nową instancję. To właśnie robią niezmienne kolekcje Scala.
Wadą jest to, że możesz utworzyć więcej instancji niż to konieczne. Ma to również zastosowanie tylko wtedy, gdy istnieją pośrednie prawidłowe konfiguracje (jak niektóre węzły bez rodzeństwa, co jest w porządku w większości przypadków), chyba że nie chcesz zajmować się częściowo zbudowanymi obiektami.
Na przykład krawędź grafu, która nie ma celu, ale nie jest prawidłową krawędzią grafu.
źródło
Rozważ cztery możliwości:
Dla mnie każdy z 2, 3 i 4 jest dostosowany do innej sytuacji. Pierwszy z nich jest trudny do pokochania z powodów przytoczonych przez OP i ogólnie jest objawem projektu, który przeszedł pewne pełzanie i wymaga refaktoryzacji.
To, co wymieniam jako (2), jest dobre, gdy za „fabryką” nie ma stanu, podczas gdy (3) jest projektem z wyboru, gdy istnieje stan. Używam (2) zamiast (3), gdy nie chcę martwić się wątkami i synchronizacją, i nie muszę martwić się o amortyzację kosztownej konfiguracji podczas produkcji wielu obiektów. (3), z drugiej strony, jest wywoływane, gdy prawdziwa praca idzie do budowy fabryki (ustawienie z SPI, odczyt plików konfiguracyjnych itp.).
Wreszcie, czyjaś odpowiedź wspomniała o opcji (4), w której masz wiele małych niezmiennych obiektów, a preferowanym wzorcem jest pobieranie wiadomości ze starych.
Zwróć uwagę, że nie jestem członkiem „fanklubu wzorców” - oczywiście, niektóre rzeczy są warte naśladowania, ale wydaje mi się, że gdy ludzie nadadzą im imiona i zabawne kapelusze, zaczynają żyć własnym życiem.
źródło
Inną potencjalną opcją jest refaktoryzacja w celu uzyskania mniejszej liczby konfigurowalnych pól. Jeśli grupy pól działają (głównie) ze sobą, zbierz je w swój własny, niezmienny obiekt. Konstruktory / konstruktory tego „małego” obiektu powinny być łatwiejsze w zarządzaniu, podobnie jak konstruktor / konstruktor dla tego „dużego” obiektu.
źródło
Używam C # i to są moje podejścia. Rozważać:
Opcja 1. Konstruktor z opcjonalnymi parametrami
Używany jako np
new Foo(5, b: new Bar(whatever))
. Nie dla wersji Java lub C # starszych niż 4.0. ale nadal warto pokazać, ponieważ jest to przykład, jak nie wszystkie rozwiązania są agnostykami językowymi.Opcja 2. Konstruktor pobierający pojedynczy obiekt parametru
Przykład użycia:
C # od 3.0 sprawia, że jest to bardziej eleganckie dzięki składni inicjatora obiektu (semantycznie równoważne z poprzednim przykładem):
Opcja 3:
Przeprojektuj swoją klasę, aby nie potrzebowała tak dużej liczby parametrów. Możesz podzielić jego obowiązki na wiele klas. Lub przekaż parametry nie do konstruktora, ale tylko do określonych metod na żądanie. Nie zawsze jest to opłacalne, ale kiedy jest, warto to zrobić.
źródło