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<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.umgebungMein 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() > 10true 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)10 Zahl 5 Text x unbekannt
Merke
delegateund Closures erlauben DSLs wie Gradle oder JenkinsmethodMissing,propertyMissingundmetaClasserweitern Klassen zur Laufzeit- Categories (
use) undExpandosind leichtgewichtige Alternativen @CompileStaticbringt Typprüfung und Tempo
Aufgabe
Baue eine kleine DSL für Einkaufslisten: einkauf { artikel "Milch", 2 }.