Jakie są dobre powody, aby zabronić dziedziczenia w Javie, na przykład używając klas końcowych lub klas używających pojedynczego, prywatnego konstruktora bez parametrów? Jakie są dobre powody, dla których metoda jest ostateczna?
Jakie są dobre powody, aby zabronić dziedziczenia w Javie, na przykład używając klas końcowych lub klas używających pojedynczego, prywatnego konstruktora bez parametrów? Jakie są dobre powody, dla których metoda jest ostateczna?
Odpowiedzi:
Najlepszym odniesieniem jest punkt 19 znakomitej książki Joshuy Blocha „Efektywna Java”, zatytułowanej „Projektuj i dokumentuj w celu dziedziczenia albo zakazuj”. (To punkt 17 w drugim wydaniu i punkt 15 w pierwszym wydaniu). Naprawdę powinieneś go przeczytać, ale podsumuję.
Interakcja klas odziedziczonych z rodzicami może być zaskakująca i nieprzewidywalna, jeśli przodek nie został zaprojektowany do dziedziczenia.
Dlatego klasy powinny być dwojakiego rodzaju:
Klasy zaprojektowane do rozszerzania i posiadające wystarczającą dokumentację, aby opisać, jak należy to zrobić
Zajęcia oceniane jako końcowe
Jeśli piszesz czysto wewnętrzny kod, może to być trochę przesada. Jednak dodatkowy wysiłek związany z dodaniem pięciu znaków do pliku klasy jest bardzo mały. Jeśli piszesz tylko do użytku wewnętrznego, przyszły programista może zawsze usunąć „końcowy” - możesz o tym myśleć jako o ostrzeżeniu mówiącym, że „ta klasa nie została zaprojektowana z myślą o dziedziczeniu”.
Możesz chcieć, aby metoda była ostateczna, aby nadpisywanie klas nie zmieniało zachowania liczonego w innych metodach. Metody wywoływane w konstruktorach są często uznawane za końcowe, więc podczas tworzenia obiektów nie ma żadnych przykrych niespodzianek.
final
.
Jednym z powodów, dla których klasa kończy się klasą, byłaby chęć narzucenia kompozycji zamiast dziedziczenia. Jest to ogólnie pożądane, aby uniknąć ścisłego powiązania między klasami.
Istnieją 3 przypadki użycia, w których możesz przejść do ostatecznych metod.
Cel zaliczenia klasy:
Aby żadne ciało nie mogło rozszerzyć tych klas i zmienić ich zachowania.
Np .: Klasa opakowująca Integer jest klasą końcową. Jeśli ta klasa nie jest ostateczna, to każdy może rozszerzyć Integer na swoją własną klasę i zmienić podstawowe zachowanie klasy całkowitej. Aby tego uniknąć, java utworzyła wszystkie klasy opakowania jako klasy końcowe.
DerivedInteger
, nadal nie zmienia oryginalnej klasy Integer, a ktokolwiek używa, DerivedInteger
robi to na własne ryzyko, więc nadal nie rozumiem, dlaczego jest to problem.
możesz chcieć tworzyć niezmienne obiekty ( http://en.wikipedia.org/wiki/Immutable_object ), możesz chcieć utworzyć singleton ( http://en.wikipedia.org/wiki/Singleton_pattern ) lub możesz chcieć uniemożliwić komuś zastąpienie metody ze względu na wydajność, bezpieczeństwo lub ochronę.
Dziedziczenie jest jak piła łańcuchowa - bardzo potężna, ale okropna w niepowołanych rękach. Albo zaprojektujesz klasę, z której będzie dziedziczona (co może ograniczyć elastyczność i zająć dużo więcej czasu), albo powinieneś tego zabronić.
Zobacz Effective Java 2. wydanie, pozycje 16 i 17 lub mój wpis na blogu „Podatek spadkowy” .
Hmmm ... przychodzą mi do głowy dwie rzeczy:
Być może masz klasę zajmującą się pewnymi kwestiami bezpieczeństwa. Podklasyfikując go i dostarczając do systemu jego podklasę, atakujący może obejść ograniczenia bezpieczeństwa. Np. Twoja aplikacja może obsługiwać wtyczki i jeśli wtyczka może po prostu podklasować klasy związane z bezpieczeństwem, może użyć tej sztuczki, aby w jakiś sposób przemycić jej podklasę na miejsce. Jednak jest to raczej coś, z czym Sun musi sobie radzić w odniesieniu do apletów i tym podobnych, może nie jest to tak realistyczny przypadek.
O wiele bardziej realistyczne jest unikanie zmienności obiektu. Np. Ponieważ ciągi znaków są niezmienne, Twój kod może bezpiecznie przechowywać odniesienia do niego
String blah = someOtherString;
zamiast najpierw kopiować ciąg. Jeśli jednak możesz podklasę String, możesz dodać do niego metody, które pozwalają na modyfikację wartości ciągu, teraz żaden kod nie może już polegać na tym, że ciąg pozostanie taki sam, jeśli po prostu skopiuje ciąg jak powyżej, zamiast tego musi powielić strunowy.
Jeśli oznaczysz klasy i metody jako ostateczne, możesz zauważyć niewielki wzrost wydajności, ponieważ środowisko wykonawcze nie musi wyszukiwać właściwej metody klasy do wywołania dla danego obiektu. Metody nieokończone są oznaczane jako wirtualne, aby można je było odpowiednio rozszerzyć w razie potrzeby, metody końcowe można bezpośrednio łączyć lub kompilować w klasie.
Aby powstrzymać ludzi przed robieniem rzeczy, które mogą zmylić siebie i innych. Wyobraź sobie bibliotekę fizyki, w której masz zdefiniowane stałe lub obliczenia. Bez użycia końcowego słowa kluczowego ktoś mógłby przyjść i przedefiniować podstawowe obliczenia lub stałe, które NIGDY nie powinny się zmieniać.
Chcesz, aby metoda była ostateczna, aby zastępowanie klas nie zmieniało jej zachowania. Jeśli chcesz mieć możliwość zmiany zachowania, upublicznij metodę. Kiedy zastępujesz metodę publiczną, można ją zmienić.