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.

Skomentuj