Łańcuch operacji „instanceof” jest uważany za „zapach kodu”. Standardowa odpowiedź brzmi „użyj polimorfizmu”. Jak bym to zrobił w tym przypadku?
Istnieje wiele podklas klasy bazowej; żaden z nich nie jest pod moją kontrolą. Analogiczna sytuacja miałaby miejsce w przypadku klas Java Integer, Double, BigDecimal itp.
if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}
Mam kontrolę nad NumberStuff i tak dalej.
Nie chcę używać wielu linii kodu, w których wystarczyłoby kilka linii. (Czasami tworzę HashMap mapujący Integer.class na instancję IntegerStuff, BigDecimal.class na instancję BigDecimalStuff itp. Ale dzisiaj chcę czegoś prostszego.)
Chciałbym coś tak prostego jak to:
public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }
Ale Java po prostu nie działa w ten sposób.
Chciałbym używać statycznych metod podczas formatowania. Rzeczy, które formatuję, są złożone, gdzie Thing1 może zawierać tablicę Thing2s, a Thing2 może zawierać tablicę Thing1s. Miałem problem, kiedy zaimplementowałem moje elementy formatujące w ten sposób:
class Thing1Formatter {
private static Thing2Formatter thing2Formatter = new Thing2Formatter();
public format(Thing thing) {
thing2Formatter.format(thing.innerThing2);
}
}
class Thing2Formatter {
private static Thing1Formatter thing1Formatter = new Thing1Formatter();
public format(Thing2 thing) {
thing1Formatter.format(thing.innerThing1);
}
}
Tak, znam HashMap i trochę więcej kodu też może to naprawić. Ale „instanceof” wydaje się tak czytelny i łatwy do utrzymania w porównaniu. Czy jest coś prostego, ale nie śmierdzącego?
Uwaga dodana 10.05.2010:
Okazuje się, że prawdopodobnie w przyszłości zostaną dodane nowe podklasy, a mój istniejący kod będzie musiał z wdziękiem sobie z nimi radzić. HashMap on Class nie zadziała w takim przypadku, ponieważ Class nie zostanie znaleziona. Łańcuch instrukcji if, zaczynający się od najbardziej szczegółowego i kończący się na najbardziej ogólnym, jest prawdopodobnie najlepszy:
if (obj instanceof SubClass1) {
// Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
// Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
// Unknown class but it implements Interface3
// so handle those methods and properties
} else if (obj instanceof Interface4) {
// likewise. May want to also handle case of
// object that implements both interfaces.
} else {
// New (unknown) subclass; do what I can with the base class
}
źródło
[text](link)
do publikowania łączy w komentarzach.Odpowiedzi:
Może zainteresuje Cię ten wpis z bloga Steve'a Yegge'a na Amazon: „kiedy polimorfizm zawodzi” . Zasadniczo zajmuje się takimi przypadkami, w których polimorfizm sprawia więcej problemów niż rozwiązuje.
Problem polega na tym, że aby użyć polimorfizmu, musisz uczynić logikę „obsługi” częścią każdej „przełączającej” klasy - tj. W tym przypadku Integer itp. Oczywiście nie jest to praktyczne. Czasami nawet logicznie nie jest to właściwe miejsce na umieszczenie kodu. Zaleca podejście „instancja” jako mniejsze zło.
Podobnie jak we wszystkich przypadkach, w których jesteś zmuszony pisać śmierdzący kod, trzymaj go zapiętym na guziki w jednej metodzie (lub co najwyżej jednej klasie), aby zapach nie wyciekł.
źródło
instanceof
.Monster
klasy metodę , której zadaniem jest po prostu wprowadzenie obiektu, np. „Witaj, jestem orkiem. Co o mnie myślisz?”. Uparty elf może następnie oceniać potwory na podstawie tych „powitań”, używając kodu podobnego dobool visitOrc(Orc orc) { return orc.stench()<threshold; } bool visitFlower(Flower flower) { return flower.colour==magenta; }
. Jedyny kod specyficzny dla potwora będzie wtedyclass Orc { <T> T accept(MonsterVisitor<T> v) { v.visitOrc(this); } }
wystarczający do każdej inspekcji potwora raz na zawsze.Jak podkreślono w komentarzach, wzorzec odwiedzających byłby dobrym wyborem. Ale bez bezpośredniej kontroli nad celem / akceptantem / odwiedzającym nie możesz zaimplementować tego wzorca. Oto jeden ze sposobów, w jaki wzorzec gości mógłby być nadal używany tutaj, mimo że nie masz bezpośredniej kontroli nad podklasami za pomocą opakowań (na przykładzie liczby całkowitej):
Oczywiście opakowanie ostatniej klasy można uznać za własny zapach, ale może dobrze pasuje do twoich podklas. Osobiście uważam, że nie ma
instanceof
tu aż tak nieprzyjemnego zapachu, zwłaszcza jeśli ogranicza się to do jednej metody i chętnie bym go użył (prawdopodobnie ponad moją własną sugestią powyżej). Jak mówisz, jest całkiem czytelny, bezpieczny i łatwy w utrzymaniu. Jak zawsze, nie komplikuj.źródło
instanceof
do wywoływania właściwejhandle()
metody, będziesz teraz musiał jej użyć, aby wywołać odpowiedniXWrapper
konstruktor ...Zamiast ogromnego
if
, możesz umieścić obsługiwane instancje w mapie (klucz: klasa, wartość: obsługa).Jeśli wyszukiwanie według klucza powróci
null
, wywołaj specjalną metodę obsługi, która próbuje znaleźć pasującą procedurę obsługi (na przykład wywołującisInstance()
każdy klucz w mapie).Gdy program obsługi zostanie znaleziony, zarejestruj go pod nowym kluczem.
To sprawia, że sprawa ogólna jest szybka i prosta, a także umożliwia obsługę dziedziczenia.
źródło
Możesz użyć refleksji:
Możesz rozwinąć pomysł, aby ogólnie obsługiwać podklasy i klasy, które implementują określone interfejsy.
źródło
if then else
łańcuchem, aby dodawać, usuwać lub modyfikować moduły obsługi. Kod jest mniej wrażliwy na zmiany. Więc powiedziałbym, że z tego powodu jest lepszy odinstanceof
podejścia. Tak czy inaczej, chciałem tylko podać ważną alternatywę.getMethod(String name, Class<?>... parameterTypes)
? Albo chciałbym wymienić==
zeisAssignableFrom
na typ kontroli parametru.Możesz rozważyć wzorzec łańcucha odpowiedzialności . Na pierwszy przykład:
a potem podobnie dla twoich innych treserów. Następnie jest to przypadek łączenia ze sobą elementów StuffHandlers w kolejności (od najbardziej do najmniej szczegółowych, z końcową funkcją obsługi „rezerwowej”), a kod programu wysyłającego jest po prostu
firstHandler.handle(o);
.(Alternatywą jest, zamiast używać łańcucha, po prostu mieć
List<StuffHandler>
w swojej klasie dyspozytora i zapętlić listę, dopóki niehandle()
zwróci true).źródło
Myślę, że najlepszym rozwiązaniem jest HashMap z klasą jako kluczem i handlerem jako wartością. Zwróć uwagę, że rozwiązanie oparte na HashMap działa ze stałą złożonością algorytmiczną θ (1), podczas gdy łańcuch zapachu if-instanceof-else działa w liniowej złożoności algorytmicznej O (N), gdzie N to liczba łączy w łańcuchu if-instanceof-else (tj. liczba różnych klas do obsługi). Zatem wydajność rozwiązania opartego na HashMap jest asymptotycznie wyższa N razy niż wydajność rozwiązania łańcuchowego if-instanceof-else. Weź pod uwagę, że musisz inaczej obsługiwać różne elementy potomne klasy Message: Message1, Message2 itd. Poniżej znajduje się fragment kodu do obsługi opartej na HashMap.
Więcej informacji na temat użycia zmiennych typu Class w Javie: http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html
źródło
Po prostu przejdź z instancją. Wszystkie obejścia wydają się bardziej skomplikowane. Oto post na blogu, który o tym mówi: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html
źródło
Rozwiązałem ten problem za pomocą
reflection
(około 15 lat temu w erze pre Generics).Zdefiniowałem jedną klasę generyczną (abstrakcyjną klasę bazową). Zdefiniowałem wiele konkretnych implementacji klasy bazowej. Każda konkretna klasa zostanie załadowana z parametrem nazwa_klasy. Ta nazwa klasy jest definiowana jako część konfiguracji.
Klasa bazowa definiuje wspólny stan wszystkich klas konkretnych, a klasy konkretne modyfikują stan, zastępując reguły abstrakcyjne zdefiniowane w klasie bazowej.
W tym czasie nie znam nazwy tego mechanizmu, który był znany jako
reflection
.Jeszcze kilka alternatyw są wymienione w tym artykule :
Map
aenum
oprócz refleksji.źródło
GenericClass
interface
Dodaj metodę w BaseClass, która zwraca nazwę klasy. I zastąp metody konkretną nazwą klasy
Teraz użyj obudowy przełącznika w następujący sposób:
źródło