Dziedziczenie na przykładzie języka Ruby
Opublikował siefca
W niedawno opublikowanym artykule pisałem o klasach i obiektach w Rubym. Jednak nie wyjaśniłem tam pojęcia dziedziczenie, które jest jednym z podstawowych mechanizmów wykorzystywanych w programowaniu obiektowym. Kiedy przydaje się dziedziczenie? Żeby sobie na to pytanie odpowiedzieć, przypomnij sobie do czego służą klasy. Są to w skrócie nowe typy danych, które sam tworzysz. Możesz w nich określić jakie dane będą przechowywały obiekty tworzonych przez Ciebie klas, a także jakie operacje na tych danych będzie można szybko i sprawnie wykonać.
Wyobraź sobie, że masz już stworzony nowy typ danych określony klasą Osoba:
class Osoba attr_accessor :wiek, :imie, :plec def pokaz puts "#{@imie} ma #{@wiek} lat i jest #{@plec ? 'kobieta' : 'mezczyzna' }" end end
Zauważysz, że tworzonych jest sześć akcesorów (trzy do zapisu, a trzy do odczytu), czyli metod, których jedyną funkcją jest ustawiać bądź odczytywać zawartość zmiennych instancyjnych o takich jak one nazwach. Naprawdę klasa ta wygląda więc tak:
class Osoba def wiek; @wiek end def wiek=(x); @wiek = x end def imie; @imie end def imie=(x); @imie = x end def plec; @plec end def plec=(x); @plec = x end def pokaz puts "#{@imie} ma #{@wiek} lat i jest #{@plec ? 'kobieta' : 'mezczyzna' }" end end
Wyobraź sobie teraz, że przychodzi ktoś do Ciebie i prosi o rozszerzenie programu o możliwość operowania w nim na informacjach o studentach. Chodzi o najpotrzebniejsze informacje: imię, wiek, płeć i kierunek studiów. Możesz wtedy stworzyć kolejną klasę, która wyglądała będzie na przykład tak:
class Student attr_accessor :wiek, :imie, :plec, :kierunek def pokaz puts "#{@imie} ma #{@wiek} lat i jest #{@plec ? 'kobieta' : 'mezczyzna' }" puts "studiuje na kierunku: #{@kierunek}" end end
Takie podejście ma jednak niewielką wadę: powtarzasz się. Masz już w programie klasę Osoba, a stwarzasz bardzo podobną klasę Student. Dlaczego ta druga klasa jest podobna? Bo student jest osobą, a reprezentujący go typ danych pochodzi od typu reprezentującego osobę. Dlatego większość języków obiektowych obsługuje coś co nazywa się dziedziczeniem. Polega ono na tym, że tworzysz klasę pochodną względem już istniejącej. W naszym przypadku tak:
class Osoba attr_accessor :wiek, :imie, :plec def pokaz puts "#{@imie} ma #{@wiek} lat i jest #{@plec ? 'kobieta' : 'mezczyzna' }" end end class Student < Osoba attr_accessor :kierunek def pokaz super puts "studiuje na kierunku: #{@kierunek}" end end s = Student.new s.imie = "Jan Kowalski" s.plec = false s.wiek = 25 s.kierunek = "Informatyka" s.pokaz # => Jan Kowalski ma 25 lat i jest mezczyzna # => studiuje na kierunku: Informatyka
Na czym polega dziedziczenie?
Dziedziczenie polega na tym, że nowa klasa może odwoływać się do metod zdefiniowanych w klasie po której ona dziedziczy. Jest to po prostu rozszerzanie funkcjonalności już istniejącego typu danych o jakieś nowe elementy. W praktyce interpreter języka pamięta w każdej klasie odpowiedni wskaźnik kierujący do klasy nadrzędnej (superklasy). Jeśli wywołasz metodę, która nie została zdefiniowana, to interpreter korzysta z tego odnośnika i zagląda do nadklasy, 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 wielokrotne dziedziczenie, a więc klasy mogą mieć tylko jednego bezpośredniego przodka, nazywanego też rodzicem lub klasą bazową. W powyższym przypadku klasa Student jest klasą pochodną, a klasa Osoba jest jej klasą bazową. Istnieją sposoby, aby metoda klasy dowiedziała się po czym jej obiekt dziedziczy. Co więcej, instancje klasy, czyli obiekty są już w nie wyposażone:
s.class # zwraca obiekt reprezentujący klasę obiektu s.class.superclass # zwraca obiekt reprezentujący klasę bazową obiektu
Klasy Object i Class
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 przypadku klasy Student. Jednak klasa Osoba będąca przodkiem klasy Student 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 znajdziesz metodę class, która zwróci obiekt klasy, dla której została ona wywołana. Brzmi to dziwacznie, ale w Rubym nawet klasy są specyficznymi obiektami.
Z kolei metoda superclass zdefiniowana jest w klasie Class, która jest klasą wszystkich obiektów będących klasami. Zauważ, ż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żesz użyć konstrukcji:
Student.class
Metody klasy nadrzędnej
W kodzie definiującym klasę Student znajdziesz metodę pokaz, której nazwa jest taka sama, jak nazwa metody z klasy bazowej Osoba. Tak więc interpreter wywoła właśnie ją i nie będzie już zawracał sobie głowy poszukiwaniami w superklasie. Jednak nasza metoda ma uzupełniać, a nie przesłaniać działanie oryginalnej i dlatego korzystamy w niej ze słowa kluczowego super. Umieszczenie go w dowolnej metodzie sprawi, że zostanie wywołana ta sama metoda zdefiniowana w superklasie, pod warunkiem, że w klasie bazowej taka metoda istnieje.
thanks a lot.. it’s very good article.