Groovy: Kategorien

Aus Wikibooks

In Vordefinierte Methoden haben wir Ihnen gezeigt wie Groovy es schafft, vorhandenen Java-Klassen und -Interfaces gewissermaßen von außen zusätzliche Funktionalität zu verleihen. Diesen Mechanismus können Sie in Ihren Programmen ihn ähnlicher Weise nutzen. Der Schlüssel hierzu ist die vordefinierte Methode use(), der bisweilen auch fälschlich als use-Anweisung bezeichnet wird. Das geht folgendermaßen:

  1. Bauen Sie eine Klasse mit statischen Methoden nach dem Muster, wie wir es in Vordefinierte Methoden für die Klasse DefaultGroovyMethods erläutert haben. Sie muss weder ein bestimmtes Interface implementieren noch von einer bestimmten Klasse abgeleitet sein. Der erste Parameter bestimmt den Typ, für den die Methode gelten soll, und die übrigen Parameter nehmen die effektiven Argumente des Methodenaufrufs entgegen. Das ist die Kategorieklasse.
  2. Kapseln Sie Ihr Programm ‒ oder den Teil des Programms, in dem die von Ihnen definierten vorgegebenen Methoden gelten sollen ‒ in einer Closure, die Sie zusammen mit Ihrer Kategorienklasse an die use()-Methode übergeben.

Genau wie die Groovy-eigenen vordefinierten Methoden kommen auch die Kategorienmethoden nur ins Spiel, wenn es keine passenden Methoden bei den Objekten selbst gibt, an denen sie ausgeführte werden sollen.

Im folgenden Beispiel wollen wir die Funktionalität einer einzelnen vorhandenen Klasse aufbessern. Im Kapitel Objekte hatten wir eine dünne Kapsel um die Calendar-Klasse gebaut, die einen bequemen Zugriff auf dessen einzelne Felder im Stil von Groovy-Properties ermöglicht. Als Alternative können wir auch eine Kategorienklasse mit vordefinierten Methoden für den Typ Calendar anlegen, die dasselbe leisten. Der Vorteil ist, dass wir dadurch eine Reihe von Methoden zur Verfügung haben, die wir direkt an einem Calendar-Objekt anwenden können, auch wenn dieses beispielsweise von einer anderen Klasse instanziiert wird, die von unserer Erweiterung nichts wissen kann.

class KalenderDatumKategorie {
  // Eine Property auslesen
  static int get(Calendar calendar, String propertyname) {
    int wert = calendar.get(calendarConst(propertyname))
    Nullrelativen in 1-relativen Monat umwandeln
    propertyname=='month' ? wert+1 : wert
  }
  // Eine Property setzen
  static void set(Calendar calendar, String propertyname, int wert) {
    // 1-relativen in null-relativen Monat umwandeln.
    if (propertyname=='month') wert--
    calendar.set(calendarConst(propertyname),wert)
  }
  // Die toString()-Methode von Calendar überschreiben
  static String toString(Calendar calendar) {
    calendar.identity { "${dayOfMonth}.${month}.${year}" }
  }
  // CamelCase in Konstantennamen umwandeln
  private static calendarConst(String propertyname) {
      def constname = propertyname.replaceAll('[A-Z]','_$0').toUpperCase();
      Calendar."$constname"
  }
}

Und so probieren wir die Kategorieklasse aus:

use (KalenderDatumKategorie) {
  def cal = new GregorianCalendar()
  println cal.toString()
  cal.dayOfMonth ++ // Um einen Tag erhöhen
  println cal.toString()
}

Wenn Sie dieses Skript zufällig am Silvesterabend aufrufen, sieht die Ausgebe korrekt so aus:

31.12.2007
1.1.2008

Das heißt also, dass wir unsere eleganten Zugriffsmöglichkeiten der Calendar-Klasse direkt zuordnen und gar keine Kapselung mehr brauchen.

Warnung: Man vergisst es zwar leicht, aber außerhalb des Einflussbereichs von Groovy haben die durch Kategorien zugeordneten Methoden keine Gültigkeit mehr. Deswegen müssen wir in dem Beispiel auch println cal.toString() schreiben. Ohne .toString() würde das Calendar-Objekt als Ganzes an System.out übergeben werden. Aber dies ist ein ganz normale Java-PrintStream, der nichts von Groovys Kategorien weiß und deshalb nur das normale toString() des Calendar aufrufen kann.