EMZETT.
Login

Kurz: Closures mit delegate ermöglichen eigene Fachsprachen, wie man sie aus Gradle kennt:

Teil des Kurses Groovy

Kapitel 6 von 8 im Kurs Groovy. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Builder und DSLs

Closures mit delegate ermöglichen eigene Fachsprachen, wie man sie aus Gradle kennt:

class HtmlBuilder {
    StringBuilder sb = new StringBuilder()
    int tiefe = 0
    def methodMissing(String name, args) {
        def attribute = args.find { it instanceof Map } ?: [:]
        def inhalt = args.find { it instanceof Closure }
        def text = args.find { it instanceof String }
        def attr = attribute.collect { k, v -> " $k=\"$v\"" }.join()
        sb << "  " * tiefe << "<$name$attr>"
        if (inhalt) {
            sb << "\n"
            tiefe++
            inhalt.delegate = this
            inhalt()
            tiefe--
            sb << "  " * tiefe
        } else if (text) sb << text
        sb << "</$name>\n"
    }
}
 
def b = new HtmlBuilder()
b.html {
    body(class: "start") {
        h1("Hallo")
        p("Absatz")
    }
}
print b.sb

Ausgabe:

<html>
  <body class="start">
    <h1>Hallo</h1>
    <p>Absatz</p>
  </body>
</html>

Konfigurations-DSL mit delegate

class Konfig {
    String name
    List<String> pakete = []
    Map<String, String> umgebung = [:]
    void paket(String p) { pakete << p }
    void umgebung(String k, String v) { umgebung[k] = v }
}
 
def konfiguriere(Closure c) {
    def k = new Konfig()
    c.delegate = k
    c.resolveStrategy = Closure.DELEGATE_FIRST
    c()
    k
}
 
def k = konfiguriere {
    name = "Mein Projekt"
    paket "groovy"
    paket "spock"
    umgebung "MODE", "test"
}
println k.name
println k.pakete
println k.umgebung

Ausgabe:

Mein Projekt
[groovy, spock]
[MODE:test]

Metaprogrammierung

Mit metaClass ändert man Klassen zur Laufzeit – auch Java-Klassen:

String.metaClass.istPalindrom = { -> delegate == delegate.reverse() }
Integer.metaClass.verdopple = { -> delegate * 2 }
println "anna".istPalindrom()
println "hallo".istPalindrom()
println 21.verdopple()
 
class Dyn {
    def methodMissing(String name, args) { "dynamisch: $name(${args.join(', ')})" }
    def propertyMissing(String name) { "Property $name" }
}
def d = new Dyn()
println d.irgendwas(1, 2)
println d.foo
 
class Kategorie {
    static String exklamation(String s) { s + "!" }
}
use(Kategorie) { println "Hallo".exklamation() }
 
def obj = new Expando()
obj.name = "Mia"
obj.gruss = { "Hallo, ${delegate.name}" }
println obj.gruss()
 
println "text".metaClass.respondsTo("text", "size").size() > 0
println Integer.class.methods.size() > 10

Ausgabe:

true
false
42
dynamisch: irgendwas(1, 2)
Property foo
Hallo!
Hallo, Mia
true
true

Statisch typisieren

@CompileStatic prüft Typen zur Übersetzungszeit und macht Groovy so schnell wie Java:

import groovy.transform.CompileStatic
 
@CompileStatic
int summiere(List<Integer> zahlen) {
    int s = 0
    for (int z : zahlen) s += z
    return s
}
 
@CompileStatic
String beschreibe(Object o) {
    switch (o) {
        case Integer: return "Zahl ${o}"
        case String: return "Text ${o}"
        default: return "unbekannt"
    }
}
 
println summiere([1, 2, 3, 4])
println beschreibe(5)
println beschreibe("x")
println beschreibe(2.5)

Ausgabe:

10
Zahl 5
Text x
unbekannt

Merke

  • delegate und Closures erlauben DSLs wie Gradle oder Jenkins
  • methodMissing, propertyMissing und metaClass erweitern Klassen zur Laufzeit
  • Categories (use) und Expando sind leichtgewichtige Alternativen
  • @CompileStatic bringt Typprüfung und Tempo

Übungsaufgabe

Baue eine kleine DSL für Einkaufslisten: einkauf { artikel "Milch", 2 }.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Strings, Dateien und Datenformate

Weiter: Nebenläufigkeit und Java-Integration

Alle Kapitel: Groovy im Überblick