Czy dobrą praktyką jest stosowanie odbicia, jeśli znacznie zmniejsza ilość kodu płyty grzewczej?
Zasadniczo istnieje kompromis między wydajnością i być może czytelnością z jednej strony a abstrakcją / automatyzacją / redukcją kodu płyty kotłowej z drugiej strony.
Edycja: Oto przykład zalecanego zastosowania odbicia .
Dla przykładu załóżmy, że istnieje klasa abstrakcyjna, Base
która ma 10 pól i 3 podklasy SubclassA
, SubclassB
a SubclassC
każda z 10 różnymi polami; wszystkie są prostymi fasolami. Problem polega na tym, że otrzymujesz dwa Base
odwołania do typów i chcesz sprawdzić, czy odpowiadające im obiekty są tego samego (pod) typu i są równe.
Jako rozwiązania istnieje surowe rozwiązanie, w którym najpierw sprawdza się, czy typy są równe, a następnie sprawdza się wszystkie pola lub można użyć odbicia i dynamicznie sprawdzać, czy są one tego samego typu i iterować wszystkie metody zaczynające się od „get” (konwencja ponad konfigurację), wywołaj je na obu obiektach i wywołaj równe na wynikach.
boolean compare(Base base1, Base, base2) {
if (base1 instanceof SubclassA && base2 instanceof SubclassA) {
SubclassA subclassA1 = (SubclassA) base1;
SubclassA subclassA2 = (SubclassA) base2;
compare(subclassA1, subclassA2);
} else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
//the same
}
//boilerplate
}
boolean compare(SubclassA subA1, SubclassA subA2) {
if (!subA1.getField1().equals(subA2.getField1)) {
return false;
}
if (!subA1.getField2().equals(subA2.getField2)) {
return false;
}
//boilerplate
}
boolean compare(SubclassB subB1, SubclassB subB2) {
//boilerplate
}
//boilerplate
//alternative with reflection
boolean compare(Base base1, Base base2) {
if (!base1.getClass().isAssignableFrom(base2.getClass())) {
System.out.println("not same");
System.exit(1);
}
Method[] methods = base1.getClass().getMethods();
boolean isOk = true;
for (Method method : methods) {
final String methodName = method.getName();
if (methodName.startsWith("get")) {
Object object1 = method.invoke(base1);
Object object2 = method.invoke(base2);
if(object1 == null || object2 == null) {
continue;
}
if (!object1.equals(object2)) {
System.out.println("not equals because " + object1 + " not equal with " + object2);
isOk = false;
}
}
}
if (isOk) {
System.out.println("is OK");
}
}
źródło
Odpowiedzi:
Odbicie został stworzony do celów konkretnej, aby odkryć funkcjonalność klasy, która była nieznana w czasie kompilacji, podobny do tego, co
dlopen
idlsym
funkcje zrobić w C. Jakiekolwiek użycie poza które powinny być mocno analizowane.Czy zdarzyło Ci się kiedyś, że sami projektanci Java napotkali ten problem? Dlatego praktycznie każda klasa ma
equals
metodę. Różne klasy mają różne definicje równości. W niektórych okolicznościach obiekt pochodny może być równy obiektowi podstawowemu. W niektórych okolicznościach równość można ustalić na podstawie prywatnych pól bez osób pobierających. Nie wieszDlatego każdy obiekt, który chce niestandardowej równości, powinien wdrożyć
equals
metodę. W końcu będziesz chciał umieścić obiekty w zestawie lub użyć ich jako indeksu skrótu, a następnie będziesz musiał zaimplementowaćequals
. Inne języki robią to inaczej, ale Java używaequals
. Powinieneś trzymać się konwencji swojego języka.Również kod „płyty grzewczej”, jeśli zostanie umieszczony w odpowiedniej klasie, jest dość trudny do zepsucia. Odbicie dodaje dodatkową złożoność, co oznacza dodatkowe szanse na błędy. Na przykład w Twojej metodzie dwa obiekty są uważane za równe, jeśli jeden zwraca
null
określone pole, a drugi nie. Co się stanie, jeśli jeden z twoich pobierających zwróci jeden z twoich przedmiotów bez odpowiedniegoequals
? Twojaif (!object1.equals(object2))
wola zawiedzie. Również podatność na błędy polega na tym, że odbicie jest rzadko używane, więc programiści nie są tak dobrze zaznajomieni z jego błędami.źródło
Nadużywanie refleksji prawdopodobnie zależy od używanego języka. Tutaj używasz Java. W takim przypadku refleksję należy stosować ostrożnie, ponieważ często jest to tylko obejście złego projektu.
Porównując różne klasy, jest to idealny problem do zastępowania metod . Zauważ, że wystąpienia dwóch różnych klas nigdy nie powinny być uważane za równe. Możesz porównać dla równości tylko wtedy, gdy masz instancje tej samej klasy. Zobacz /programming/27581/overriding-equals-and-hashcode-in-java, aby uzyskać przykład prawidłowego wdrożenia porównania równości.
źródło
compare()
metoda, zakładając, że każda metoda zaczynająca się od „get” jest geterem i jest bezpieczna i odpowiednia do wywołania w ramach operacji porównania. Jest to naruszenie definicji interfejsu obiektu i chociaż może to być celowe, prawie zawsze jest błędne.get
od gettera, a nie niestandardowa metoda zwraca coś?Myślę, że masz tutaj dwa problemy.
Kod dynamiczny a statyczny
To odwieczne pytanie, a odpowiedź jest bardzo pozytywna.
Z jednej strony twój kompilator jest bardzo dobry w wyłapywaniu wszelkiego rodzaju złego kodu. Dokonuje tego poprzez różne formy analizy, przy czym analiza typów jest powszechna. Wie, że nie można użyć
Banana
obiektu w kodzie, który oczekujeCog
. Mówi to przez błąd kompilacji.Teraz może to zrobić tylko wtedy, gdy może wywnioskować zarówno zaakceptowany Typ, jak i dany Typ z kontekstu. Ile można wywnioskować i jak ogólne jest to wnioskowanie, w dużej mierze zależy od używanego języka. Java może wnioskować o typach informacji za pomocą mechanizmów takich jak dziedziczenie, interfejsy i ogólne. Przebieg jest różny, niektóre inne języki zapewniają mniej mechanizmów, a niektóre zapewniają więcej. Nadal sprowadza się do tego, co kompilator może wiedzieć, że jest prawdziwy.
Z drugiej strony twój kompilator nie jest w stanie przewidzieć kształtu obcego kodu, a czasem ogólny algorytm może być wyrażony na wielu typach, których nie można łatwo wyrazić za pomocą systemu typów języka. W takich przypadkach kompilator nie zawsze może znać wynik z góry i może nawet nie być w stanie wiedzieć, jakie pytanie zadać. Odbicie, interfejsy i klasa Object są sposobem Java radzenia sobie z tymi problemami. Będziesz musiał zapewnić prawidłowe kontrole i obsługę, ale nie jest niezdrowe mieć tego rodzaju kod.
Czy sprecyzować kod, czy też ogólnie, sprowadza się do problemu, który próbujesz rozwiązać. Jeśli możesz to łatwo wyrazić za pomocą systemu typów, zrób to. Pozwól kompilatorowi wykorzystać jego mocne strony i pomóc. Jeśli system typów prawdopodobnie nie wiedziałby z góry (kod obcy) lub system typów jest nieodpowiedni do ogólnej implementacji algorytmu, wówczas właściwym narzędziem jest odbicie (i inne dynamiczne środki).
Pamiętaj tylko, że wyjście poza system pisma twojego języka jest zaskakujące. Wyobraź sobie, że podchodzisz do przyjaciela i zaczynasz rozmowę po angielsku. Nagle dodaj kilka słów z hiszpańskiego, francuskiego i kantońskiego, które dokładnie wyrażają twoje myśli. Kontekst wiele powie twojemu przyjacielowi, ale może również nie wiedzieć, jak radzić sobie z tymi słowami, co prowadzi do różnego rodzaju nieporozumień. Lepiej radzi sobie z tymi nieporozumieniami niż wyjaśnianie tych pomysłów w języku angielskim przy użyciu większej liczby słów?
Niestandardowa równość
Rozumiem, że Java w dużej mierze opiera się na
equals
metodzie ogólnego porównywania dwóch obiektów, ale nie zawsze jest odpowiednia w danym kontekście.Jest inny sposób, a także standard Java. Nazywa się to Komparator .
To, jak zaimplementujesz swój komparator, będzie zależeć od tego, co porównasz i od tego, jak.
equals
implementacji.źródło
Wolę unikać programowania refleksyjnego, ponieważ jest to możliwe
Jest również znacznie mniej wydajny niż proste wywołania metod; kiedyś był wolniejszy o rząd wielkości lub więcej.
Kod kontroli statycznej
Każdy kod odblaskowy wyszukuje klasy i metody za pomocą ciągów; w oryginalnym przykładzie szuka dowolnej metody zaczynającej się od „get”; zwróci gettery, ale oprócz tego inne metody, takie jak „gettysburgAddress ()”. Reguły można zaostrzyć w kodzie, ale chodzi o to, że jest to kontrola czasu wykonywania ; IDE i kompilator nie mogą pomóc. Zasadniczo nie lubię kodu „ciągłego pisania” lub „prymitywnej obsesji”.
Trudniej jest uzasadnić
Kod odblaskowy jest bardziej szczegółowy niż proste wywołania metod. Więcej kodu = więcej błędów lub przynajmniej większy potencjał błędów, więcej kodu do czytania, testowania itp. Mniej to więcej.
Trudniej zrefaktoryzować
Ponieważ kod oparty jest na łańcuchach / dynamice; gdy tylko na scenie pojawi się refleksja, nie można refaktoryzować kodu ze 100% pewnością przy użyciu narzędzi refaktoryzacyjnych IDE, ponieważ IDE nie może rozpoznać zastosowań odblaskowych.
Zasadniczo unikaj refleksji w ogólnym kodzie, jeśli to w ogóle możliwe; szukaj ulepszonego projektu.
źródło
Nadużywanie czegokolwiek z definicji jest złe, prawda? Więc na razie pozbądźmy się (ponad).
Czy nazwałbyś niewiarygodnie ciężkie wewnętrzne użycie odbicia wiosny złym nawykiem?
Wiosenne oswajanie odbicia za pomocą adnotacji - podobnie Hibernacja (i prawdopodobnie dziesiątki / setki innych narzędzi).
Postępuj zgodnie z tymi wzorcami, jeśli używasz go we własnym kodzie. Użyj adnotacji, aby upewnić się, że IDE użytkownika nadal może im pomóc (nawet jeśli jesteś jedynym „użytkownikiem” swojego kodu, nieostrożne użycie refleksji prawdopodobnie wróci, by ostatecznie ugryźć cię w tyłek).
Jednak bez uwzględnienia sposobu, w jaki kod będzie wykorzystywany przez programistów, nawet najprostsze użycie refleksji jest prawdopodobnie nadmierne.
źródło
Myślę, że większość z tych odpowiedzi nie ma sensu.
Tak, prawdopodobnie powinieneś napisać
equals()
ihashcode()
, jak zauważył @KarlBielefeldt.Ale dla klasy z wieloma polami może to być nużąca płyta.
To zależy .
Inna możliwość: jeśli twoje zajęcia naprawdę mają tyle pól, że pisanie równości jest uciążliwe, zastanów się
Map.equals()
do porównania. (lubMap.hashcode()
)np. ( uwaga : ignorowanie czeków zerowych, prawdopodobnie powinno się używać Wyliczeń zamiast kluczy Łańcuchowych, wiele nie pokazano ...)
źródło