Ostatnio miałem zwyczaj „maskowania” kolekcji Java za pomocą przyjaznych dla ludzi nazw klas. Kilka prostych przykładów:
// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}
Lub:
// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}
Kolega zwrócił mi uwagę, że jest to zła praktyka i wprowadza opóźnienie / opóźnienie, a także jest anty-wzorem OO. Rozumiem, że wprowadza bardzo niewielki poziom wydajności, ale nie mogę sobie wyobrazić, że to w ogóle znaczące. Pytam więc: czy to dobrze czy źle i dlaczego?
java
anti-patterns
generics
collections
herpylderp
źródło
źródło
ChangeList
kompilacja ulegnie awariiextends
, ponieważ List jest interfejsem, wymagaimplements
. @randomA to, co sobie wyobrażasz, nie trafia w sedno z powodu tego błęduimplementation
ieHashMap
lub,TreeMap
a to, co miał, było literówką.Odpowiedzi:
Opóźnienie / opóźnienie? W tej sprawie nazywam BS. Z tej praktyki powinno być dokładnie zero. ( Edycja: W komentarzach zaznaczono, że może to w rzeczywistości hamować optymalizacje wykonywane przez maszynę wirtualną HotSpot. Nie wiem wystarczająco dużo o implementacji maszyny wirtualnej, aby to potwierdzić lub zaprzeczyć. Opierałem swój komentarz na C ++ implementacja funkcji wirtualnych).
Istnieje pewien narzut kodu. Musisz utworzyć wszystkie konstruktory z pożądanej klasy podstawowej, przekazując ich parametry.
Nie postrzegam tego też jako anty-wzoru. Uważam to jednak za straconą okazję. Zamiast tworzyć klasę, która wyprowadza klasę podstawową tylko ze względu na zmianę nazwy, może zamiast tego stworzysz klasę, która zawiera kolekcję i oferuje ulepszony interfejs dla poszczególnych przypadków? Czy pamięć podręczna widgetów naprawdę powinna oferować pełny interfejs mapy? A może powinien oferować wyspecjalizowany interfejs?
Ponadto w przypadku kolekcji wzorzec po prostu nie działa razem z ogólną zasadą używania interfejsów, a nie implementacji - to znaczy, że w zwykłym kodzie kolekcji należy utworzyć
HashMap<String, Widget>
a następnie przypisać ją do zmiennej typuMap<String, Widget>
. TwojaWidgetCache
puszka nie przedłużaćMap<String, Widget>
, bo to interfejs. Nie może być interfejsem rozszerzającym interfejs podstawowy, ponieważHashMap<String, Widget>
nie implementuje tego interfejsu, podobnie jak żadna inna standardowa kolekcja. I chociaż możesz uczynić go klasą rozszerzającą sięHashMap<String, Widget>
, musisz następnie zadeklarować zmienne jakoWidgetCache
lubMap<String, Widget>
, a pierwsza utraci elastyczność zastępowania innej kolekcji (być może kolekcji leniwego ładowania ORM), podczas gdy drugi rodzaj pokona punkt posiadania klasy.Niektóre z tych kontrapunktów dotyczą także mojej zaproponowanej klasy specjalistycznej.
To są wszystkie kwestie do rozważenia. Może to być właściwy wybór. W obu przypadkach argumenty oferowane przez twojego kolegę są nieważne. Jeśli myśli, że to anty-wzór, powinien to nazwać.
źródło
public class Properties extends Hashtable<Object,Object>
injava.util
mówi: „Ponieważ właściwości dziedziczą z Hashtable, metody put i putAll można zastosować do obiektu właściwości. Ich użycie jest zdecydowanie odradzane, ponieważ pozwalają wywołującemu wstawiać wpisy, których klucze lub wartości nie są ciągami. ”. Kompozycja byłaby znacznie czystsza.Według IBM jest to w rzeczywistości anty-wzorzec. Te klasy „typedef” nazywane są typami psuedo.
Artykuł wyjaśnia to znacznie lepiej niż ja, ale postaram się to podsumować na wypadek, gdyby link się złączył:
WidgetCache
nie może obsłużyćMap<String, Widget>
W artykule proponują następującą sztuczkę, aby ułatwić życie bez używania pseudo typów:
Który działa z powodu automatycznego wnioskowania typu.
(Doszedłem do tej odpowiedzi przez to pytanie dotyczące przepełnienia stosu)
źródło
newHashMap
operator diamentów nie rozwiązuje teraz potrzeby?WidgetCache
mogła być przypisana do zmiennej typuWidgetCache
lubMap<String,Widget>
bez rzutowania, ale tam był środkiem, za pomocą którego metoda statycznaWidgetCache
mogła odwoływać sięMap<String,Widget>
i zwracać go jako typWidgetCache
po wykonaniu dowolnej pożądanej weryfikacji. Taka funkcja może być szczególnie przydatna w przypadku leków generycznych, których nie trzeba by było kasować (ponieważ ...Skuteczność działania byłaby co najwyżej ograniczona do wyszukiwania vtable, które najprawdopodobniej już ponosisz. To nie jest uzasadniony powód do sprzeciwu.
Sytuacja jest na tyle powszechna, że większość wszystkich statycznie typowanych języków programowania ma specjalną składnię dla typów aliasingu, zwykle nazywaną a
typedef
. Java prawdopodobnie ich nie skopiowała, ponieważ pierwotnie nie miała sparametryzowanych typów. Rozszerzenie klasy nie jest idealne z powodów, dla których Sebastian tak dobrze opisał swoją odpowiedź, ale może to być rozsądne obejście ograniczonej składni Javy.Typedef ma wiele zalet. Wyrażają intencje programisty jaśniej, lepszą nazwą, na bardziej odpowiednim poziomie abstrakcji. Łatwiej jest je wyszukiwać w celach debugowania lub refaktoryzacji. Zastanów się nad znalezieniem wszędzie
WidgetCache
używanego znaku a zamiast znalezieniem tych konkretnych zastosowańMap
. Łatwiej je zmienić, na przykład jeśli później okaże się, że potrzebujeszLinkedHashMap
zamiast tego, a nawet własnego niestandardowego kontenera.źródło
Sugeruję, jak już wspomniano, użycie kompozycji zamiast dziedziczenia, abyś mógł udostępnić tylko te metody, które są naprawdę potrzebne, z nazwami pasującymi do zamierzonego przypadku użycia. Czy użytkownicy Twojej klasy naprawdę muszą wiedzieć, że
WidgetCache
to mapa? I być w stanie zrobić z nim wszystko, czego chcą? A może po prostu muszą wiedzieć, że jest to pamięć podręczna dla widżetów?Przykładowa klasa z mojej bazy kodu z rozwiązaniem podobnego problemu, który opisałeś:
Widać, że wewnętrznie jest to „mapa”, ale interfejs publiczny tego nie pokazuje. I ma metody „przyjazne dla programistów”, takie jak
appendMessage(Locale locale, String code, String message)
łatwiejszy i bardziej sensowny sposób wstawiania nowych wpisów. Użytkownicy klasy nie mogą na przykład tego zrobićtranslations.clear()
, ponieważTranslations
nie rozszerzają sięMap
.Opcjonalnie zawsze możesz przekazać niektóre potrzebne metody wewnętrznie używanej mapie.
źródło
Widzę to jako przykład znaczącej abstrakcji. Dobra abstrakcja ma kilka cech:
Ukrywa szczegóły implementacji, które nie mają znaczenia dla używającego go kodu.
Jest tak skomplikowany, jak musi być.
Rozszerzając, ujawniasz cały interfejs rodzica, ale w wielu przypadkach większość z nich może być lepiej ukryta, więc chcesz robić to, co sugeruje Sebastian Redl, preferować kompozycję zamiast dziedziczenia i dodać instancję rodzica jako prywatny członek twojej klasy niestandardowej. Dowolną metodę interfejsu, która ma sens dla twojej abstrakcji, można łatwo delegować do (w twoim przypadku) wewnętrznej kolekcji.
Jeśli chodzi o wpływ na wydajność, zawsze dobrym pomysłem jest najpierw zoptymalizować kod pod kątem czytelności, a jeśli podejrzewa się wpływ na wydajność, profiluj kod, aby porównać dwie implementacje.
źródło
+1 do innych odpowiedzi tutaj. Dodam też, że społeczność DDD (Domain Driven Design) uważa to za bardzo dobrą praktykę. Opowiadają się za tym, aby twoja domena i interakcje z nią miały znaczenie semantyczne, w przeciwieństwie do podstawowej struktury danych. Może
Map<String, Widget>
to być pamięć podręczna, ale może to być także coś innego. Prawidłowo zrobione w My Not So Humble Opinion (IMNSHO) jest modelowanie tego, co reprezentuje kolekcja, w tym przypadku pamięć podręczna.Dodam ważną zmianę w tym, że opakowanie klasy domeny wokół podstawowej struktury danych powinno prawdopodobnie mieć również inne zmienne składowe lub funkcje, które naprawdę sprawiają, że jest to klasa domeny z interakcjami, a nie tylko struktura danych (gdyby tylko Java miała typy wartości , dostaniemy je w Javie 10 - obietnica!)
Ciekawie będzie zobaczyć, jaki wpływ na to wszystko będą miały strumienie Java 8, mogę sobie wyobrazić, że być może niektóre publiczne interfejsy wolą radzić sobie ze Strumieniem (wstaw zwykłą prymityw Java lub Łańcuchem) w przeciwieństwie do obiektu Java.
źródło