Dziedziczenie
Dziedziczenie jest relacją między klasami, zachodzącą wtedy gdy jakaś klasa (klasa pochodna) korzysta w swej definicji z definicji innej klasy (klasy bazowej, podstawowej). Można wyobrażać sobie, że obiekty klasy pochodnej zawierają w sobie elementy klasy podstawowej.
Podstawowe rozróżnienie rodzajów dziedziczenia:
- interfejsu – dziedziczonymi elementami są wyłącznie sygnatury funkcji składowych klasy
- interfejsu i implementacji – dziedziczone są dane składowe (stan), sygnatury i implementacja metod
- implementacji (nie zalecane) – dziedziczone jest wszystko jak wyżej, ale klientom na zewnątrz nie udostępnia się ani danych składowych (dobrze), ani funkcji składowych z interfejsu (kontrowersyjne)
W niektórych językach rozróżnienie dziedziczenia interfejsu i implementacji jest jawnie zapisywane w kodzie.
Kiedy, mając pewną klasę warto próbować dokonać specjalizacji i stworzyć podklasy?
- gdy pojawiają się różniące się metodami rodzaje obiektów i wystarczająco duża liczba metod jest inna dla wyróżnionych rodzajów (podklas)
- gdy specjalizacja umożliwi ponowne użycie klasy bazowej w innych kontekstach (klasa bazowa uwolniona od pewnych konkretów, przejdzie na wyższy poziom abstrakcji)
Klasa pochodna powinna wnosić co najmniej jedną z poniższych zmian:
- nowe dane lub funkcje składowe
- redefinicja istniejących funkcji składowych (redefinicja może oznaczać dodanie implementacji lub zmianę widzialności metody)
- zmiana niezmienników klasy
Kiedy stosować dziedziczenie?
Dziedziczenie jest podobne do składania obiektów, należy je stosować wtedy gdy:
- chcemy używać obiektów klas pochodnych, dla których typ określać będzie klasa podstawowa (np. będziemy posługiwać się wskaźnikiem do obiektu typu klasy podstawowej)
- chcemy wprowadzić nową klasę, która częściowo modyfikuje zachowanie lub własności innej klasy, przy czym większość atrybutów i funkcji starej klasy pozostaje bez zmian (zwłaszcza jeśli chcemy pozostawić możliwość stosowania starej klasy)
- nie spodziewamy się, aby obiekt klasy pochodnej miał kiedykolwiek w trakcie swojego istnienia dokonać zmiany atrybutów i zachowania związanych z klasą podstawową (w przeciwnym przypadku – gdy spodziewamy się wymiany składowych związanych z klasą podstawową na inne składowe – należy użyć bardziej elastycznej kompozycji obiektów)
Ważnym przypadkiem dziedziczenia jest sytuacja kiedy klasa podstawowa definiuje pewną istotną cechę lub zachowanie, natomiast klasa pochodna ma reprezentować zbiór obiektów wyposażonych w daną cechę lub zachowanie (przykłady z Javy: Serializable, Observable itp.). Ten typ dziedziczenia jest typowy dla dziedziczenia interfejsu: klasa podstawowa (abstrakcyjna) definiuje wymagane zachowanie, a klasa pochodna dostarcza odpowiednią dla obiektów tej klasy implementację zachowania.