W niedawno opublikowanym artykule pisałem o klasach i obiektach w Rubym. Jednak nie wyjaśniłem tam mechanizmu tzw. dziedziczenia, które jest jednym z fundamentalnych koncepcji programowania obiektowego.
Dziedziczenie
Co to jest i do czego służy dziedziczenie (ang. inheritance)? Przypomnijmy sobie do czego służą klasy. Są to w skrócie nowe typy danych, które sami tworzymy. Możemy w nich określać, z jakich danych będą się składały obiekty klas, a także jakie operacje na tych danych będzie można wykonywać.
Programowanie bez dziedziczenia
Wyobraźmy sobie, że mamy już stworzony nowy typ danych określony klasą Osoba
:
Zauważymy, że jest tam sześć akcesorów (trzy służące do zapisu, a trzy do odczytu), czyli metod, których funkcja polega na ustawianiu bądź odczytywaniu zawartości zmiennych instancyjnych o nazwach takich jak one. Naprawdę klasa ta wygląda więc tak:
Wyobraźmy sobie teraz, że ktoś nas odwiedza i prosi o rozszerzenie programu o możliwość operowania na informacjach o studentach. Zmiana ma polegać na dodaniu obsługi następujących danych: imię, wiek, płeć i kierunek studiów. Możemy wtedy stworzyć kolejną klasę, która wyglądała będzie na przykład tak:
Takie podejście ma jednak wadę: powtarzamy się. Mamy już w programie klasę
Osoba
, a stwarzamy bardzo podobną klasę Student
. Dlaczego ta druga klasa
jest podobna? Ze względu na relację zawierania cech, ponieważ student jest
osobą, a reprezentujący go typ danych pochodzi od typu reprezentującego
osobę.
Klasa pochodna
Aby unikać powtarzania się, większość języków obiektowych obsługuje mechanizm zwany dziedziczeniem. Warunkiem do skorzystania z niego jest stworzenie tzw. klasy pochodnej (ang. derived class) względem już istniejącej, zwanej w tej relacji klasą bazową (ang. base class).
Mechanizm działania
Dziedziczenie polega na tym, że klasa pochodna może odwoływać się do metod zdefiniowanych w klasie bazowej. Jest to po prostu rozszerzanie już istniejącego typu danych o nowe elementy. Wywołania metod są przezroczyste i nie wymagana jest specyficzna składnia.
W praktyce interpreter języka pamięta w każdej klasie odpowiedni wskaźnik kierujący do klasy bazowej (zwanej też superklasą). Jeżeli wywołamy metodę, która nie została zdefiniowana w klasie bieżącej, to interpreter skorzysta z tego odwołania i zajrzy do klasy nadrzędnej, aby sprawdzić, czy aby tam nie ma tej metody. Czynność ta jest powtarzana, aż do momentu, w którym ścieżka dziedziczenia się zakończy.
W języku Ruby nie istnieje dziedziczenie wielobazowe, a więc klasy mogą mieć
tylko jednego bezpośredniego „przodka”. We wcześniejszym przykładzie klasa
Student
jest klasą pochodną, a klasa Osoba
jest jej klasą bazową.
Sprawdzanie ścieżki dziedziczenia
Istnieją sposoby na to, aby metoda klasy „dowiedziała się” po czym jej obiekt dziedziczy. Co więcej, instancje klas (obiekty), są już w nią wyposażone:
Jak to? Przecież nie tworzyliśmy metody o nazwie class
ani klasowej metody
superclass
, więc skąd się one wzięły? Żeby ułatwić życie programiście w języku
Ruby wszystkie tworzone klasy dziedziczą po klasie Object
. Wyjątkiem są
oczywiście te klasy, które dziedziczą po innych klasach, jak w naszej
przykładowej klasie Student
. Jednak klasa Osoba
, będąca przodkiem klasy
Student
, również jest potomkiem klasy Object
, a więc ścieżka dziedziczenia
zaprowadzi nas w końcu do zdefiniowanej metody class
.
Właśnie w klasie Object
znajdziemy metodę class
, która zwraca obiekt klasy,
dla której została ona wywołana. Brzmi to dziwacznie, ale przecież w Rubym nawet
klasy są specyficznymi obiektami.
Z kolei metoda superclass
zdefiniowana jest w klasie Class
, która z kolei
jest klasą wszystkich obiektów będących klasami. Zauważmy, że w podobny sposób
dostępna jest między innymi metoda klasowa new
, używana do tworzenia nowych
obiektów. Aby przekonać się, że klasy są też obiektami (instancjami klasy Class
)
możemy użyć konstrukcji:
Nadpisywanie metod
W klasie Student
z wcześniejszego przykładu znajdziemy metodę pokaz
, której
nazwa jest taka sama, jak nazwa metody z klasy bazowej Osoba
. Interpreter
wywoła właśnie ją i nie będzie już poszukiwał jej w superklasie. Nazywamy to
nadpisywaniem metody (ang. method overriding).
Jednak nasza metoda ma uzupełniać, a nie całkowicie unieważniać działanie
oryginalnej i dlatego korzystamy w niej ze słowa kluczowego super
.
Umieszczenie go sprawia, że zostanie wywołana nadpisywana sama metoda
zdefiniowana w superklasie.