PO Dziedziczenie i interfejsy: Różnice pomiędzy wersjami
Nie podano opisu zmian |
|||
Linia 53: | Linia 53: | ||
Dziedziczenie jest bardzo silnym narzędziem pozwalającym lepiej strukturalizować programy. Jest charakterystyczne dla podejścia obiektowego. Tak jak każde silne narzędzie ma zarówno zalety jak i wady. Poniżej krótko je charakteryzujemy. | Dziedziczenie jest bardzo silnym narzędziem pozwalającym lepiej strukturalizować programy. Jest charakterystyczne dla podejścia obiektowego. Tak jak każde silne narzędzie ma zarówno zalety jak i wady. Poniżej krótko je charakteryzujemy. | ||
Zalety: | Zalety: | ||
Linia 60: | Linia 61: | ||
błędy w częściej używanych fragmentach programów) i pozwala szybciej tworzyć nowe systemy (budować je z gotowych klocków). | błędy w częściej używanych fragmentach programów) i pozwala szybciej tworzyć nowe systemy (budować je z gotowych klocków). | ||
* Zgodność interfejsów (osiągana przez tworzenie hierarchii klas dziedziczących po wspólnej nadklasie lub interfejsie). | * Zgodność interfejsów (osiągana przez tworzenie hierarchii klas dziedziczących po wspólnej nadklasie lub interfejsie). | ||
Problemy: | Problemy: |
Wersja z 09:17, 4 paź 2006
Wprowadzenie
Wiemy już, że przy pomocy klas możemy modelować pojęcia z dziedziny obliczeń naszego programu. Bardzo często podczas takiego modelowania zauważamy, że pewne pojęcia są mocno ze sobą związane. Podejście obiektowe dostarcza mechanizmu umożliwiającego bardzo łatwe wyrażanie związków pojęć polegających na tym, że jedno pojęcie jest uszczegółowieniem (lub uogólnieniem) drugiego. Ten mechanizm nazywa się dziedziczeniem. Stosujemy go zawsze tam, gdzie chcemy wskazać, że dwa pojęcia są do siebie podobne.
Zwróćmy uwagę, że zastosowanie dziedziczenia jest związane ze znaczeniem klas powiązanych tych związkiem, a nie z ich implementacją. To że dwie klasy mają podobną implementację w żadnym stopniu nie upoważnia do wyrażenia tej zależności za pomocą dziedziczenia. Implementacja ma być ukryta w klasach (więc jej podobieństwa między klasami nie powinny być eksponowane za pomocą dziedziczenia). Implementacja poszczególnych pojęć może się zmieniać, co nie powinno mieć wpływu na relację dziedziczenia pomiędzy klasami w programie.
Dziedzieczenie jest jedną z fundamentalnych własności podejścia obiektowego. Pozwala kojarzyć klasy obiektów w hierarchie klas. Te hierarchie, w zależności od użytego języka programowania, mogą przyjmować postać drzew, lasów drzew, bądź skierowanych grafów acyklicznych. W Javie hierarchia dziedziczenia dla klas ma postać drzewa. Jej korzeniem jest klasa Object. Jak wkrótce się przekonamy jest niesłychanie wygodnie mieć taką jedną wspólną nadklasę dla wszystkich klas występujących w programie.
Realizacja dziedziczenia polega na tym, że klasa dziedzicząca dziedziczy po swojej nadklasie wszystkie jej atrybuty i metody (i nie ma znaczenia, czy te atrybuty i metody były zadeklarowane bezpośrednio w tej nadklasie, czy ona też odziedziczyła je po swojej z kolei nadklasie).
W różnych językach programowania można natknąć się na różną terminologię, klasę po której inna klasa dziedziczy nazywa się nadklasą lub klasą bazową, zaś klasę dziedziczącą podklasą lub klasą pochodną.
Dziedziczenie odzwierciedla relację is-a (jest czymś). Oznacza to, że każdy obiekt podklasy jest także obiektem nadklasy. Na przykład hierarchia klas zbudowana z nadklasy Owoc i dwu podklas Jabłko i Gruszka jest prawidłowo zbudowana, bo każde jabłko i każda gruszka jest owocem. Niestety często relacja is-a jest mylona z relacją has-a (ma coś) dotyczącą składania obiektów.
Zasada podstawialności: ponieważ obiekty podklas są też obiektami nadklas, to oczywiście zawsze można podstawiać obiekty podklas w miejsce obiektów nadklas. Na przykład metoda, która ma parametr typu Owoc prawidłowo zadziała z argumentem będącym jabłkiem albo gruszką.
Oto typowe zastosowania dziedziczenia:
- opisanie specjalizacji jakiegoś pojęcia (np. klasa Prostokąt dziedzicząca po klasie Czworokąt),
- specyfikowanie pożądanego interfejsu (klasy abstrakcyjne, interfejsy),
Nie należy natomiast stosować dziedziczenia do wyrażania ograniczania (np. klasa Stos dziedzicząca po uniwersalnej implementacji sekwencji wartości z operacjami dostępu do dowolnego elementu sekwencji - tu należy zastosować składanie).
Dziedziczenie pozwala pogodzić ze sobą dwie sprzeczne tendencje w
tworzeniu oprogramowania:
- chcemy żeby stworzone programy były zamknięte: gdy program już kompiluje się, działa i przeszedł przez wszystkie testy, chcielibyśmy go zapieczętować tak, by nikt już go nie modyfikował, bo modyfikacje często sprawiają, że programy przestają działać.
- chcemy żeby stworzone programy były otwarte: jeśli napisaliśmy dobry program, taki który jest używany przez użytkowników, to na pewno trzeba go będzie modyfikować. Nie dlatego że jest zły i zawiera błędy, tylko właśnie dlatego, że jest dobry i użytkownicy chcą go używać w ciągle zmieniającym się świecie. Skoro zmienia się sprzęt, na którym jest uruchamiany program, system operacyjny, upodobania użytkownika, przepisy prawne, to oczywiście i sam program musi być zmieniany. Losem dobrych programów jest częste ich modyfikowanie.
Dziedziczenie pozwala wtedy gdy potrzebujemy zmian nie modyfikować klas już istniejących, lecz na ich podstawie (przez dziedziczenie właśnie) stworzyć nowe dostosowane do zmienionych wymagań. Czyli dotychczasowy kod pozostaje bez zmian, a jednocześnie możemy rozwijać nasz program.
Realizacja w Javie
W Javie można dziedziczyć zarówno po klasach jak i po interfejsach (będziemy jeszcze mówić o nich w dalszej części wykładu). Składniowo oba te sposoby dziedziczenia są wyrażane inaczej (za pomocą innych słów kluczowych) ponadto dziedziczenie po klasach jest ograniczone do tylko jednej bezpośredniej nadklasy (nie ma w Javie wielodziedziczenia takiego jak w C++). Można natomiast dziedziczyć (bezpośrednio) po dowolnej liczbie interfejsów.
Polimorfizm
Klasy abstrakcyjne i interfejsy
Podsumowanie
Dziedziczenie jest bardzo silnym narzędziem pozwalającym lepiej strukturalizować programy. Jest charakterystyczne dla podejścia obiektowego. Tak jak każde silne narzędzie ma zarówno zalety jak i wady. Poniżej krótko je charakteryzujemy.
Zalety:
- Możliwość jawnego zapisywania w programie związków ogólniania/uszczegóławiania pomiędzy opisywanymi pojęciami.
- Możliwość ponownego wykorzystywania (nie trzeba od nowa pisać
odziedziczonych metod i deklaracji odziedziczonych zmiennych). Ponowne wykorzystywanie zwiększa niezawodność (szybciej wykrywa się błędy w częściej używanych fragmentach programów) i pozwala szybciej tworzyć nowe systemy (budować je z gotowych klocków).
- Zgodność interfejsów (osiągana przez tworzenie hierarchii klas dziedziczących po wspólnej nadklasie lub interfejsie).
Problemy:
- Problem jo-jo (nadużywanie dziedziczenia może uczynić czytanie programu
bardzo żmudnym procesem).
- Modyfikacje kodu w nadklasach mają wpływ na podklasy i na odwrót (wirtualność metod).
Należy na koniec podkreślić, że dziedziczenie nie jest jedyną techniką wyrażania związków pomiędzy klasami. Innym ważnym mechanizmem wyrażania związków jest składanie. Oba te mechanizmy wzajemnie się uzupełniają i nigdy nie należy dążyć na siłę do zastąpienia jednego z nich drugim.
Przypisy
<references/>