Groovy: Das Expando
In Closures haben wir bereits gezeigt, wie sich mit in einer Map gespeicherten Closures bereits fast ein komplettes Objekt zaubern lässt. Ein Objekt ist ja eigentlich nicht viel Anderes als ein Behälter für Code (Methoden) und Daten (Felder). Es gibt da nur ein Problem: die Closures werden zwangsläufig in einem anderen Kontext als dem dieser als Quasi-Objekt dienenden Map definiert und "sehen" damit nicht den Inhalt der Map, können also nicht direkt auf die eigenen Member zugreifen. Und das würde man sich bei einem richtigen Objekt schon wünschen.
Die Groovy-Standardbibliothek enthält eine Klasse Expando, die genau dieses leistet: Sie ist ein Behälter für Closures und andere Daten, und der Inhalt des Expando wird automatisch zum Namensraum der zugeordneten Closures. Wir können das interaktiv ausprobieren:
groovy> x = new Expando( groovy> feld: "Feld im Expando", groovy> methode1: { "Methode 1 - "+feld }, groovy> methode2: { println "Methode 2 - "+methode1() } groovy> ) groovy> x.methode2() Methode 2 - Methode 1 - Feld im Expando
Wir weisen dem Expando einen String als Property feld zu sowie zwei Closures methode1 und methode2. Die Closure methode2 ruft methode1 auf und diese referenziert feld auf. Das Entscheidende hier ist, dass die Referenz von methode2 auf methode1 und die Referenz von methode1 auf feld wie lokale Referenzen in den Namensraum der Closure, also das umgebende Skript aussehen. Trotzdem finden sie die dem Expando zugeordneten Elemente.
Der Trick besteht darin, dass sich das Expando beim Zuordnen der Closures bei diesen selbst als Delegate einträgt. Daher wird die Suche nach einer Property, wenn sie in der Closure selbst oder beim Eigentümerobjekt (hier also dem Skript) nicht fündig wird, automatisch im Expando fortgesetzt. Und somit werden feld und methode1 auch mit unqualifizierten Referenzen gefunden. Zweitens ist die Methode invokeMethod() so überschrieben: Immer, wenn bei eine aufzurufende Methode nicht gefunden wird, schaut sie nach, ob dem Expando eine passende Closure mit dem Namen der Methode zugeordnet ist. Und wenn sie eine solche Closure findet, ruft sie diese anstelle der Methode auf.
Expandos können also im Groovy-Kontext fast wie Objekte verwendet werden, denen man Felder und Methoden zur Laufzeit zuordnen kann. Sie sind damit wie geschaffen für generativ erzeugte Ad-hoc-Objekte wie Datencontainer, Transferobjekte usw.
Zu beachten ist, dass die Auflösung der Referenzen im Expando immer nachgelagert ist. Wenn es im Eigentümerobjekt bereits ein Feld feld oder eine Methode methode1 gäbe, würden diese anstelle der gleichnamigen Elemente des Expando gefunden werden. Insofern ist etwas Vorsicht bei der Anwendung von Expando-Objekten angebracht.
Das Expando eignet sich gut als Dummy-Objekt für Testzwecke oder für sonstige Einmal-Objekte, da Sie nicht eigens eine Klasse definieren müssen. Allerdings ist die Verwendbarkeit etwas eingeschränkt, da Expandos keine Vererbung und kein Überladen von Methoden kennen, keine Interfaces implementieren können und auch von Java-Klassen aus nicht sinnvoll nutzbar sind. In vielen Fällen bietet es sich an, anstelle von Expandos einfache Closures oder Maps von Closures einzusetzen, mit denen sich sogar beliebige Interfaces implementieren lassen (siehe Closures). Demgegenüber haben Expandos den Vorzug, dass sie eine Art lokalen Status kennen, auf den die Closure-Methoden direkt zugreifen können.