Wprowadzenie do języka Ruby, cz. 2

Klasy i obiekty

Grafika

Jakiś czas temu, przy okazji opisywania podstaw Rails, starałem się wyjaśnić czym są klasy i obiekty. Jednak takie mieszanie poziomów ogólności w jednym wpisie może sprawiać, że całość wyda się mało przystępna dla początkujących i nudna dla obeznanych z tematem. Napiszę więc krótko o programowaniu obiektowym dla wszystkich tych, którzy znają już jakieś inne imperatywne, ale nieobiektowe języki programowania, a chcą dowiedzieć się, czym są obiekty i klasy.

Klasy i obiekty

Zmienne i ich typy

Wiemy, że 31337 to liczba całkowita, a "siała baba mak" to łańcuch znakowy. Programując, na przykład w języku C, musimy deklarować, jakiego typu dane (ang. data type) będą przechowywane pod adresem w pamięci przypisanym do tworzonej zmiennej. W tym celu korzystamy na przykład z konstrukcji int a = 31337;, gdzie int jest nazwą typu, a zmiennej, zaś 31337 jej początkową wartością. Podanie typu jest potrzebne kompilatorowi, aby tłumacząc kod na język maszynowy, wiedział ile przestrzeni powinien zarezerwować na tę „szufladkę” służącą do przechowywania informacji.

W językach średniego i niskiego poziomu często musimy przejmować się pamięcią zajmowaną przez zmienne. Dzieje się tak dlatego, że im niższego poziomu jest język, tym bliżej mu do maszyny. Oznacza to, że programista musi uwzględniać np. kolejności bajtów w długich liczbach czy dbać, aby po użyciu struktur danych zajmowana przez nie pamięć została zwolniona. W językach wysokiego poziomu zajmuje się tym kompilator lub interpreter.

Zmiennych używa się w instrukcjach i wyrażeniach realizowanych przez program. Gdy programista pisze kod, stara się przenieść jakiś problem z rzeczywistego świata do komputera, aby korzystając z jego mocy obliczeniowej, szybciej go rozwiązać. Jednak mając tylko kilka typów danych, które zostały pomyślane bardziej z myślą o maszynie niż człowieku, trzeba się czasem porządnie nagimnastykować. Na przykład: jeśli zamierzamy stworzyć program przechowujący dane osobowe, takie jak wiek, imię i nazwisko oraz płeć, to programując na średnim czy niskim poziomie, musimy utworzyć osobną zmienną przechowującą wiek (liczba całkowita o długości jednego bajtu), osobną na łańcuch tekstowy z imieniem i nazwiskiem (tablica bajtów zakończona znakiem o kodzie 0), a osobną na zmienną mówiącą, czy osoba jest płci męskiej czy żeńskiej (zmienna typu boolean o długości jednego bitu, choć w praktyce może być przezroczyście rzutowana na jeden bajt).

W języku C mamy akurat pewne ułatwienie w postaci tzw. struktur – typu danych pozwalającego grupować obiekty innych typów. Dzięki temu program staje się bardziej czytelny i łatwiej zarządzać informacjami, ale wciąż trzeba pamiętać o osobnym przetwarzaniu każdej informacji. W końcu zechcemy pewnie zdefiniować tablicę osób, czyli tak naprawdę tablicę wskaźników do miejsc, w których znajdują się struktury zawierające wspomniane pola. Jeszcze tylko zarządzanie tą tablicą, przydzielanie i zwalnianie pamięci, podstawowe operacje wyszukiwania, dodawania i kasowania… brzmi nieco koszmarnie.

Klasa i obiekt

Ktoś pomyślał kiedyś, że znacznie łatwiej byłoby, gdyby dało się tworzyć własne typy danych i to nie w celu zadowolenia komputera, ale lepszego operowania na abstrakcyjnych informacjach. Języki pozwalające tworzyć takie typy danych nazywa się właśnie językami obiektowymi, ponieważ zamiast zmiennych występują tam obiekty (ang. objects), a zamiast typów klasy (ang. classes). Na obiekty mówi się też egzemplarze klasy lub instancje klasy.

Oczywiście zmienne również istnieją w językach obiektowych, ale często przechowują odwołania do obiektów, a nie bezpośrednio jakieś pojedyncze wartości.

Wyobraźmy sobie, że zamiast typu oznaczającego liczbę całkowitą i osobnego typu przechowującego pojedyncze znaki możemy skorzystać z typu Osoba, który pozwala określić jednocześnie wiek, imię i nazwisko, a także płeć. Oczywiście nie uświadczymy takiego gotowego typu danych, jednak możemy go samodzielnie stworzyć.

Przykład definicji klasy
class Osoba; end
class Osoba; end

Żeby zapamiętać do czego służy klasa, wystarczy skojarzyć sobie, że dokonujemy klasyfikacji danych jakiegoś typu, w taki sposób, iż wyróżniamy wspólne dla nich operacje i struktury w pamięci zdolne je pomieścić. Z kolei obiekt można sobie wyobrazić jako coś bardziej namacalnego, co jest tworzone w oparciu o wytyczne zawarte w klasie.

Składowe

Klasa posiada składowe, to znaczy, że podczas jej definiowania określamy z jakich elementów będzie złożona. Część z nich służy do przechowywania danych, a część do operowania na nich.

W większości popularnych, obiektowych języków programowania istnieje operator pozwalający na dostęp do danej składowej klasy – nazywa się on operatorem wyboru składowej lub operatorem kropki. Po nazwie obiektu stawia się kropkę, a po niej podaje nazwę składowej, którą chcemy uzyskać. Jeśli składową jest na przykład definiowana w klasie funkcja, to dla danego obiektu tej klasy zostanie ona wywołana, a jej wynik zwrócony.

W języku Ruby, podobnie jak w innych językach obiektowych, składowymi są polametody.

Ilustracja klasy

Pola

Pola (ang. fields), nazywane czasem zmiennymi instancyjnymi (ang. instance variables) lub zmiennymi egzemplarza (bo należą do egzemplarza klasy, czyli do obiektu), są składowymi służącymi do przechowywania danych obiektu.

Jeśli przykładowa klasa Osoba ma określać ile ktoś ma lat i jakiej jest płci, to możemy stworzyć odpowiednie pola o wskazujących na zawartość nazwach:

Klasa może mieć tyle pól, ile chcemy. W języku Ruby nie trzeba deklarować, jakich typów będą pola (czy raczej, jakich klas obiektami one będą). Nie musimy nawet ich wprost podawać, opisując klasę.

Pola osadzone są w obiektach. Co to oznacza? Materializowane są dopiero w momencie utworzenia jakiegoś opisywanego za pomocą klasy egzemplarza. Nie można też uzyskać bezpośrednio dostępu do danego pola obiektu, posługując się operatorem wyboru składowej. Wychodzi więc na to, że zmienne instancyjne w języku Ruby są prywatną sprawą obiektów, w których istnieją.

Jeśli w metodzie operującej na obiekcie spróbujemy odczytać jakieś pole, którego wartość nie została ustawiona, to zwrócony zostanie obiekt nil.

Zmienne instancyjne obiektów w Rubym charakteryzują się tym, że przed ich nazwami występuje znak @, np. @wiek.

Metody

W językach obiektowych rozwiązano problem polegający na rozdzieleniu specyficznych operacji od danych, których te operacje dotyczą, wprowadzając metody. Metoda (ang. method) to nic innego jak funkcja składowa klasy, która służy do dokonywania operacji na jej obiektach i ma dostęp do innych składowych (zarówno metod, jak i pól).

Ponieważ w języku Ruby zmienne instancyjne widoczne są tylko dla metod klasy, więc to właśnie metody są jedynym sposobem na to, aby można było odpytać obiekt o jego właściwości, np. obiekt klasy Osoba o wiek czy płeć. Podobnie tworzy się też metody, które będą ustawiały wartości pewnych pól. Oto przykład klasy zawierającej takie metody:

Przykład klasy zawierającej metody
class Osoba
  
  # metoda odczytująca pole wiek
  def wiek(x)
    return @wiek
  end
 
  # metoda ustawiająca pole wiek
  def wiek=(x)
    @wiek = x
  end
end
class Osoba # metoda odczytująca pole wiek def wiek(x) return @wiek end # metoda ustawiająca pole wiek def wiek=(x) @wiek = x end end

Taka klasa ma tylko jedną zmienną instancyjną (@wiek), która powoływana jest do życia po wywołaniu metody wiek=. Ta dziwna, zakończona operatorem przypisania nazwa funkcji ma specjalne znaczenie syntaktyczne. Zostanie ona wywołana za każdym razem, gdy w programie pojawi się wywołanie tego typu:

obiekt.wiek = 1234
obiekt.wiek = 1234

Jest ono równoważne zapisowi:

obiekt.wiek=(1234)
obiekt.wiek=(1234)

Możemy też stworzyć metodę ustaw_wiek, jednak wtedy będzie to mniej przejrzyste w zapisie.

Metody zlokalizowane są w klasie, a nie w obiekcie, chociaż fakt ten jest przed nami ukrywany. Byłoby to marnotrawieniem pamięci, gdyby każdy tworzony obiekt miał na wyposażeniu kopie wszystkich funkcji składowych. Rozwiązano to więc w ten sposób, że w przypadku metod, inaczej niż w przypadku pól, w instancji klasy (czyli w obiekcie) znajdują się tylko odwołania do miejsc w klasie, pod którymi rezydują funkcje składowe.

Można powiedzieć, że metody obiektu są pewnego rodzaju interfejsem pozwalającym mu komunikować się z innymi obiektami.

Akcesory

Wyobraźmy sobie klasę, która ma 5 czy więcej pól, przy czym pierwsze trzy powinno dać się bez przeszkód zapisywać i odczytywać, przedostanie tylko ustawiać, a ostatnie jedynie odczytywać.

Przykład zastosowania metod dostępowych
class Osoba
  # metody odczytujące pola
  def pole1(x); @pole1 end
  def pole2(x); @pole2 end
  def pole3(x); @pole3 end
  def pole5(x); @pole5 end
 
  # metody zapisujące pola
  def pole1=(x); @pole1 = x end
  def pole2=(x); @pole2 = x end
  def pole3=(x); @pole3 = x end
  def pole4=(x); @pole4 = x end
end
class Osoba # metody odczytujące pola def pole1(x); @pole1 end def pole2(x); @pole2 end def pole3(x); @pole3 end def pole5(x); @pole5 end # metody zapisujące pola def pole1=(x); @pole1 = x end def pole2=(x); @pole2 = x end def pole3=(x); @pole3 = x end def pole4=(x); @pole4 = x end end

W stosunku do poprzedniego przykładu usunąłem słowo kluczowe return, ponieważ w Rubym można je stosować, ale nie trzeba – metoda i tak zwraca rezultat ostatnio wykonywanego wyrażenia. Poza tym użyłem średnika jako separatora, żeby zmieścić definicję każdej z metod w jednej linii.

Jednak twórcy Ruby’ego pomyśleli o takich częstych operacjach i w związku z tym istnieją słowa kluczowe pozwalające szybciej zdefiniować tzw. akcesory (ang. accessors), w tym settery (metody do ustawiania pól) i gettery (metody do odczytywania ich zawartości). Oto jak można łatwiej zakodować poprzednio zdefiniowane zachowanie:

Przykład korzystania z akcesorów
class Osoba
  # metody odczytujące i zapisujące pola
  attr_accessor :pole1, :pole2, :pole3
 
  # metody odczytujące pola
  attr_reader :pole5
 
  # metody zapisujące pola
  attr_writer :pole4
end
class Osoba # metody odczytujące i zapisujące pola attr_accessor :pole1, :pole2, :pole3 # metody odczytujące pola attr_reader :pole5 # metody zapisujące pola attr_writer :pole4 end

Skrót attr bierze się stąd, że pola nazywa się też czasem atrybutami klasy. Dwukropek przed nazwami pól (podawanymi jako argumenty) zmienia następujący po nim łańcuch tekstowy w tzw. symbol.

Symbole w Rubym to wydajne łańcuchy znakowe. Są takie, ponieważ dokonywana jest ich internalizacja, która w skrócie polega na przechowywaniu w jednym pamięciowym miejscu łańcuchów, których wartości są takie same.

Słowo kluczowe, np. attr_reader, jest specjalną metodą wyższego poziomu, która przyjmuje tablicę symboli i dla każdego z nich definiuje w klasie metodę służącą do odczytu zmiennej egzemplarza (czyli pola obiektu) o danej nazwie.

Lokalizacja pól i metod

W języku Ruby, podobnie jak w innych językach obiektowych, składowymi są pola i metody. Pola przypisane są do obiektu tworzonego na podstawie klasy, a metody przypisane są do klasy, choć można używać ich w obiektach.

Oczywiście można powiedzieć, że „klasa ma pola”, choć tak naprawdę dopiero metody instancyjne zawierają odwołania do pól obiektu, które powstaną, gdy zostanie on stworzony.

Ilustracja klasy i obiektu

Zapamiętajmy więc ogólną zasadę:

Obiekty służą do przechowywania zmiennych instancyjnych,
a w klasach można przechowywać nie tylko zmienne, ale również metody.

Ta druga część zdania jest prawdziwa dlatego, że w Rubym klasa też jest obiektem, czyli tworząc klasę, tak naprawdę stwarzamy pewien specyficzny egzemplarz, z tym że na wyższym poziomie abstrakcji. Oczywiście nie działa to w drugą stronę i nie każdy obiekt jest klasą.

Życie obiektu

Nie napisałem jeszcze, jak tworzy się nowy obiekt jakiejś klasy. Służy do tego metoda klasowa new:

obiekt = Osoba.new
obiekt = Osoba.new

Co się stało? Po pierwsze: po lewej stronie wyrażenia przypisania stworzona została zmienna o nazwie obiekt. Będzie ona zawierała referencję, czyli odniesienie do miejsca w pamięci, pod którym umieszczony zostanie nowo powstały obiekt klasy Osoba.

Z kolei po prawej stronie wyrażenia mamy stałą o nazwie Osoba, która wcześniej została skojarzona z miejscem w pamięci, w którym rezyduje zaprezentowana wcześniej definicja klasy.

W języku Ruby pierwotne wskazania na klasy są przechowywane w stałych, a stałe w tym języku można poznać po tym, że ich nazwy zapisujemy zawsze rozpoczynając je wielką literą. Wynika z tego, że nazwy klas też podlegają tej regule.

Po nazwie stałej jest wywołanie metody new. Powinno to nas zastanowić, bo po pierwsze nie była tworzona żadna taka metoda, a po drugie nie mamy do czynienia z obiektem, ale z klasą. Czy to nie jest czasem odwołanie do metody, która zawsze „siedzi w klasie”? Otóż nie. Wyjaśnię to później, natomiast teraz ważne jest, aby wiedzieć, że metoda ta tworzy nowy obiekt klasy, dla której ją wywołano i zwraca referencję wskazującą na ten obiekt. Jak łatwo się domyślić, wynik powędruje do zmiennej obiekt, która będzie reprezentowała go w programie.

Konstruktor

Obiekty posiadają tzw. konstruktory (ang. constructors), czyli metody, które wykonywane są za każdym razem, gdy instancje zaczynają istnieć. W języku Ruby konstruktorem jest metoda o nazwie initialize. Zwykle ustawia się w niej domyślne wartości pól i wykonuje inne potrzebne rzeczy.

Dla każdego nowo tworzonego obiektu będzie wywołana metoda initialize. Możemy przekazać jej argumenty, jeśli tego potrzebujemy, ale nie wywołując jej wprost, lecz korzystając z tzw. metody klasowej new, która przekaże podane jej argumenty właśnie konstruktorowi.

Przykład tworzenia konstruktora klasy
class Osoba
  def initialize(imie=nil)
    @wiek = 12345
    @plec = false
    @imie = imie unless imie.nil?
  end
end
 
mariola = Osoba.new 'Mariola'
 
p mariola.wiek  # => 12345
p mariola.imite # => Mariola
class Osoba def initialize(imie=nil) @wiek = 12345 @plec = false @imie = imie unless imie.nil? end end mariola = Osoba.new 'Mariola' p mariola.wiek # => 12345 p mariola.imite # => Mariola

Metody klasowe

Powiem trochę więcej o tej dziwnej i ciekawej zarazem konstrukcji:

obiekt = Osoba.new
obiekt = Osoba.new

Zauważmy, że wywołujemy tutaj metodę klasy, a nie metodę obiektu. Takie metody nazywamy metodami klasowymi, a w niektórych językach statycznymi metodami. W definicji klasy Osoba trudno więc szukać metody new.

Metoda klasowa przydaje się wtedy, gdy mamy do czynienia z operacjami, które nie muszą mieć dostępu do pól konkretnych obiektów, jednak działają w odniesieniu do nowego typu danych. Na przykład w klasie Osoba mogłaby zostać zdefiniowana metoda klasowa, która dla każdej osoby, nie ważne jakiej, jest w stanie stwierdzić płeć na podstawie imienia.

Inne zastosowanie klasowych metod to tworzenie ułatwień obsługi polegających na dodawaniu własnych słów kluczowych, z których można korzystać podczas definiowania klasy. Metody klasowe można bowiem wywoływać w jej ciele, a efektem ich działania może być np. zdefiniowanie metody (tak jak dzieje się to w przypadku konstrukcji w stylu attr_accessor).

Przykład zdefiniowania i użycia metody klasowej
class Osoba
  # definiowanie metody klasowej
  # przedrostek self. sygnalizuje, że tworzymy metodę dla klasy
  def self.plec?(imie)
    imie.downcase.slice(-1) == 'a' ? 'kobieta' : 'mężczyzna'
  end
end
 
Osoba.plec? "Mariola"  # => kobieta
class Osoba # definiowanie metody klasowej # przedrostek self. sygnalizuje, że tworzymy metodę dla klasy def self.plec?(imie) imie.downcase.slice(-1) == 'a' ? 'kobieta' : 'mężczyzna' end end Osoba.plec? "Mariola" # => kobieta

Z wnętrza metody instancyjnej możemy dobrać się do metody klasowej:

Przykład wywołania metody klasowej przez metodę egzemplarza
class Osoba
  def plec?(imie)
    self.class.plec(imie)  # wywołanie metody klasowej
  end
end
 
obiekt = Osoba.new
obiekt.plec? "Mariola"  # => kobieta
class Osoba def plec?(imie) self.class.plec(imie) # wywołanie metody klasowej end end obiekt = Osoba.new obiekt.plec? "Mariola" # => kobieta

Możemy też skorzystać z niej w bardziej praktyczny sposób, np. aby ustawiać płeć na podstawie imienia, jeśli jeszcze płci nie określono. Dla czytelności stworzę całą klasę.

Przykład wykorzystania metody klasowej
class Osoba
  attr_accessor :plec, :wiek
  attr_reader :imie
 
  # metoda klasowa wykrywająca płeć
  # zwraca false w przypadku kobiety
  # zwraca true w przypadku mężczyzny
  def self.plec?(imie)
    imie.downcase.slice(-1) != 'a'
  end

  # metoda instancyjna podająca płeć
  # na podstawie zmiennej instancyjnej @plec
  def plec?
    @plec ? 'chlop' : 'baba'
  end
 
  # metoda ustawiająca imię, czyli setter
  # przy okazji wywołuje metodę klasową plec
  # i na tej podstawie ustawia pole @plec
  # jeśli nie zostało ono ustawione (jest nil)
  def imie=(imie)
    @imie = imie
    @plec = self.class.plec?(imie) if @plec.nil?
  end
end
 
# tworzenie obiektów
mariola = Osoba.new
stefan = Osoba.new
 
# nadawanie imion
mariola.imie = "Mariola"
stefan.imie = "Stefan"
 
# odpytywanie informacji o płci
p mariola.plec?  # => baba
p stefan.plec?   # => chlop
class Osoba attr_accessor :plec, :wiek attr_reader :imie # metoda klasowa wykrywająca płeć # zwraca false w przypadku kobiety # zwraca true w przypadku mężczyzny def self.plec?(imie) imie.downcase.slice(-1) != 'a' end # metoda instancyjna podająca płeć # na podstawie zmiennej instancyjnej @plec def plec? @plec ? 'chlop' : 'baba' end # metoda ustawiająca imię, czyli setter # przy okazji wywołuje metodę klasową plec # i na tej podstawie ustawia pole @plec # jeśli nie zostało ono ustawione (jest nil) def imie=(imie) @imie = imie @plec = self.class.plec?(imie) if @plec.nil? end end # tworzenie obiektów mariola = Osoba.new stefan = Osoba.new # nadawanie imion mariola.imie = "Mariola" stefan.imie = "Stefan" # odpytywanie informacji o płci p mariola.plec? # => baba p stefan.plec? # => chlop

Metody instancyjne, czyli te które można wywoływać dla obiektów, pamiętane są wewnątrz klasy, gdzie więc zapamiętywane są metody klasowe? Specjalnie dla nich tworzona jest dodatkowa, ukryta przed programistą, dodatkowa klasa zwana w Rubym metaklasą (ang. metaclass) lub klasą własną opisującą inną klasę (ang. singleton class of a class). Jest to wirtualny „towarzysz” każdej klasy, generowany wtedy, gdy pojawi się taka potrzeba. O metaklasach postaram się napisać w kolejnej części.

Wiemy już, czym jest metoda new, ale nie wiemy kiedy została zdefiniowana. Zajmiemy się tym później, przy okazji omawiania dziedziczenia, ważnej cechy języków obiektowych.

Zmienne klasowe

Skoro istnieją klasowe metody, to konsekwentnie korzystać możemy również z klasowych zmiennych (ang. class variables), czyli z atrybutów niezależnych od obiektów, ale związanych z daną klasą. Dzięki nim da się zapamiętywać dane, które są wspólne dla wszystkich egzemplarzy. Na przykład możemy używać zmiennej klasowej, żeby pamiętać liczbę wszystkich ludzi.

Przykład wykorzystania zmiennej klasowej
class Osoba
  # wszyscy ludzie
  @@suma = 0
 
  # akcesory
  attr_accessor :imie, :plec, :wiek
 
  # konstruktor
  def initialize
    @@suma += 1
  end
end
 
# tworzenie pięciu obiektów
5.times { Osoba.new }
 
# wyświetlanie zawartości zmiennej klasowej @@suma
p Osoba.class_variable_get :@@suma
class Osoba # wszyscy ludzie @@suma = 0 # akcesory attr_accessor :imie, :plec, :wiek # konstruktor def initialize @@suma += 1 end end # tworzenie pięciu obiektów 5.times { Osoba.new } # wyświetlanie zawartości zmiennej klasowej @@suma p Osoba.class_variable_get :@@suma

W metodzie initialize wywoływanej za każdym razem, gdy tworzony jest nowy obiekt, znajduje się wyrażenie, które zwiększa wartość zmiennej klasowej @@suma o jeden. W związku z tym zawierała ona będzie liczbę odpowiadającą liczbie stworzonych (a właściwie zainicjowanych) obiektów klasy Osoba.

Zmienne klasowe (lub innymi słowy pola klasowe) w języku Ruby przechowywane są w obiekcie klasy, a jak widać na powyższym listingu, aby z nich skorzystać używa się dwóch znakow @ umieszczonych przed nazwą.

Gdy interpreter języka widzi zapis @@zmienna bezpośrednio w kontekście klasy, to odwołuje się do zmiennej o tej nazwie, składowanej w klasie. Jeśli natomiast konstrukcja taka zostanie wywołana w kontekście instancji (np. w uruchomionej, korzystając z obiektu, metodzie instancyjnej), to poszukiwana jest klasa tego konkretnego egzemplarza i tam odnajdywana jest zmienna. Dlatego zmienne klasowe można zapisywać tak samo w metodach instancyjnych, metodach klasowych i w samej klasie.

Niektórzy radzą, żeby tam gdzie się da unikać korzystania ze zmiennych klasowych, ponieważ są zaprzeczeniem zasady hermetyzacji.

Zmienne instancyjne klasy

Zmienne instancyjne klasy lub klasowe zmienne instancyjne (ang. class instance variables, class-level instance variables) kojarzą się z jakimś dziwnym wynalazkiem podobnym do świnki morskiej (ni to świnka, ni to morska). Jednak przypominając sobie slogan „w Rubym wszystko jest obiektem”, ma to sens.

Jeżeli klasa jest również obiektem, a więc tak samo, jak każdy obiekt, może mieć własne zmienne instancyjne – taka nazwa może być wieloznaczna, bo mocno zależy od kontekstu, dlatego używa się dopełniacza „klasy”. Jeżeli traktujemy klasę tak jak obiekt, to będą to po prostu zmienne instancyjne tego obiektu, jeśli natomiast mamy na myśli nieco szerszą perspektywę, w tym obiekty tej klasy, to warto dodać, że chodzi o zmienne instancyjne tej klasy, a nie po prostu o zmienne instancyjne (w domyśle: obiektów tej klasy).

Zmienne instancyjne klasy różnią się od zmiennych klasowych tym, że nie można mieć do nich bezpośredniego dostępu w kontekście obiektu (np. w metodzie instancyjnej wywołanej dla obiektu). Widać je tylko w samej klasie i w metodach klasowych.

Przykład wykorzystania zmiennej instancyjnej klasy
class Osoba
  # wszyscy ludzie
  @suma = 0
 
  # akcesory dla zmiennej instancyjnej klasy
  def self.suma; @suma end
  def self.suma=(x); @suma = x end
 
  # akcesory dla zmiennych instancyjnych obiektów
  attr_accessor :imie, :plec, :wiek
 
  # konstruktor
  def initialize
    self.class.suma += 1
  end
end
 
# tworzenie pięciu obiektów
5.times { Osoba.new }
 
# wyświetlanie zawartości zmiennej instancyjnej klasy @suma
p Osoba.suma
class Osoba # wszyscy ludzie @suma = 0 # akcesory dla zmiennej instancyjnej klasy def self.suma; @suma end def self.suma=(x); @suma = x end # akcesory dla zmiennych instancyjnych obiektów attr_accessor :imie, :plec, :wiek # konstruktor def initialize self.class.suma += 1 end end # tworzenie pięciu obiektów 5.times { Osoba.new } # wyświetlanie zawartości zmiennej instancyjnej klasy @suma p Osoba.suma

I jeszcze jeden przykład, który warto pamiętać, ucząc się języka Ruby:

Przykład obrazujący różnicę kontekstu w dostępie do zmiennej instancyjnej
class Osoba
  @suma = 0  # zmienna instancyjna klasy

  # metoda klasowa odczytująca zmienną instancyjną klasy
  def self.suma
    @suma
  end
 
  # metoda odczytująca zmienną instancyjną obiektu
  def suma
    @suma  # to zupełnie inna zmienna niż poprzednia!!!
  end
end
class Osoba @suma = 0 # zmienna instancyjna klasy # metoda klasowa odczytująca zmienną instancyjną klasy def self.suma @suma end # metoda odczytująca zmienną instancyjną obiektu def suma @suma # to zupełnie inna zmienna niż poprzednia!!! end end

Podsumowanie

Klasa to nowy typ danych, pewien rodzaj wzorca, który jest matrycą dla powoływanych do życia obiektów stworzonych w oparciu o niego. Może ona zawierać składowe, czyli pola i metody, ale tak naprawdę sama klasa nie służy do przechowywania danych – informacje którymi chcemy zarządzać z użyciem klasy zaczynają „żyć” w pamięci dopiero, gdy na podstawie klasy stworzymy obiekt.

Klasa jest czymś subtelnym, niczym idea na zorganizowanie danych. Z kolei obiekt jest materializacją klasy, jej wyrazem w pamięci zajmującym określone miejsce. Można też użyć prostszej analogii, która jest łatwa do zapamiętania:

Klasa to foremka,
a jej instancje to ciastka wypiekane w tej foremce.

Na koniec małe zestawienie elementów programowania obiektowego i ich cech:

ElementInne nazwyZastosowaniePrzykład
klasawzorzectworzenie typu danychclass Osoba; end
metaklasaklasa własna opisująca klasę,
klasa typu singleton opisująca klasę
uzupełnianie właściwości typu danychclass Osoba << self
end
obiektegzemplarz, instancjaprzechowywanie danychosoba = Osoba.new
klasa własna obiektuklasa własna opisująca obiekt,
klasa typu singleton opisująca obiekt
uzupełnianie właściwości obiektuclass Osoba; end
osoba = Osoba.new
osoba << self
end
zmienna instancyjnapole,
zmienna egzemplarza
przechowywanie danych obiektuclass Osoba
  def ustaw_wiek
    @wiek = 18
  end
end
zmienna klasowapole klasy,
zmienna statyczna klasy
przechowywanie danych klasyclass Osoba
  @@liczba = 5
end
zmienna instancyjna klasyzmienna metaklasy,
pole metaklasy
przechowywanie danych metaklasyclass Osoba
@suma = 5
  def self.daj_sume
    return @suma
  end
end
Osoba.daj_sume
metoda instancyjnametoda,
funkcja składowa
operowania na polach obiektuclass Osoba
def dodaj(x)
@x=x
  end
end
osoba = Osoba.new
osoba.dodaj 5
metoda klasowafunkcja statyczna,
statyczna funkcja składowa,
metoda statyczna
operowania na danych niezwiązanych z obiektemclass Osoba
  def self.ustaw(x)
    @@x=x
  end
end
Osoba.ustaw 5
metoda singletonowametoda własna,
metoda własna egzemplarza
definiowania operacji dla konkretnego obiektucap = Osoba.new
def cap.smrod(x)
  @smrod=x
end
cap.smrod 1000
ElementOpisywany przez…Dostępny w…Składowany w…
klasaklasę Class i ew. metaklasęprogramiepamięci przeznaczonej na klasy i obiekty
metaklasaklasę Classklasie i programiepamięci przeznaczonej na klasy i obiekty
obiektklasę i ew. klasę własnąprogramiepamięci przeznaczonej na klasy i obiekty
klasa własna obiektuklasęobiekcie i programiepamięci przeznaczonej na klasy i obiekty
zmienna instancyjnametodach instancyjnychobiekcie
zmienna klasowaklasie i metodach instancyjnych oraz klasowychklasie
zmienna instancyjna klasyklasie i metodach klasowychklasie
metoda instancyjnaobiekcieklasie
metoda klasowaklasiemetaklasie
metoda singletonowaobiekcieklasie własnej obiektu

comments powered by Disqus