czwartek, 31 lipca 2008

Groovy w praktyce - part 5 - mapy 2/2

Pora wrócić do Groovy po drobnej przerwie.

Pewną szczególną cechą Groovy jest podejście do interfejsów w połączeniu z mapami i domknięciami. Tym razem włączam groovyConsole i...
interface Kaczka{
void kwa()
void lataj()
}

def PrawieJezioroLabedzie(Kaczka k){
6.times{k.kwa()}
}

def mapa = [ kwa:{println 'kwa'}, lataj: {println 'ja latam!'} ]
def kaczka = mapa as Kaczka

if(kaczka instanceof Kaczka){
PrawieJezioroLabedzie(kaczka)
kaczka.lataj()
}
Wynik:
kwa
kwa
kwa
kwa
kwa
kwa
ja latam!
To przecież jakaś herezja ! Dynamicznie tworzę instancję interfejsu za pomocą zawartości mapy. Jak to możliwe? W Javie od wersji 1.3 można dynamicznie tworzyć nowe implementacje klas i interfejsów! Jest to możliwe za pomocą Dynamic Proxy, łatwo można sprawdzić, że Groovy wykorzystuje ten mechanizm.
println java.lang.reflect.Proxy.isProxyClass(kaczka.class)
Wynik:
true

Wstrzykiwanie zależności, aspekty, zarządzane ziarna, binding, rmi ... wszystko to wykorzystuje dynamic proxy. Ma to swoje minusy. Proxy, jak przystało na twór dynamiczny, będzie wolniejszy od normalnej klasy. Jest to cena warta zapłacenia, jeśli chcesz stworzyć jakikolwiek zarządzany komponent to trudno nie wykorzystać proxy. .Net ma podobny twór o mniejszych możliwościach (bo m.in. w C# metody są domyślnie nie wirtualne, takiego wywołania nie można dynamicznie przechwycić, więc tylko niektóre metody proxy może implementować).

Można też rzutować pojedyncze domknięcie na interfejs
PrawieJezioroLabedzie({println "ćwir"} as Kaczka)
Wynik:
ćwir
ćwir
ćwir
ćwir
ćwir
ćwir

Wtedy wszystkim funkcją interfejsu zostanie przypisana jedna akcja. Co ciekawe niektóre z propozycji domknięć dla Java pozwalają na niejawne zamienianie domknięć na interfejsy. Jest to niebywale wygodne, gdy definiujemy zdarzenia np. w GUI.

Groovy sprawdza w trakcie wykonania typy obiektów dlatego poniższy kod jest nie poprawny.
PrawieJezioroLabedzie(mapa)
// powoduje
Exception thrown: groovy.lang.MissingMethodException: No signature of method: Script12.PrawieJezioroLabedzie() is applicable for argument types: (java.util.LinkedHashMap)

Jeśli jednak usunę informację o oczekiwanym typie z parametru funkcji, to funkcja nie będzie widzieć różnicy między klasą, a mapą i w obu przypadkach wykona się poprawnie.
def PrawieJezioroLabedzie(k){
6.times{k.kwa()}
}

PrawieJezioroLabedzie(mapa)
Wynik
kwa
kwa
kwa
kwa
kwa
kwa

Więcej w implementacja interfejsów w Groovy

Brak komentarzy: