Obiekty w Rubim, cz 1
(10 komentarzy)W kategoriach: Ruby , Ruby tutorial , Techblog / 30 lipca 2007 [01:19:44]
Tagi technorati: class instance method object oop programming ruby tutorial
Ta część tutoriala omówi w ekspresowym tempie podstawy definiowania klas i metod.
Teorię programowania obiektowego każdy już zapewne zna, przejdziemy więc od razu do konkretów: jak to się robi w Rubim?
Klasy
Spójrzmy na poniższy kod:
class Animal
@@count = 0 # (0)
def initialize(name, sound) # (1)
@name, @sound, @state = name, sound, "sitting"
@@count += 1 # (0)
end
def growl
puts @sound
end
def hierarchy
"Object > Animal"
end
end
class Wolf < Animal # (3)
def initialize(name, sound, color = "white")
super(name, sound) # (4)
@color = color
end
def hierarchy
super + " > Wolf" # (5)
end
end
burek = Wolf.new("Burek", "wrrrrrr") # (2)
Garść wyjaśnień:
- zmienne klasy (0) są zmiennymi, które nie należą do konkretnego obiektu (egzemplarza) klasy, są dla wszystkich obiektów danej klasy wspólne; odwołujemy się do nich, poprzedzając ich nazwę podwójną małpą (
@@) - konstruktory realizuje się metodą o nazwie
initialize(1), natomiast wywołuje się poprzezNazwaObiektu.new(parametry) - dziedziczenie (3) realizujemy poprzez konstrukcję
NazwaKlasy < NazwaKlasyNadrzednej; w Ru3bim istnieje jedynie dziedziczenie jednobazowe, ale tzw. miksiny (o których napiszę w następnej cześci) umożliwiają bardziej czytelny i łatwiejszy do zapanowania odpowiednik dziedziczenia wielobazowego super(4) umożliwia wywołanie metody klasy nadrzędnej; wywołany bez parametrów przyjmuje takie, jakie otrzymała metoda podrzędna; w naszym przypadku, dla klasy wilka, dochodzi jeden dodatkowy parametr, stąd konieczność ręcznego podania parametrów dla metodysuper- warto pamiętać, że super jest metodą, a więc może zwracać wartość - można go więc stosować tak, jak w (5)
getAttribute(), setAttribute(Attr attr) - won!
Nigdy nie używajmy takich konstrukcji - zamiast tego stosujmy piękne, rubinowe odpowiedniki. Jak zdefiniować settery i gettery w Rubim?
class Animal
# ...
def name=(name) # set
@name = name
end
def name # get
@name
end
end
burek = Wolf.new("Burek", "wrrrrrr")
puts burek.name
burek.name = "Szarik"
puts burek.name
Można jeszcze krócej. I ładniej przy okazji. Zastąpmy te dwie metody dwiema linijkami kodu:
class Animal
# ...
attr_reader :name # get
attr_writer :name # set
end
Gdy mamy pewność, że potrzebujemy jednocześnie settera i gettera dla danego atrybutu, możemy powyższą deklarację skrócić do jednej linijki:
attr_accessor :name
Ups...
Settery i gettery mogą powodować błędy, które czasem trudno wykryć. Nie jest to spowodowane niedociągnięciami języka, a zwykłą nieuwagą programisty. Chcąc skorzystać z settera/gettera wewnątrz klasy, zawsze używajmy self. Chcąc zmienić atrybut name, nigdy nie piszemy name = "newname", tylko zawsze self.name = "newname". W pierwszym przypadku zmieniamy nie atrybut, a zwykłą zmienną lokalną, czego przecież nie chcemy. Sytuacja wygląda zupełnie analogicznie w przypadku getterów.
Duck typing
Podobnie jak inne dynamiczne języki obiektowe, Ruby umożliwia tzn. duck typing (polskiego tłumaczenia nie ma co przytaczać, gdyż jest wulgarne). Krótko mówiąc: jeśli coś chodzi jak kaczka i kwacze jak kaczka, może byc traktowane jak kaczka. Dopiszmy do kodu z klasą zwierzęcia i wilka parę linijek:
class Animal
# ...
end
class Wolf < Animal
# ...
end
class Daemon
def growl
puts "666!"
end
end
arr = [Wolf.new("Burek", "wrrrrrrr"), Daemon.new]
arr.each { |a| a.growl }
wrrrrrrr 666!
Duck typing daje programiście wspaniały prezent - nie musi się babrać metodami wirtualnymi i polimorfizmem ani martwić się o interfejsy - zamiast tego otrzymuje te same możliwości, do których sięga się prostą i przejrzystą składnią.
Metody klasy (class methods)
Dotychczas zajmowaliśmy się metodami obiektu, egzemplarza (instance methods). Ich wywołanie było ściśle uzależnione od konkretnego obiektu i tego właśnie obiektu dotyczyło. Czasem jednak zachodzi potrzeba wywołania metody, która nie jest w żaden sposób uzależniona od jakiegokolwiek obiektu. Przykładem jest IO.read("plik.txt") (czytanie z pliku bez konieczności tworzenia obiektu klasy IO) lub...
class Animal
# ...
def Animal.population
@@count
end
end
Proste puts Animal.population wyświetli ilość egzemplarzy klasy Animal lub potomnych, które zostały stworzone w programie.
Kontrola dostępu
...czyli private, public i protected. W skrócie:
public- metodę może wywołać każdy, bez ograniczeń dostępuprivate- tylko dany obiekt może wywołać taką metodę (wykonywana jest w kontekścieself, czyli na sobie samym); uwaga: w innych językach jest inaczej - tam bowiem metodę prywatną może wywołać również inny obiekt tej samej klasyprotected- metodę może wywołać tylko obiekt danej klasy lub klasy potomnej
Jak to skodzić?
Można na dwa sposoby: klasycznie lub... inaczej :-) [jak to nazwać?].
class TestClass
public # każda metoda (poza initialize, która jest prywatna)
# jest domyślnie publiczna
def pub_meth1
end
def pub_meth2
end
private
def priv_meth1
end
protected
def prot_meth1
end
def prot_meth2
end
end
class TestClass
def pub_meth1
end
def pub_meth2
end
def priv_meth1
end
def prot_meth1
end
def prot_meth2
end
public :pub_meth1, :pub_meth2
private :priv_meth1
protected :prot_meth1, :prot_meth2
end
Który sposób wybierasz?
Parę słów o parametrach metod
Podstawy programowania obiektowego w Rubim mamy za sobą. Skupmy się teraz na nieco bardziej zaawansowanej składni przy definicjach metod.
Zmienna liczba argumentów
Argumenty, których ilości nie potrafimy przewidzieć, są zwyczajną tablicą, która pozwala nam na wygodne nimi manipulowanie. Tablicę znakujemy gwiazdką i występuje ona jako ostatni argument metody.
def test1(a, b, *c)
print a, b
c.each { |arg| print arg }
end
test1(1, 2, 4, 6, 8)
Zauważmy, że wywołanie test1(1, 2) nie zwróci żadnego błędu. Na wyjściu otrzymamy 12. Wygodnie i bezpiecznie :-).
Wywołać blok bez yielda
Czasem możemy potrzebować mieć dostęp do bloku jako obiektu. yield nam w tym nie pomoże. Ale są inne sposoby. Argument, który jest blokiem oznaczamy ampersandem (&), a przekazujemy go na wszystkim znany sposób:
def test2(a, b, &c)
print a, b
if block_given?
puts "", c.class
c.call
end
end
test2(1, 2) do
puts "Jestem w bloku!"
end
Jedno i drugie
Coś dla ludzi o wysokich wymaganiach:
def test3(a, b, *c, &d)
print a, b
c.each { |arg| print arg }
if block_given?
puts "", d.class
d.call
end
end
test3(1, 2, 4, 6, 8) do
puts "Jestem w bloku!"
end
Czy wiesz, że...
private,protectedorazpublicw obu formach to metody (w pierwszej formie informują, że następne definiowane metody będą miały określony dostęp, w drugiej - dynamicznie zmieniają dostęp metody na zadany)poniższy kod wykona się poprawnie:
class TestClass puts "Test!" endMożna tutaj Rubiemu zarzucać małą restrykcyjność, która owocuje burdelem w kodzie. Nic bardziej mylnego, czego dowodzą metody kontroli dostępu (
public...) a także wspaniałe konstrukcje, które znamy z Rails (np.has_many :commentslubvalidates_presence_of :login)metody klasy można definiować na aż trzy sposoby:
class Animal # ... # to już znamy # tak definiować możemy zarówno wewnątrz, jak i na zewnątrz klasy: def Animal.population @@count end # można też tak: def self.population @@count end # oraz tak: (przydatne przy grupowaniu metod klas) class <<self def population @@count end end end
sharnik
30 lipca 2007, 09:40:51> duck typing (polskiego tłumaczenia nie ma co przytaczać, gdyż jest wulgarne)
Hm, nie znałem.. Możesz przytoczyć? ;)
RazorJack
30 lipca 2007, 11:22:17Hyhy, ja też nie znam. Chyba nie ma. Ale w wolnym tłumaczeniu byłoby coś z kaczkami. Tfu, tfu.
lopex
30 lipca 2007, 14:12:50„metody klasy można definiować na aż trzy sposoby:” Ogólna postać definicji metod dla klas to: def Klasa.metoda… (w tym przypadku Animal to to samo co self). Dlatego metody klasy można też definiować „od zewnątrz” (def String.foo;end) bez jawnego otwierania klasy. Można też definiować metody per instance: (s=„foo”;def s.blah;end). Metoda blah jest definiowana dla klasy singleton wpinanej do listy (i w drugą stronę pod zmienną attached) przed właściwą klasą String (można więc powiedzieć że nawet klasy są wirtualne). Btw. używanie
zmiennych jest raczej odradzane na korzyść zmiennych instancji klasy.ferrante
30 lipca 2007, 14:26:14Świetne! :-)
mcv
31 lipca 2007, 00:17:21Podkreśliłbym, iż w innych językach zwykło być tak, że metoda prywatna może być wywołana przez inny obiekt tej samej klasy. W Rubym — nie — może być wywołana tylko przez obiekt, na rzecz którego jest wywoływana. ;-)
A i dopisałbym, że aby wywołać setter z wewnątrz jakiejś metody (tej samej klasy), należy napisać „self.setter = cośtam”, zamiast „setter = cośtam”. W drugim wypadku Ruby uzna, że chceliśmy zadeklarować zmienną lokalną o nazwie „setter”.
RazorJack
02 sierpnia 2007, 23:54:28mcv, lopex: Wielkie dzięki za uwagi. Uzupełniłem posta o Wasze sugestie :-).
lopex: O singletonach (a także modułach, miksinach i innych takich) napiszę w przyszłym odcinku, który nadchodzi wielkimi człapami. Cierpliwości ;).
Szymon Wro
05 sierpnia 2007, 23:13:31W klasie Animal:
def hierarchy „Object < Animal”
end
Nie powinno być „Animal < Object”?
RazorJack
05 sierpnia 2007, 23:21:03Hmm.
Objectjest klasą nadrzędną dla wszystkich, więc powinien w wyliczeniu być najbardziej po lewej stronie. Czyżbym odwrócił kierunek „strzałek” (< i >)? ;)Szymon Wro
06 sierpnia 2007, 00:15:40No właśnie, strzałki czy znaki większości? ;) Wg. Slagella Obiekt jest „większy” od innych klas… ;)
RazorJack
06 sierpnia 2007, 01:44:13Ależ ja głupi, przecież nawet w składni Rubiego dziedziczenie strzałkuje się tak, jak mówisz. Widocznie walnąłem kierunek, na jaki miałem wtedy ochotę. Ale byłem konsekwentny, nie można mi tego odmówić :-D.
Dzięki za czujność :).