Akcesory w Javie, Pythonie, Ruby i PHP5
Posted by Jarosław Zabiełło Tue, 07 Feb 2006 22:25:00 GMT
Java, język który dobył sobie silną pozycję na rynku korporacyjnym, jest od jakiegoś czasu pod obstrzałem krytyki z różnych stron. Wyszła cała seria książek krytykujących model obiektowy oraz metodologie promowane przez Javę (od najsłynniejszej Beyond Java po Bitter Java, Bitter EJB, czy inne)
Nie umniejszając zalet Javy, jej krytycy wytykają jej niepotrzebną nadmiarowość kodu, ociężałośći i małą zwrotność, która powoduje że język ten słabo nadaje się do modnej ostatnio metodologii _agile programming. _ Aby lepiej ten problem zobaczyć, przyjrzyjmy się typowej praktyce programistów Javy. Tekst ten zainspirowany jest niedawną dyskusją jaka miała miejsce w jednym z blogów mojego kolegi.
Czym są akcesory?
Akcesory (lub inaczej: gettery i settery) to slangowe okreslenie metod jakie używa obiekt do odczytu i modyfikacji swoich atrybutów. Są one tak nagminnie używane przez programistów Javy, że niektóre edytory (np. Eclipse) zostały nawet wyposażone w makra do automatycznego ich generowania.
Dorzucanie nieużywanego kodu “na wszelki wypadek”...
Nagminna (wśród programistów Javy) praktyka dodawania akcesorów do atrybutów klasy pachnie jakimś antipatternem i są głosy, które używanie akcesorów nazwyają wprost złą praktyką . Jest w tym trochę racji. Ale, jak to poniżej wykażę, jest to pewnego rodzaju kompromis w związku ze słabym modelem obiektowy Javy.
Dlaczego programiści Javy generują akcesory dla każdego atrybutu nawet, jak nie przewidują potrzeby ich używania? Otóż czynią to “na wszelki wypadek” bo nie wiedzą, czy w przyszłości nie będzie im to potrzebne…
Weźmy np. taki kod (z powodu kolejnego ograniczenia Javy nie można w jednym pliku trzymać więcej, niż jednej klasy; tu dla krótkości umieszczam kod z obu plików razem)
public class First {
public String msg = "hello";
}
public class Second extends First {
public static void main(String[] args) {
First obj = new First();
System.out.println(obj.msg);
}
}Jak widać, w Javie można sięgać do atrybutu w sposób bezpośredni.
Wyobraźmy sobie jednak, że projekt nam się rozrósł i mamy już całkiem sporo kodu oraz sporo plików (pomnażanych wydatnie przez javowe ograniczenia co do ilości klas mogących wystąpić w pliku). Teraz przychodzi polecenie aby w momencie odczytu i zapisu atrybutu coś dodatkowego wykonać. (Np. niech to będzie logowanie informacji o takim zdarzeniu, albo zablokowanie możliwości modyfikacji treści atrybutu. Wszystko jedno co)
Mamy zatem pierwszy problem. Trzeba w tych wszystkich milionach miejsc, gdzie odwoływaliśmy się bezpośrednio do atrybutów, wymienić kod…
Właśnie dlatego, aby takich niespodzianek uniknąć w przyszłości, programiści Javy dorzucają dodatkowe metody opakowujące odczyt i zapis atrybutów. Eclipse upraszcza ten proces zwalniając programistę od ręcznego wpisywania tego kodu.
Klasa First po zmianie:
public class First {
public String msg = "hello";
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}Wydaje się, że problem jest rozwiązany. Ale to dopiero początek innych problemów.
Jeśli bowiem, automatycznie dorzucamy te metody do wszystkich tysięcy atrybutów nowo tworzonych klas, to pierwszą rzeczą którą zauważamy, jest nagłe “spuchnięcie kodu”. Mamy kupę nieużywanych linii kodu, z których prawdopodobnie większość nie będzie nigdy używana!
Ale to nie wszystko. Załóżmy, że stajemy przed potrzebą zmiany nazwy atrybutu. Oczywiście, możemy to zmienić w jednym miejscu (w ciele akcesora), ale wtedy wprowadzamy chaos w pozostałej części odnośnie… nazw (atrybut “msg” zmieniony na “title” trochę głupio wygląda z akcesorami o nazwie “getMsg” i “setMsg”) Musimy przekopać się przez miliony miejsc w kodzie i pozmieniać nazwy starym wywołaniom.
Zobaczmy jak ta sytuacja wygląda w językach dynamicznych.
Python
Python (podobnie jak Java) pozwala na bezpośredni dostęp do atrybutów klasy. A co w sytuacji kiedy chcemy opakować atrybut akcesorami? Nic prostszego. Python pozwala na dodanie akcesorów wtedy, i tylko wtedy, kiedy są potrzebne. Na dodatek czyni to w sposób całkowicie przezroczysty dla pozostałej części kodu. Programista Javy może sobie o tym tylko pomarzyć.
class X(object):
def get_msg(self):
return self.__msg
def set_msg(self, val):
self.__msg = val
msg = property(get_msg, set_msg)
obj = X()
obj.msg = "hello"
print obj.msgPython pozwala także związać z dowolną metodą, atrybutem, klasą czy modułem docstring, czyli tekst z dokumentacją, objaśnieniem itp. To jedna z genialnych cech Pythona specjalnie pomyślana dla leniwych programistów, którym nie chce się pisać dokumentacji. ;)
Ruby
W języku Ruby z definicji nie ma żadnej możliwości dostępu do atrubutów klasy inaczej jak przez akcesory. Odpada więc problem zapominania aby je dodać. Ruby jednak narzuca nazwy dla akcesorów (mają nazwę taką jak atrybut!) i całość wygląda tak, jakby operowano bezpośrednio na atrybucie.
class X
def msg
@msg
end
def msg=(val)
@msg = val
end
end
obj = X.new
obj.msg = "hello"
puts obj.msgclass X
attr_accessor :msg
endIstnieją także oddzielne skróty dla getterów i setterów. No i można po przecinku dodać akcesory dla całej grupy atrybutów.
PHP5
PHP w wersji 5 ma przebudowany model obiektowy od podstaw. Twórcy języka PHP nie starali się poprawiać modelu obiektowego PHP4 (był on tak zły, że prościej było im napisać go od nowa). W nowym PHP5 mamy już możliwość przezroczystego dodania akcesora.
<?php
class X {
private $attributes = array('msg'=>null);
private function __get($attrname) {
return $this->attributes[$attrname];
}
private function __set($attrname, $val) {
$this->attributes[$attrname] = $val;
}
}
$obj = new X();
$obj->msg = "hello";
print $obj->msg;
?>Składnia może nie jest tak prosta jak w Ruby, ale (przynajmniej w tym miejscu), PHP5 zachowuje się tu sensownie i unika dylematów Javy.


Kanały IRC![[Dilber w Onecie]](/images/larry.png)


nie zgadzam się z autorem w kwestii wad akcesorów javowych. Fakt jest faktem że nie można ich “przezroczyście” dodać, lecz nie jest to problemem, gdyż zaawansowane narzedzia (Eclipse, NetBeans) mają wsparcie dla refaktoryzacji kodu i pozwalają załatwić to kilkoma kliknięciami Zaś samo przezroczyste modyfikowanie akcesorów jest dostępne także w VB
To jest problemem, bo albo dodajesz wszędzie akcesory “na wszelki wypadek” i masz kupę nadmiarowego, nieużywanego kodu, z którego większość prawdopodobnie nigdy nie będzie użyta. Albo ich nie dodajesz i wtedy jakakolwiek zmiana (np. nazwy czy typu) atrybutu klasy wymaga wymiany kodu we wszystkich milionach innych miejsc, które gdzieś z tego korzystają. Poleganie w 100% na zachowaniu IDE to raczej monkey patching, a nie rozwiązanie problemu. A jeśli część kolejnych bibliotek jest już skompilowana i dostępna tylko w tej formie? Wtedy nawet NetBeans nic nie pomoże.
Niedawno Sun zatrudnił programistów JRuby. Myślę, że rola Javy może tylko maleć, gdyż JRuby posiada niezrównanie bardziej prostą i elastyczną składnię, a ma taki sam dostęp do wszystkich, dopracowanych przez lata, bibliotek Javy. Bo tak naprawdę, to nie chodzi o (toporną) składnię Javy ale o jej biblioteki. I jeśli JRuby da nam ten sam dostęp przy znacznie mniejszym nakładzie kodowania, to czemu nie?
Hmm, generalnie zgadzam się z artykułem, ale nie przesadzajmy i nie demonizujmy Javy. Nie wiem czy wiecie, ale w najnowszej wersji JDK, problem metod dostępowych będzie można załatwić za pomocą adnotacji (metadanych). Osobiście bardzo lubię Pythona i uważam, że Java sporo się uczy od języków dynamicznych, ale jest to tak naprawdę niewielki ułamek w porównaniu do tego, w czym naśladują Javę np. Ruby lub Python. Często dochodzą powoli dopiero do rozwiązań, jakie w Javie funkcjonują od dawna. Zresztą dobra składnia języka to świetny punkt wyjścia i podstawa, ale to dopiero początek dłuuugiej drogi rozwoju.
Oj muszę dopisać, bo zapomniałem: Czemu w Rubym i Pythonie listing kodu jest pokazany ze ślicznym kolorowaniem składni, a w Javie i PHP surowym tekstem, czyżby taka mała propagandka? ;)
Ta aplikacja koloruje tylko format Rubiego, YAML i XML. Python jest tu kolorowany jako Ruby.
Słusznie przyznajesz, że Python i Ruby mają lepszą składnię, bo mają. Java ma tylko dojrzalsze zaplecze bibliotek, zoptymalizowaną JVM, kupę książek i rozbudowaną teorię. Ale to nic dziwnego, bo to efekt milionów dolarów pompowanych przez lata przez duże korporacje. Na pewno nie wieku. bo Java powstała w 1994 r roku. Ruby jest tylko rok młodszy. A Python jest starszy od obu, bo powstał w 1991 r. Hehe, nawet JVM Java zerżnęła z języka dynamicznego (SmallTalk). Java ostatnio usiłuje trochę się modernizować, ale wiele osób uważa, że jej czas się kończy i że nadchodzi era wysokopoziomowych języków dynamicznych. Wielcy w branży nie są w stanie już ignorować tego trendu. M$ zaczął jawnie wspierać Pythona (IronPython). A Sun po początkowym wsparciu dla PHP zatrudnił niedawno developerów projektu JRuby.
Ruby w ogóle jakoś dziwnym trafem dosyć łatwo pozyskuje sobie programistów Javy, którzy potem stają się jego wielkimi zwolennikami. :)
Autor artykułu nie ma racji co do accesorów javy – tak powinno się właśnie programiwać i jest to dobra praktyka.
Moim zdaniem przykład który pokazano z rubym jest właśnie dziwny, ale to tak trochę o mówieniu wyższości świąt Bożego Narodzenia nad świętami Wielkiej Nocy.
A co do świetlanej przyszłości Rubiego (JRuby) to jest tak samo jak z CORBA – “zawsze będzie miała świetlaną przyszłość” – nie bądź naiwny – jeśli Sun coś robi to robi to wyłącznie dla javy… (i dla siebie, ale to chyba oczywiste).
W Javie może uzyskać podobny efekt co w Pythonie i Rubym przez AOP.
Też lubię rubina – choć najwięcej pisze w Javie :) .