Programowanie obiektowe – dziedziczenie

ByKamil

Programowanie obiektowe – dziedziczenie

W poprzedniej części szkolenia podjęliśmy temat wprowadzania i wyświetlania danych wejściowych.

Nadszedł czas na powrót do programowania obiektowego, a dokładniej do kluczowej części czyli dziedziczenia. Na początek przypomnijmy, że każda klasa w Javie dziedziczy po klasie bazowej Object. Możliwe jest dziedziczenie tylko po jednej klasie własnej – nie występuje tu mechanizm znany np. z języka C++ czyli dziedziczenia wielokrotnego.

Relacje między klasami

Aby dobrze zrozumieć relacje zachodzące między klasą bazową a klasą dziedziczną należy przypomnieć pojęcia znane z algebry takie jak podzbiór i nadzbiór.

Relacja inkluzji

A ⊂ B ⇔ ∀ x ( x ∈ A ⇒ x ∈ B) – co należy rozumieć, że każdy element zbioru A należy do zbioru B (ale zbiory nie muszą być równe – B może posiadać więcej elementów).

Niech A={1,2,3} i B={1,2,3,4}, wtedy A ⊂ B
Niech A={1,2,3,5} i B={1,2,3,4}, wtedy A ⊄ B, gdyż zawiera element, który nie należy do B.

Inkluzja a dziedziczenie w Javie

Odniesienie relacji inkluzji zbiorów do klas jest następujące – nadklasa jest podzbiorem podklasy. Intuicyjnie wydawało by się, że to klasa nadrzędna zawiera, więcej funkcji i daje więcej możliwości. W rzeczywistości podklasa posiada wszystkie elementy nadklasy oraz dodatkowe cechy wyłącznie do dyspozycji podklasy.

Przy dziedziczeniu należy pamiętać, że klasa dziedziczy wszystkie pola.

Dziedziczenie

Przejdziemy teraz do przykładu już spoza teorii matematyki i przyjrzymy się przykładowi z życia codziennego. Wyobraźmy sobie, że próbujemy zaimplementować produkty produkty spożywcze, które będą częścią aplikacji Dieta.

Założenia koncepcyjne

Zacznijmy od opisania całego problemu. Nasza aplikacja powinna pomagać kontrolować ilość spożywanych kalorii przez użytkownika. Dlatego ogólną cechą, która będzie polem każdego obiektu będzie kaloryczność oraz wartości odżywcze (białka, węglowodany i tłuszcze).

Żywność możemy podzielić na napoje oraz jedzenie. Robimy tak dlatego, że napoje będziemy odmierzać w litrach, natomiast jedzenie w gramach w związku z czym potrzebujemy zupełnie innej jednostki dla żywności odpowiedniego typu. Jednakże kaloryczność oraz makroelementy są wspólne dla obu bardziej szczegółowych typów żywności.

Podział na poziomy abstrakcji

Przechodzenie na kolejne poziomy abstrakcji można kontynuować bardzo dłogu. Jedzenie możemy dalej podzielić na wegetariańskie i mięsne, napoje na alkoholowe i zwykłe, a te z kolei na gazowane i niegazowane. My jednak zatrzymamy się na bardzo ogólnym podziale czyli jedzenie i napoje gdyż naszym celem jest pokazanie mechanizmów dziedziczenia klas.

Poniżej pokażemy uproszczony model klas.

dziedziczenie Java
Hierarchia dziedziczenia

Załóżmy teraz, że chcemy dodatkowo posłodzić nasz napój i dodać cukru. Czy metoda odziedziczona pokaże nam prawidłową kaloryczność? Niestety musimy napisać dodatkową metodę, która obliczy nam kaloryczność napoju po dodaniu do niego cukru. Do tego napiszemy klasę, która będzie dziedziczyć po napojach

Dlaczego nie dopisać tej metody po prostu do klasy napój? Ponieważ w naszym modelu jest ona już osobnego typu. Przykładem niech będzie sok z owoców – jeśli dodamy do niego cukru lub w jakikolwiek sposób zmienimy jego skład dodając wodę przestaje być sokiem – zostaje np. nektarem. My po prostu zrobimy dodatkową klasę, która będzie posiadała wszystkie cechy Napoju oraz da nam możliwość zmodyfikowania go dodając cukier. Klasę nazwiemy WlasnyNapoj.

Słowo kluczowe extends

Aby przekazać klasę do dziedziczenia należy użyć słowa kluczowego extends. Składnia wyrażenia wygląda następująco.

Mechanizm dziedziczenia bardzo ogranicza rozmiar kodu. Wszystko, co znajduje się w klasie wyżej automatycznie „przechodzi” do klasy pochodnej. W przykładzie powyżej w klasie Child korzystamy z pola, którego nie definiowaliśmy w niej. To pole zostało odziedziczone z klasy Parent.

Polimorfizm i przesłanianie metod

W przypadku, gdy odziedziczona metoda nie pasuje już w klasie podrzędnej można ją przesłonić (ang. override). Taką czynność nazywamy polimorfizmem. Polega to na napisaniu na nowo metody w klasie pochodnej.

Metoda getDane(), która wyświetla zawartość pola obiektu, ale najpierw wypisuje stały fragment np. „Dane rodzica: „. Dlatego należy ją przesłonić dzięki czemu metoda zadziała prawidłowo.

Metoda przesłaniana musi zwracać ten sam typ, co metoda pierwotna, jednakże może posiadać różne argumenty.

Słowo kluczowe super

Aby dostać się do metody klasy nadrzędnej należy użyć słowa kluczowego super (w przeciwnym wypadku użyjemy metody, która znajduje się wewnątrz klasy, której jest dany obiekt.

Wywoływanie metod i konstruktorów klas nadrzędnych

Słowo kluczowe super możemy użyć także w przypadku konstruktorów. Poniżej pokażemy przykłady jak wywoływać konstruktor i metody klasy nadrzędnej. Poniżej pokazujemy przykład.

Spójrzmy na konstruktor klasy Child. Słowo super powoduje wywołanie konstruktora klasy nadrzędnej w związku z czym w dalszej części pozostaje tylko zainicjować pola specjalne niedostępne w klasie Parent.

Metoda getDane() z klasy podrzędnej zwraca sumę obu wartości przez wywołanie metody getDane() z klasy nadrzędnej i dodanie wartości z klasy Child.

Modyfikatory dostępu

Wcześniej wspominaliśmy, że dziedziczone są różne elementy z klasy nadrzędnej. W tej sekcji omówimy jak modyfikatory dostępu wpływają na widoczność w klasach potomnych. Natomiast bardziej szczegółowo omawiamy je w artykule o modyfikatorach dostępu.

Modyfikator dostępu public

Najmniej restrykcyjny modyfikator dostępu. Wszystkie metody i pola widoczne są w klasie podrzędnej i można z nich bez problemu korzystać.

Przykładem jest wykorzystanie konstruktora Parent oraz metody getDane() przy użyciu słowa kluczowego super.

Modyfikator dostępu protected

W kategorii dziedziczenia efekty użycia protected będą identyczne jak public. Oznacza to, że metody i pola będą widoczne w klasie potomnej.

Modyfikator dostępu private

Najbardziej restrykcyjny modyfikator, który ukrywa wszystko wewnątrz klasy. Jednakże nie należy mylić tego modyfikatora jako blokowanie dziedziczenia. Pole będzie widoczne w podklasie, a dostęp do niego zapewnia metoda getDane().

Inaczej zachowują się metody. Jeśli są prywatne, to nie ma do nich dostępu. Przykładem jest ukrytaMetoda() w klasie Parent. Próba wywołania tej metody w klasie Child lub na jej obiekcie spowoduje poniższy błąd.

W kolejnej części omówimy hierarchię dziedziczenia, rzutowanie oraz klasy abstrakcyjne

About the author

Kamil editor