czwartek, 18 października 2007

Ale co to właściwie znaczy, dynamiczny?

W definicji języka Ruby, podanej na jego stronie domowej, już jako pierwsze słowo pojawia się "dynamiczny". Dla kogoś, kto dopiero poznaje świat Ruby, nie do końca może być jasne, co tak naprawdę znaczy jego dynamiczność. Czym się objawia, skoro wymieniana jest jako sztandarowa jego cecha?

Dynamikę można rozumieć jako ruch czy przeobrażenie - i tego wytłumaczenia chciałbym tu użyć. Ruby, podobnie jak Java, jest językiem obiektowym. Inaczej jednak niż w Javie, raz utworzony obiekt może zmieniać nie tylko swój stan, ale i zestaw komunikatów, które przyjmuje. Prościej i dosadniej rzecz ujmując, w Javie jedyne, co może zmienić się w obiekcie to zawartość jego pól. Natomiast w Ruby, oprócz tego możliwe jest dynamiczne (czyli w trakcie wykonywania programu) dokładanie nowych oraz zmiana czy usuwanie istniejących metod i pól.

Taka funkcjonalność języka daje programiście duże możliwości. Bardzo chętnie skorzystali z nich między innymi twórcy frameworku Rails. Przykładowo w warstwie modelu (bazy danych), definiując własną klasę dziedziczącą z ActiveRecord::Base, otrzymujemy za darmo cały zestaw "magicznych" metod. Dlaczego magicznych? Ponieważ można by długo i nieskutecznie szukać np. źródła dla metody find_by_author klasy Book (reprezentującej dane z tabeli book w bazie danych - zakładamy tu że tabela ta ma kolumnę author). A mimo to można z niej od razu skorzystać:
my_favourite = Book.find_by_author "Terry Prattchet"
Jak to możliwe? Oto co dzieje się w momencie wywołania takiej metody:
  1. do obiektu Book wysyłany jest komunikat find_by_author (to po prostu określenie wywołania metody, używane w świecie Ruby) z parametrem "Terry Prattchet"
  2. obiekt Book nie posiada żądanej metody, więc wysyłany jest komunikat method_missing (to standardowa metoda klasy Object, z której dziedziczą wszystkie inne klasy w Ruby; jej domyślna implementacja rzuca wyjątek NoMethodError)
  3. wykonywana jest method_missing przeciążona w klasie ActiveRecord::Base - i tu dynamika Ruby wykorzystana jest w pełni, bo na podstawie informacji, że próbowano wykonać metodę o nazwie zaczynającej się od "find_by" oraz tego, że klasa Book ma pole "author", w locie tworzona jest nowa metoda find_by_author, której implementacja to wywołanie gotowej uniwersalnej metody ActiveRecord::Base.find z odpowiednimi parametrami
Prawda, że pięknie? Wywołujemy nieistniejącą metodę i w efekcie nie tylko nie dostajemy błędu, ale jeszcze metoda sama się tworzy po czym wykonuje dając poprawny wynik! :-)

Co najważniejsze, nie trzeba wcale sięgać po własne konstrukcje z dziedziny metaprogramowania czy Domain-Specific Languages, aby używać i korzystać w pełni z dynamicznej natury Ruby. Oto prosty przykład, klasa Book, tym razem niezwiązana z ActiveRecord::Base:
class Book
attr_accessor :author, :title
end
A oto jej odpowiednik w języku Java:
class Book {
private String author;
private String title;
public String getAuthor() { return author; }
public void setAuthor(String value) { author = value; }
public String getTitle() { return title; }
public void setTitle(String value) { title = value; }
}
Wbrew pozorom, attr_accessor nie jest jakimś udziwnionym słowem kluczowym do definiowania pól razem z metodami dostępowymi. To najzwyklejsza metoda z modułu Module, dostępna dla każdego obiektu w Ruby. Rezultat jej wykonania to utworzenie w takim obiekcie pola o zadanej nazwie oraz metod: pobierającej i ustawiającej wartość tego pola. Dokładnie to samo można uzyskać w sposób zbliżony do Javy:
class Book
def author
@author
end

def author= value
@author = value
end

def title
@title
end

def title= value
@title = value
end
end
Osobiście nie mam wątpliwości, który sposób jest przyjemniejszy w użyciu. Ruby pozwala na pisanie szybciej i wygodniej, więc dlaczego z tego nie skorzystać?

Brak komentarzy: