Czy to prawda, że nadrzędnymi konkretnymi metodami jest zapach kodu? Ponieważ uważam, że jeśli chcesz zastąpić konkretne metody:
public class A{
public void a(){
}
}
public class B extends A{
@Override
public void a(){
}
}
można go przepisać jako
public interface A{
public void a();
}
public class ConcreteA implements A{
public void a();
}
public class B implements A{
public void a(){
}
}
a jeśli B chce ponownie użyć a () w A, można go przepisać jako:
public class B implements A{
public ConcreteA concreteA;
public void a(){
concreteA.a();
}
}
który nie wymaga dziedziczenia, aby zastąpić metodę, czy to prawda?
java
object-oriented
overriding
grgrr
źródło
źródło
virtual
słowa kluczowego do obsługi zastąpień. Tak więc problem jest bardziej (lub mniej) niejednoznaczny ze względu na szczegóły języka.final
modyfikator istnieje dokładnie w takich sytuacjach. Jeśli zachowanie metody jest takie, że nie jest przeznaczone do zastąpienia, oznacz ją jakofinal
. W przeciwnym razie spodziewaj się, że programiści mogą go zastąpić.Odpowiedzi:
Nie, to nie jest zapach kodu.
Odpowiedzialność każdej klasy leży w starannym rozważeniu, czy podklasowanie jest właściwe i które metody można zastąpić .
Klasa może zdefiniować siebie lub dowolną metodę jako ostateczną, lub może nałożyć ograniczenia (modyfikatory widoczności, dostępne konstruktory) na sposób i miejsce jej podziału.
Zwykłym przypadkiem zastąpienia metod jest domyślna implementacja w klasie bazowej, którą można dostosować lub zoptymalizować w podklasie (szczególnie w Javie 8 wraz z pojawieniem się domyślnych metod w interfejsach).
Alternatywa dla nadrzędnym jest zachowanie wzoru Strategia , w której zachowanie jest wydobywane jako interfejs i implementacja może być ustawiony w klasie.
źródło
A
implementowaćDescriptionProvider
?A
może implementowaćDescriptionProvider
, a zatem być domyślnym dostawcą opisu. Ale chodzi tutaj o rozdzielenie problemów:A
wymaga opisu (niektóre inne klasy też mogą), podczas gdy inne implementacje je zapewniają.Tak, ogólnie rzecz biorąc, nadrzędnymi konkretnymi metodami jest zapach kodu.
Ponieważ metoda podstawowa jest powiązana z zachowaniem, które programiści zwykle szanują, zmiana ta będzie prowadzić do błędów, gdy twoja implementacja zrobi coś innego. Co gorsza, jeśli zmienią zachowanie, poprzednio poprawna implementacja może zaostrzyć problem. Ogólnie rzecz biorąc, dość trudne jest przestrzeganie zasady substytucji Liskowa w przypadku nietrywialnych konkretnych metod.
Teraz są przypadki, w których można to zrobić i jest to dobre. Półtrywialne metody można nieco bezpiecznie ominąć. Podstawowe metody, które istnieją tylko jako „rozsądne” zachowanie, które ma być nadrzędne, mają dobre zastosowanie.
Dlatego wydaje mi się to zapachem - czasem dobrze, zwykle źle, spójrz, aby się upewnić.
źródło
Number
klasa zincrement()
metodą, która ustawia wartość na następną prawidłową wartość. Jeśli podklasujesz to w miejsce,EvenNumber
gdzieincrement()
dodaje dwa zamiast jednego (a ustawiający wymusza równość), pierwsza propozycja OP wymaga zduplikowanych implementacji każdej metody w klasie, a jego druga wymaga pisania metod otoki w celu wywołania metod w elemencie. Częścią dziedziczenia jest, aby klasy dziecięce wyjaśniły, czym różnią się od rodziców, podając tylko te metody, które muszą być różne.Jeśli można go przepisać w ten sposób, oznacza to, że masz kontrolę nad obiema klasami. Ale wtedy wiesz, czy zaprojektowałeś w ten sposób klasę A, a jeśli tak, to nie jest to zapach kodu.
Jeśli z drugiej strony nie masz kontroli nad klasą podstawową, nie możesz przepisać w ten sposób. Więc albo klasa została zaprojektowana w taki sposób, aby wyprowadzić ją w ten sposób, w takim przypadku po prostu śmiało, nie jest to zapach kodu, albo nie było, w którym to przypadku masz dwie opcje: poszukaj innego rozwiązania w ogóle, lub idź naprzód , ponieważ atuty robocze projektują czystość.
źródło
abstract
; ale w wielu przypadkach nie musi tak być. 2) jeśli nie można go przesłonić, powinien byćfinal
(nie-wirtualny). Ale wciąż istnieje wiele przypadków, w których metody mogą zostać zastąpione. 3) jeśli dalszy programista nie czyta dokumentów, postrzelą się w stopę, ale zrobią to bez względu na zastosowane środki.Po pierwsze, chciałbym tylko zaznaczyć, że to pytanie ma zastosowanie tylko w niektórych systemach pisania. Na przykład, w typowania strukturalnych i kaczka wpisywanie systemów - klasa po prostu mając metodę o tej samej nazwie i podpis będzie oznaczać , że klasa była wpisać kompatybilny (przynajmniej dla tego konkretnego sposobu, w przypadku Duck wpisywanie).
Jest to (oczywiście) bardzo różne od dziedziny języków o typie statycznym , takich jak Java, C ++, itp. Jak również charakter silnego pisania , używanego zarówno w Javie i C ++, jak i C # i innych.
Domyślam idziesz z tła Java, gdzie metody muszą być wyraźnie oznaczone jako final jeśli projektant kontrakt zamierza dla nich nie do zmiany. Jednak w językach takich jak C # ustawienie domyślne jest odwrotne. Metody (bez dekoracji, szczególne słowa kluczowe), „ zamknięte ” (C # 's wersja
final
) i musi być jawnie zadeklarowane jakovirtual
jeśli są one dopuszczone do być pominięte.Jeśli interfejs API lub biblioteka została zaprojektowana w tych językach przy użyciu konkretnej metody, którą można zastąpić, wówczas (przynajmniej w niektórych przypadkach) musi mieć sens zastąpienie tej metody . Dlatego nie jest to zapach kodu zastępujący niezamkniętą / wirtualną / nie
final
konkretną metodę. (Zakłada się oczywiście, że projektanci API przestrzegają konwencji i albo oznaczają jakovirtual
(C #), albo oznaczają jakofinal
(Java) odpowiednie metody do tego, co projektują.)Zobacz też
źródło
Tak, ponieważ może to prowadzić do wywołania super -wzorca. Może również denaturować początkowy cel metody, wprowadzać skutki uboczne (=> błędy) i powodować niepowodzenia testów. Czy użyłbyś klasy, której testy jednostkowe są czerwone (wypełnianie)? Nie będę
Pójdę jeszcze dalej, mówiąc, że początkowy zapach kodu występuje, gdy metoda nie jest
abstract
anifinal
. Ponieważ pozwala to zmienić (poprzez przesłonięcie) zachowanie skonstruowanej istoty (to zachowanie może zostać utracone lub zmienione). Zabstract
ifinal
intencja jest jednoznaczna:Najbezpieczniejszym sposobem na dodanie zachowania do istniejącej klasy jest to, co pokazuje twój ostatni przykład: użycie wzoru dekoratora.
źródło
Object.toString()
Javie ... Gdyby tak byłoabstract
, wiele domyślnych rzeczy nie działałoby, a kod nawet nie skompilowałby się, gdyby ktoś nie przesłoniłtoString()
metody! Gdyby tak byłofinal
, sytuacja byłaby jeszcze gorsza - brak możliwości konwersji obiektów na ciąg znaków, z wyjątkiem Java. Jak mówi odpowiedź @Peter Walser, ważne są implementacje domyślne (takie jakObject.toString()
) . Zwłaszcza w domyślnych metodach Java 8.toString()
było alboabstract
czyfinal
byłoby to ból. Moja osobista opinia jest taka, że ta metoda jest zepsuta i lepiej byłoby w ogóle jej nie mieć (jak equals i hashCode ). Pierwotnym celem domyślnych ustawień Javy jest umożliwienie ewolucji API z retrokompatybilnością.