JavaRush /Blog Java /Random-PL /Dziedziczenie jako zjawisko
articles
Poziom 15

Dziedziczenie jako zjawisko

Opublikowano w grupie Random-PL
Prawdę mówiąc, początkowo nie planowałem tego artykułu. Zagadnienia, które chcę tu poruszyć, uznałem za trywialne, nawet nie warte wspominania. Jednakże w trakcie pisania artykułów dla tej witryny na jednym z forów wywołałem dyskusję na temat dziedziczenia wielokrotnego. W rezultacie okazało się, że większość programistów ma bardzo niejasne pojęcie o dziedziczeniu. I w związku z tym popełnia wiele błędów. Ponieważ dziedziczenie jest jedną z najważniejszych cech OOP (jeśli nie najważniejszą!), postanowiłem poświęcić temu zjawisku osobny artykuł. * * * Najpierw chcę rozróżnić dwa pojęcia – obiekt i klasę. Pojęcia te są stale mylone. Tymczasem odgrywają kluczową rolę w OOP. I moim zdaniem trzeba znać różnice między nimi. Zatem przedmiot. W zasadzie to wszystko. Oto sześcian. Drewniany, niebieski. Długość krawędzi wynosi 5 cm.To jest przedmiot. I jest piramida. Plastik, czerwony. Żebro 10 cm. To jest także przedmiot. Co oni mają ze sobą wspólnego? Różne rozmiary. Inny kształt. Inny materiał. Mają jednak coś wspólnego. Po pierwsze, zarówno sześcian, jak i piramida są wielościanami foremnymi. Te. suma liczby wierzchołków i liczby ścian jest o 2 większa niż liczba krawędzi. Dalej. Obydwa kształty mają twarze, krawędzie i wierzchołki. Obie liczby mają taką cechę, jak rozmiar żeber. Obydwa kształty można obracać. Można narysować obie figury. Dwie ostatnie właściwości są już zachowaniem. I tak dalej. Praktyka programowania pokazuje, że znacznie łatwiej operować obiektami jednorodnymi niż heterogenicznymi. A ponieważ między tymi postaciami wciąż jest coś wspólnego, istnieje potrzeba, aby w jakiś sposób podkreślić tę podobieństwo. Tutaj pojawia się koncepcja klasy. Zatem definicja.
Klasa jest deskryptorem wspólnych właściwości grupy obiektów. Właściwościami tymi mogą być zarówno cechy obiektów (rozmiar, waga, kolor itp.), jak i zachowania, role itp.
Komentarz. Słowo „wszystko” (deskryptor wszystkich właściwości) nie zostało wypowiedziane. Oznacza to, że dowolny obiekt może należeć do kilku różnych klas. Dziedziczenie jako zjawisko - 1 Weźmy ten sam przykład na podstawie kształtów geometrycznych. Najbardziej ogólnym opisem jest wielościan foremny . Niezależnie od wielkości krawędzi, liczby ścian i wierzchołków. Wiemy tylko, że ta figura ma wierzchołki, krawędzie i ściany oraz że długości krawędzi są równe. Dalej. Możemy uczynić opis bardziej szczegółowym. Powiedzmy, że chcemy narysować ten wielościan . Wprowadźmy takie pojęcie jak narysowany wielościan foremny . Czego potrzebujemy do rysowania? Opis ogólnej metody rysowania, która nie zależy od konkretnych współrzędnych wierzchołków. Być może kolor obiektu. Przedstawmy teraz klasy Cube i Tetrahedron . Obiekty należące do tych klas są z pewnością wielościanami foremnymi. Jedyna różnica polega na tym, że dla każdej z nowych klas liczba wierzchołków, krawędzi i ścian jest już ściśle ustalona. Ponadto, znając rodzaj konkretnej figury, możemy podać opis metody rysowania. Oznacza to, że dowolny obiekt klasy Cube lub Tetrahedron jest także obiektem narysowanej klasy foremnego wielościanu . Istnieje hierarchia klas. W tej hierarchii schodzimy od opisu najbardziej ogólnego do opisu najbardziej szczegółowego. Należy zauważyć, że obiekt dowolnej klasy pasuje również do opisu dowolnej bardziej ogólnej klasy w hierarchii. Ten związek klasowy nazywa się dziedziczeniem . Każda klasa potomna dziedziczy wszystkie właściwości rodzica, bardziej ogólne, i (być może) dodaje trochę własnych do tych właściwości. Lub zastępuje niektóre właściwości klasy nadrzędnej. Tutaj chcę zacytować klasyczną książkę Gradiego Buchy o projektowaniu obiektowym:
Dziedziczenie definiuje zatem „jest” hierarchią pomiędzy klasami, w której podklasa dziedziczy z jednej lub większej liczby nadklas. Jest to w istocie papierek lakmusowy dziedziczenia. Biorąc pod uwagę klasy A i B, jeśli A „nie jest” rodzajem B, to A nie powinno być podklasą B.
W tłumaczeniu brzmi to tak:
Dziedziczenie definiuje zatem hierarchię „jest” pomiędzy klasami, w której podklasa dziedziczy z jednej lub większej liczby nadklas. W rzeczywistości jest to test definiujący (dosłownie papierek lakmusowy, przyp. red.) dotyczący dziedziczenia. Jeśli mamy klasy A i B i jeśli klasa A „nie jest” wariantem klasy B, to A nie może być podklasą B.
Ci, którzy doczytali aż do tego miejsca, prawdopodobnie ze zdumieniem kręcą palcem w stronę skroni. Pierwsza myśl jest taka, że ​​to banalne! To prawda. Ale gdybyś wiedział, ile widziałem szalonych hierarchii dziedziczenia! W tej dyskusji, o której wspomniałem na samym początku, jeden z uczestników całkiem poważnie odziedziczył czołg od... karabinu maszynowego!!! Z prostego powodu: czołg MA karabin maszynowy. I to jest najczęstszy błąd. Dziedziczenie jest mylone z agregacją – włączeniem jednego obiektu do drugiego. Czołg nie jest karabinem maszynowym, zawiera jeden. I z powodu tego błędu najczęściej pojawia się chęć użycia wielokrotnego dziedziczenia. Przejdźmy teraz bezpośrednio do Javy. Co istnieje w zakresie dziedziczenia? W języku istnieją dwa typy klas – te, które mogą zawierać implementację, i te, które nie mogą tego zrobić. Te ostatnie nazywane są interfejsami, chociaż w istocie są to klasy całkowicie abstrakcyjne. Dziedziczenie jako zjawisko - 2 Zatem język pozwala dziedziczyć klasę z innej klasy, która potencjalnie zawiera implementację. ALE TYLKO Z JEDNEGO! Pozwólcie, że wyjaśnię, dlaczego tak zrobiono. Rzecz w tym, że każda implementacja może zająć się tylko swoją częścią – tymi zmiennymi i metodami, o których wie. I nawet jeśli odziedziczymy klasę C z A i B , to metoda procesA , odziedziczona z klasy A, może działać tylko ze zmienną wewnętrzną a , ponieważ nie wie nic o b , tak samo jak nie wie nic o c i metodzie procesC . Podobnie metoda ProcessB może działać tylko ze zmienną b. Oznacza to, że odziedziczone części są izolowane. Klasa C z pewnością może z nimi współpracować, ale równie dobrze może współpracować z tymi częściami, jeśli zostaną po prostu uwzględnione jako część, a nie dziedziczone. Jest tu jednak jeszcze jedna uciążliwość, jaką jest nakładanie się nazw. Jeśli metody procesA i procesB miałyby takie same nazwy, powiedzmy proces, jaki skutek miałoby wywołanie metody procesu klasy C ? Która z tych dwóch metod zostanie wywołana? Oczywiście C++ ma kontrolę w tej sytuacji, ale nie dodaje to harmonii językowi. Zatem dziedziczenie implementacji nie zapewnia korzyści, ale ma wady. Z tego powodu porzucono to dziedziczenie implementacji w Javie. Twórcom pozostawiono jednak taką opcję dziedziczenia wielokrotnego, jak dziedziczenie z interfejsu. W języku Java jest to implementacja interfejsu. Jaki jest interfejs? Zestaw metod. (Obecnie nie rozważamy definicji stałych w interfejsach; więcej na ten temat tutaj .) Co to jest metoda? Metoda, w swojej istocie, określa zachowanie obiektu. To nie przypadek, że nazwa niemal każdej metody zawiera akcję - getXXX , remisXXX , countXXX itp. A ponieważ interfejs jest zbiorem metod, w rzeczywistości jest on wyznacznikiem zachowania . Inną możliwością wykorzystania interfejsu jest zdefiniowanie roli obiektu. Obserwator, Słuchacz itp. W tym przypadku metoda jest właściwie ucieleśnieniem reakcji na jakieś wydarzenie zewnętrzne. Czyli znowu zachowanie. Obiekt może oczywiście wykazywać kilka różnych zachowań. Jeśli trzeba to wyrenderować, jest to renderowane. Jeśli trzeba go ocalić, jest ocalony. Cóż, itp. W związku z tym możliwość dziedziczenia po klasach definiujących zachowanie jest bardzo, bardzo przydatna. Podobnie obiekt może pełnić kilka różnych ról. Jednak wdrożeniezachowanie zależy całkowicie od sumienia klasy dziecka. Dziedziczenie z interfejsu (jego implementacja) mówi, że obiekt tej klasy powinien móc zrobić to i tamto. JAK to robi, jest określane niezależnie przez każdą klasę implementującą interfejs. Wróćmy do błędów w dziedziczeniu. Z mojego doświadczenia w tworzeniu różnych systemów wynika, że ​​mając dziedziczenie z interfejsów, można zaimplementować dowolny system bez konieczności korzystania z wielokrotnego dziedziczenia implementacji. Dlatego też, gdy spotykam się z narzekaniami na brak wielokrotnego dziedziczenia w takiej formie, w jakiej występuje ono w C++, jest to dla mnie pewny znak błędnego projektu. Najczęściej popełnianym błędem jest ten, o którym już wspomniałem – dziedziczenie jest mylone z agregacją. Czasami dzieje się tak z powodu błędnych założeń. Te. Weźmy na przykład prędkościomierz. Argumentuje się, że prędkość można zmierzyć jedynie poprzez pomiar drogi i czasu, po czym prędkościomierz zostaje pomyślnie odziedziczony od linijki i zegara, stając się w ten sposób linijką i zegarem, zgodnie z definicją dziedzictwo. (Na moje prośby o zmierzenie czasu prędkościomierzem zwykle odpowiadano żartami. Albo nie odpowiadano w ogóle.) Jaki tu jest błąd? W założeniu. Faktem jest, że prędkościomierz nie mierzy czasu. Swoją drogą, odległości też. Licznik przebiegu, który znajduje się w każdym prędkościomierzu, jest klasycznym przykładem drugiego urządzenia w tej samej obudowie, czyli tzw. zbiór. Pomiar prędkości nie jest konieczny. Można go całkowicie usunąć - nie będzie to miało żadnego wpływu na pomiar prędkości. Czasami takie błędy popełniane są celowo. To jest dużo gorsze. „Tak, wiem, że to niewłaściwe, ale tak jest dla mnie wygodniej”. Do czego to może prowadzić? Ale oto co: czołg odziedziczymy z armaty i karabinu maszynowego. Tak jest wygodniej. W rezultacie czołg staje się armatą i karabinem maszynowym. Następnie wyposażymy samolot w dwa karabiny maszynowe i działo. Co otrzymujemy? Samolot z podwieszaną bronią w postaci trzech czołgów! Bo NAPRAWDĘ jest osoba, która nie rozumiejąc tego, używa czołgu jako karabinu maszynowego. Wyłącznie według hierarchii dziedziczenia. I będzie miał całkowitą rację, bo ten, kto zaprojektował taką hierarchię, popełnił błąd.
Generalnie nie bardzo rozumiem podejście „tak jest mi wygodniej”. Wygodnie jest pisać jak słuchacz, a ci, którzy mówią o podstawach gramatyki, są kazly. Przesadzam oczywiście, ale główna idea pozostaje – oprócz chwilowej wygody istnieje coś takiego jak umiejętność czytania i pisania. Pojęcie to definiowane jest na podstawie doświadczeń bardzo dużej liczby osób. Tak naprawdę to właśnie w języku angielskim nazywa się „najlepszą praktyką” – najlepszym rozwiązaniem. Często rozwiązania, które wydają się prostsze, niosą ze sobą wiele problemów w przyszłości.
Przykład ten jest oczywiście mocno przesadzony i przez to absurdalny. Istnieją jednak mniej oczywiste przypadki, które mimo wszystko prowadzą do katastrofalnych konsekwencji. Dziedzicząc z obiektu, zamiast go agregować, programista daje każdemu możliwość bezpośredniego korzystania z funkcjonalności obiektu nadrzędnego. Ze wszystkim, co to oznacza. Wyobraź sobie, że masz klasę współpracującą z bazą danych, DBManager . Tworzysz kolejną klasę, która będzie pracować z Twoimi danymi za pomocą DBManager - DataManager . Ta klasa będzie wykonywać kontrolę danych, transformacje, dodatkowe akcje itp. Ogólnie rzecz biorąc, warstwa pomiędzy warstwą biznesową a warstwą bazową. Jeśli odziedziczysz DataManager z DBManager, każdy, kto go użyje, będzie miał bezpośredni dostęp do bazy danych. A zatem będzie mógł dokonać dowolnych działań z pominięciem kontroli, przekształceń itp. No dobrze, załóżmy, że nikt nie chce celowo wyrządzić krzywdy i działania bezpośrednie będą kompetentne. Ale! Załóżmy, że baza się zmieniła. Mam na myśli, że zmieniły się pewne zasady kontroli czy transformacji. DataManager został zmieniony. Jednak kod, który wcześniej działał bezpośrednio z bazą danych, będzie nadal działał. Najprawdopodobniej go nie zapamiętają. Rezultatem będzie błąd takiej klasy, że ci, którzy go szukają, zmienią kolor na szary. Nikomu nie przyszłoby do głowy, że pracuje z bazą danych z pominięciem DataManagera. Swoją drogą przykład z życia wzięty. Znalezienie błędu zajęło BARDZO dużo czasu. Na koniec powtórzę to jeszcze raz. Dziedziczenia należy używać TYLKO wtedy, gdy istnieje relacja „jest”. Ponieważ na tym właśnie polega istota dziedziczenia – możliwość wykorzystania obiektów klasy potomnej jako obiektów klasy bazowej. Jeśli nie ma relacji „jest” pomiędzy klasami, NIE POWINNO być dziedziczenia!!! Nigdy i pod żadnym pozorem. A tym bardziej – tylko dlatego, że jest to tak wygodne. Link do oryginalnego źródła: http://www.skipy.ru/philosophy/inheritance.html
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION