Ruby - wprowadzenie, część 3 - Gdyby Chuck Norris programował, używałby języka Ruby
sobota, 11 luty 2006, w kategoriach: Programowanie, Ruby
Dziś zajmiemy się kluczową dla całego tutoriala koncepcją - programowaniem obiektowym. Ciężko będzie dalej zrobić cokolwiek fajnego np. w Rails, bez bardzo dokładnego zrozumienia co i jak z obiektami. Więc, jedziemy…
Podstawowym terminem w programowaniu obiektowym jest klasa - komputerowy model jakiejś rzeczywistej bądź abstrakcyjnej rzeczy. Na model ten składają się właściwości, opisane za pomocą zmiennych (zmienna będąca częścią klasy to pole) i zachowanie, możliwe czynności które mogą być przez tą rzecz wykonane, opisane procedurami (z kolei procedura będąca częścią klasy to metoda).
Można to zilustrować na następującym, pasjonującym przykładzie:
irb(main):001:0> class Pies irb(main):002:1> def initialize irb(main):003:2> @odglos = "Hau hau!" irb(main):004:2> end irb(main):005:1> def szczekaj irb(main):006:2> puts @odglos irb(main):007:2> end irb(main):008:1> end => nil irb(main):009:0> pies = Pies.new => # irb(main):010:0> pies.szczekaj Hau hau! => nil
Metoda “initialize” nie jest normalną procedurą, przeznaczoną do normalnego, jawnego wywoływania przez użytkownika. Opisuje ona sposób, w jaki konstruowany jest obiekt. Uruchamiana jest ona, kiedy użytkownik konstruję obiekt, czyli w tym przypadku “pies = Pies.new” i argumenty przekazane “new”, będą także przekazane “initialize”. Metodę tą nazywamy konstruktorem. “@” poprzedzająca definicję zmiennej, oznacza iż jest ona polem, częścią klasy.
Jak pewnie większość z Was wie, bezpośredni dostęp do składowych klas jest sprzeczny z podstawą zasadą programowania obiektowego - ukrywania danych. Dlatego w większości języków piszemy jakiś rodzaj funkcji get/set zwracających i ustawiającyh wartość pola. W Rubym jednak sprawa wygląda inaczej. Do dyspozycji mamy trzy makra (w sensie metody generującej metodę - tak tak, takie rzeczy są w Rubym możliwe): attr_reader, attr_writer i attr_accessor generujące odpowiednio “get”, “set” oraz obie te metody. Późniejsze użycie tych metod odbywa się przezroczyście - kiedy piszemy “puts instancjaklasy.pole” albo “instancjaklasy.pole = 100″ w rzeczywistości wołamy wygenerowane przez ww. makra metody. Przykład:
irb(main):001:0> class Klasa irb(main):002:1> attr_accessor :pole irb(main):003:1> def initialize irb(main):004:2> @pole = 100 irb(main):005:2> end irb(main):006:1> end => nil irb(main):007:0> instancjaklasy = Klasa.new => # irb(main):008:0> instancjaklasy.pole => 100 irb(main):009:0> instancjaklasy.pole = 200 => 200 irb(main):010:0> instancjaklasy.pole => 200
Są klasy, które pewne cechy mają wspólne, jak to w świecie - pewne rzeczy są specjalnymi odmianami pewnych innych rzeczy, “dziedzicząc” część ich właściwości i zastosowań. Tak samo tu, pewne klasy, będą specjalnymi odmianami pewnych innych klas, z pewną dodatkową funkcjonalnością. Przykład:
irb(main):001:0> class Bazowa irb(main):002:1> attr_accessor :pole1 irb(main):003:1> def initialize irb(main):004:2> @pole1 = 100 irb(main):005:2> end irb(main):006:1> end => nil irb(main):007:0> class Pochodna Bazowa irb(main):008:1> attr_accessor :pole2 irb(main):009:1> def initialize irb(main):010:2> super irb(main):011:2> @pole2 = 200 irb(main):012:2> end irb(main):013:1> end => nil irb(main):014:0> bazowa = Bazowa.new => # irb(main):015:0> pochodna = Pochodna.new => # irb(main):016:0> bazowa.pole1 => 100 irb(main):017:0> pochodna.pole1 => 100 irb(main):018:0> pochodna.pole2 => 200 irb(main):019:0> bazowa.pole2 # pole 2 jest zdefiniowane jedynie w # klasie pochodnej, a więc otrzymujemy # błąd NoMethodError: undefined method `pole2' for # from (irb):19
Widzimy dwie nowe konstrukcje. Pierwsza z nich “class Pochodna
irb(main):001:0> module ModulBazowy
irb(main):002:1> def hello
irb(main):003:2> puts "Heyah!"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> class KlasaPochodna1
irb(main):007:1> include ModulBazowy
irb(main):008:1> end
=> KlasaPochodna1
irb(main):009:0> class KlasaPochodna2
irb(main):010:1> include ModulBazowy
irb(main):011:1> end
=> KlasaPochodna2
irb(main):012:0> klasa1 = KlasaPochodna1.new
=> #
irb(main):013:0> klasa2 = KlasaPochodna2.new
=> #
irb(main):014:0> klasa1.hello
Heyah!
=> nil
irb(main):015:0> klasa2.hello
Heyah!
=> nil
Funkcjonalność taką nazywamy “mixin”.
Innym zastosowaniem modułu, jest zgrupowanie pewnej liczby funkcji narzędziowych i przydzielenie im przestrzeni nazw, tak jak poniżej:
irb(main):001:0> module PrzestrzenNazw1 irb(main):002:1> STALA = 100 irb(main):003:1> def PrzestrzenNazw1.przedstaw_sie irb(main):004:2> puts "PrzestrzenNazw1" irb(main):005:2> end irb(main):006:1> end => nil irb(main):007:0> module PrzestrzenNazw2 irb(main):008:1> STALA = 200 irb(main):009:1> def PrzestrzenNazw2.przedstaw_sie irb(main):010:2> puts "PrzestrzenNazw2" irb(main):011:2> end irb(main):012:1> end => nil irb(main):013:0> puts PrzestrzenNazw1::STALA 100 => nil irb(main):014:0> puts PrzestrzenNazw2::STALA 200 => nil irb(main):015:0> PrzestrzenNazw1.przedstaw_sie PrzestrzenNazw1 => nil irb(main):016:0> PrzestrzenNazw2.przedstaw_sie PrzestrzenNazw2 => nil
Gwoli wyjaśnienia - “def PrzestrzenNazw1.przedstaw_sie” - tak definiuje się metody stayczne, czyli nie wymagające inicjializacji otaczającego obiektu do swego działania. Tu mamy do czynienia z modułem, a więc wszystkie metody muszą być statyczne, jeśli nie zamierzamy “wmiksować go” do żadnej klasy, lecz używać bezpośrednio.
Pozostał nam jeszcze temat kontroli dostępu do składowych klasy (metod i pól). Zgodnie z powszechnie znanymi zasadami programowania obiektowymi, powinniśmy udostępniać “światu zewnętrznemu”, czyli wszystkiemu spoza klasy, jak najmniej informacji. Służą temu specyfikatory dostępu, które w Rubym wyglądają tak:
public - Wszystko w klasie, co następuje po tym słowie kluczowym, jest dostępne dla wszystkich.
protected - Wszystko w klasie, co następuje po tym słowie kluczowym, jest dostępne tylko we wnętrzu tej klasy i we wnętrzu jej klas pochodnych (zdziedziczonych z niej).
private - Wszystko w klasie, co następuje po tym słowie kluczowym, jest dostępne tylko we wnętrzu tej klasy.
W praktyce wygląda to tak:
irb(main):001:0> class Bazowa irb(main):002:1> public irb(main):003:1> def initialize irb(main):004:2> @pole1 = 100 irb(main):005:2> @pole2 = 200 irb(main):006:2> @pole3 = 300 irb(main):007:2> end irb(main):008:1> attr_accessor :pole1 irb(main):009:1> def pokaz_pola irb(main):010:2> puts "Pole1: #{@pole1}" irb(main):011:2> puts "Pole2: #{@pole2}" irb(main):012:2> puts "Pole3: #{@pole3}" irb(main):013:2> end irb(main):014:1> private irb(main):015:1> attr_accessor :pole2 irb(main):016:1> protected irb(main):017:1> attr_accessor :pole3 irb(main):018:1> end => nil irb(main):019:0> bazowa = Bazowa.new => # irb(main):020:0> puts bazowa.pole1 100 => nil irb(main):021:0> puts bazowa.pole2 NoMethodError: private method 'pole2' called for # from (irb):21 irb(main):022:0> puts bazowa.pole3 NoMethodError: protected method 'pole3' called for # from (irb):22 irb(main):023:0> bazowa.pokaz_pola Pole1: 100 Pole2: 200 Pole3: 300 => nil irb(main):024:0> class Pochodna Bazowa irb(main):025:1> def pokaz_pola irb(main):026:2> puts @pole1 irb(main):027:2> # puts @pole2 irb(main):028:2* puts @pole3 irb(main):029:2> end irb(main):030:1> end => nil irb(main):031:0> pochodna = Pochodna.new => # irb(main):032:0> pochodna.pokaz_pola 100 300 => nil
Generalnie jednak, przynajmniej dla prostych przykładów, domyślny poziom dostępu jest zupełnie satysfakcjonujący.
No, uff… Najnudniejszą część mamy za sobą - poznaliśmy wszystkie podstawowe konstrukcję językowe, możemy więc przystąpić do pisania prawdziwych aplikacji - już w odcinku numer 4. Jeśli ktokolwiek nauczy się czegokolwiek z tego tutoriala, to w którejś z kolejnych części spróbujemy zbudować coś również w Rails + Ajax. Do zobaczenia.
Dodaj do del.icio.us | Dodaj do wykop.pl
Komentarze ():
dick, 16 luty 2006, 10:02 pm
Wszystko fajnie, ale czy mógłbyś jakoś sprawić (najlepiej lekko oddzielić kolorami) żeby część wypluwana przez interpreter była odróżnialna od wpisywanej przez użytkownika? Łatwiej by mi się śledziło przykłady.
pwa, 27 marzec 2006, 11:03 pm
tutorial jest spoko
putanie:
czy mozna nazywac modół - klasa abstarakcyjna??
sztywny, 28 marzec 2006, 5:03 am
Nie, choć w zastosowaniu nr. 1 moduł faktycznie zachowuję się trochę jak klasa abstrakcyjna - tyle że dziedziczyć w Rubym można tylko z jednej klasy, a wmiksować można wiele modułów.
W zastosowaniu nr. 2 główną rolą modułu jest przydzielenie przestrzeni nazw i tu zupełnie nie miałoby takie określenie sensu.
Uzytkownik, 2 kwiecień 2006, 5:04 pm
1. “Tylko Chuck Norris rozumie perla” (czyli programuje)
2. Moduł przypomina raczej połączenie interface’u z klasą czysto statyczną
3. Akurat wieludziedziczenie uważam za jedną z lepszych cech C++ - nie trzeba pisać różnego typu proxy itp.
Pozdrawiam
sztywny, 2 kwiecień 2006, 6:04 pm
Nikt nie mówi, że dziedziczenie z wielu klas jednocześnie jest złą cechą, ale jest to tylko jedno z rozwiązań, niekoniecznie i nie zawsze idealne.
papaj, 28 maj 2006, 11:05 am
Dlaczego w dziedziczonej klasie Pochodna wypisuje @pole2 skoro jest to private?
sztywny, 28 maj 2006, 12:05 pm
instancja_pochodnej.pole2 nie oznacza bezpośredniego dostępu do składowej @pole2 klasy Pochodna, tylko wywołanie metody o nazwie “pole2″ pełniącej rolę “gettera” z C++/Java/C#/łotewa.
luk4sz, 3 marzec 2007, 10:19 pm
“irb(main):024:0> class Pochodna Bazowa”
powinno byc: “class Pochodna
luk4sz, 6 marzec 2007, 3:23 pm
…juz rozumiem-stronka zjada <
teraz dobrze: class Pochodna
Micki21, 21 maj 2007, 8:15 pm
Witam mam pytanie czy w ruby jest jakaś komęda żeby zapisać program na dysku albo coś takiego ???????
wro, 4 sierpień 2007, 5:37 pm
Przydalo by sie zeby zrodla do sciagniecia byly dostepne pod wydrukiem :)
Początkujący, 11 luty 2008, 7:03 pm
Gdy klikam enter po:
irb(main):003:2> @odglos = “Hau hau!”
Zamyka się interpreter ;/ Co robić?
emilekm, 22 luty 2009, 2:56 pm
Truuudne:(((((((:((((
def h
puts “Nie umiem:(”
end
h
Nie umiem
->nil
emilekm, 22 luty 2009, 3:09 pm
i dlaczego nie ychodzi mi 1+1?
mc, 12 grudzień 2009, 2:42 am
…
Widzimy dwie nowe konstrukcje. Pierwsza z nich “class Pochodna
irb(main):001:0> module ModulBazowy
irb(main):002:1> def hello
irb(main):003:2> puts “Heyah!”
…
Czegoś tutaj chyba brakuje.