03 czerwca 2009

Kontynuacja relacji z rozdziału 3. "Groovy for the Java eyes" z "Programming Groovy"

Skończyłem moją ostatnią relację z lektury "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama na stronie 32. rozdziału 3. "Groovy for the Java eyes" (zajrzyj do Rozpoczynam lekturę "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama).

Na kolejnych stronach wciąż zagłębiamy się w te cechy Groovy, które pozwalają programistom Java uzmysłowić sobie paradoks swojej produktywności - pisaliśmy wiele, szybciej, ale w dużej mierze niepotrzebnie. Akurat ten rozdział dostępny jest bezpłatnie ze strony książki (na dole w sekcji Contents and Extracts znajduje się odnośnik do PDFa), więc można w ten sposób wyrobić sobie własny pogląd na sprawę.

Przykłady cech Groovy, które programiści javowi mogą z początku nazwać udziwnieniami mogą być:
  • Słowo kluczowe return jest opcjonalne i przez to prawie nieużywane, podobnie średnik
  • Metody i klasy są domyślnie publiczne
  • Operator bezpiecznego odczytu ?. wykona metodę następującą po nim, jedynie jeśli poprzedzająca referencja nie jest null
  • Wprowadzono mechanizm nazwanych parametrów inicjalizacyjnych, więc POJO (a teraz POGO - ang. Plain Old Groovy Object) konstruujemy z konstruktorami, które akceptują mapę parametrów i ich wartości
  • Obsługa wyjątków jest opcjonalna - bez względu na rodzaj - kontrolowane czy nie, nieprzechwycone idą wyżej, do wywołującego
  • this w bloku statycznym jest wskazaniem na obiekt typu Class, więc możemy stworzyć ciąg wywołań statycznych (zwanych fluent API)
Deklarując właściwość w klasie Groovy dostarczy dla nich metodę odczytu i zapisu (ang. getter/setter) - podobnie jak z domyślnym konstruktorem w Javie, jeśli nie zdefiniowaliśmy żadnego. Deklaracja właściwości jako final jest wskazaniem dla Groovy, że ma "dołożyć" wyłącznie metodę odczytu. Właściwość jest wciąż do zmiany, ale wyłącznie z innych metod klasy, do której należy. Deklaracja właściwości może być nietypowana (bez określenia typu) z def albo podobnie jak w Javie ze wskazaniem typu.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_13)
Type 'help' or '\h' for help.
---------------------------------------------------------------------------------------------------------------
groovy:000> class Pracownik
groovy:001> {
groovy:002> def imie, nazwisko
groovy:003> final def pewnaDanaStala
groovy:004> }
===> true
groovy:000> p = new Pracownik(imie: 'Jacek', nazwisko: 'Laskowski')
===> Pracownik@9abce9
groovy:000> p.imie
===> Jacek
groovy:000> p.nazwisko
===> Laskowski
groovy:000> p.pewnaDanaStala = 'Zabronione, ale próbujemy'
ERROR groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: pewnaDanaStala for class: Pracownik
at Pracownik.setProperty (groovysh_evaluate)
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> quit
Właściwości klasy można odczytywać bez konieczności uruchamiania metod odczytu, a po prostu wskazać ją po kropce. Uwaga na class, która odpowiada metodzie getClass(), ale dla pewnej grupy typów, np. java.util.Map czy budowniczych (ang. builders) w Groovy jest niczym specjalnym, np. w mapie jest traktowane jako...klucz. Zaleca się używanie metody getClass() zamiast class, gdzie interesuje nas dostęp do typu java.lang.Class.

Groovy pozwala na deklarację opcjonalnych parametrów wejściowych metody/konstruktora, jednakże muszą one być końcowymi parametrami. Ostatni parametr wejściowy metody będący tablicą jest również traktowany przez Groovy jako opcjonalny. Tym samym programiści C++ mogą wrócić do swoich przyzwyczajeń, których wyzbywali się przez tyle lat programowania w Javie.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_13)
Type 'help' or '\h' for help.
-------------------------------------------------------------------------------------------
groovy:000> def wypisz(nazwa, String[] tablica) { println "$nazwa - tablica: $tablica" }
===> true
groovy:000> wypisz 'Tablica podana', 'Pierwszy parametr', 'drugi parametr tablicy'
Tablica podana - tablica: [Pierwszy parametr, drugi parametr tablicy]
===> null
groovy:000> wypisz 'Brak tablicy'
Brak tablicy - tablica: []
===> null
groovy:000> def dajPodwyzke(kwota=10000) {
groovy:001> println "Masz podwyzke o $kwota PLN"
groovy:002> }
===> true
groovy:000> dajPodwyzke()
Masz podwyzke o 10000 PLN
===> null
groovy:000> dajPodwyzke(300)
Masz podwyzke o 300 PLN
===> null
groovy:000> quit
Realizacja interfejsu to często tworzenie metod-zaślepek, gdzie jedynie pojedyncza metoda interfejsu nas interesuje, a parametry wejściowe często wcale. Znany i (nie)lubiany przypadek to klasy wewnętrzne do obsługi zdarzeń w GUI. Jak to ujął Venkat: "Groovy brings a charming idiomatic difference here" (strona 39 u dołu). Wystarczy zdefiniować domknięcie i zrzutować na odpowiedni typ konstrukcją as [typ], np.
 button.addActionListener(
{ JOptionPane.showMessageDialog(frame, "You clicked!") } as ActionListener
)
Zadaniem dla wytrwałych i dociekliwych byłoby napisanie podobnej konstrukcji w Javie.

Możemy również tak:
 displayMessage = { JOptionPane.showMessageDialog(frame, "You clicked!") }
button.addActionListener(displayMessage as ActionListener)
W końcu Groovy udostępnia domknięcia - bloki z kodem do wykonania - i możemy je traktować jak zmienne.

Co powoduje, że realizacja interfejsów w Groovy staje się jeszcze bardziej pikantna, to opcjonalna implementacja metod interfejsu (w przypadku próby wykonania niedostarczonej metody z interfejsu Groovy zgłosi NullPointerException!) oraz ich definicja w postaci mapy - nazwa metody i domknięcie - jej ciało.
 handleFocus = [
focusGained : { msgLabel.setText("Good to see you!") },
focusLost : {msgLabel.setText("Come back soon!") }
]
button.addFocusListener(handleFocus as FocusListener)
Realizacja interfejsu może być dynamiczna, tzn. poznamy realizowany interfejs w trakcie działania programu. Pomocne jest wykorzystanie metody asType().
 events = ['WindowListener', 'ComponentListener'] // listę budujemy dynamicznie, np. podane przez użytkownika

handler = { msgLabel.setText("$it") }

for (event in events) {
handlerImpl = handler.asType(Class.forName("java.awt.event.${event}))
frame."add${event}"(handlerImpl)
}
Nie powinniśmy mieć złudzeń - tyle, ile zabawy mamy podczas programowania, tyle będziemy mieli podczas utrzymania takiego kodu. To musi być niezwykle interesujące dla obu stron :)

Jako podsumowanie wiadomości nt. obsługi interfejsów w Groovy autor przedstawia kompletny przykład aplikacji okienkowej zbudowanej na bazie JFC/Swing API. Kompletny kod źródłowy tej i pozostałych programów dostępny jest pod adresem ze strony książki.

Groovy nie wymaga podania wyrażenia boolowskiego explicite, tzw. prawda Groovy (ang. Groovy truth). Referencja null to false, a nie null to...zależy. Jeśli dotyczy kolekcji, wtedy niepusta kolekcja daje true. Bardzo intuicyjnie będzie z innymi typami, np. Character to niezero, Enumeration to niepusta lista, Iterator podobnie, Map posiada przynajmniej jedną parę, itd. Warto zajrzeć do dokumentacji, aby...nie rozpisywać się (ja teraz, Ty w trakcie programowania w Groovy). Pora przetrawić to Groovy.