Posts mit dem Label DSL werden angezeigt. Alle Posts anzeigen
Posts mit dem Label DSL werden angezeigt. Alle Posts anzeigen

Donnerstag, 17. April 2008

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Artikel 'Entwurfsmuster: Flexibel programmieren mit dem Fluent Interface'

Fünf Jahre ist es her, dass ich das letzte Mal einen Artikel veröffentlicht habe. Man kommt ja zu nix mehr neben der Arbeit und dem Laufen. Vor einem halben Jahr hatte ich aber mal wieder Lust aufs Artikelschreiben, und das Ergebnis ist jetzt da, in der ix 05/2008 auf Seite 126: Entwurfsmuster: Flexibel programmieren mit dem Fluent Interface.

[Update 13.01.20082009: Den Artikel gibt's jetzt auch online im heise Developer.]

In dem viereinhalbseitigen Artikel entwickle ich schrittweise einen Builder (im Artikel "Erbauer" genannt) zu einem sog. Fluent Builder (im Artikel schreibe ich vom "Flüssigen Erbauer"), also einem Builder mit einem Fluent Interface. Der hat dann einige Vorteile, weil man z.B. eine Grammatik vorgeben kann und so eine domänenspezifische Sprache (DSL) ermöglicht. Dadurch wiederum wird der Builder sehr flexibel verwendbar, ohne dabei Inkonsistenzen zu erzeugen.

Die Bezeichnung Fluent Builder ist nur logisch, wenn man ein Fluent Interface mit einem Builder kreuzt; es haben schon andere ihr Ergebnis so bezeichnet. Allerdings habe ich da nicht geklaut: Der Artikel ist im September 2007 entstanden, wohingegen das erste Auftauchen eines Fluent Builders (der auch das meint, was er bezeichnet) von Dezember 2007 her datiert. Dort wird allerdings das gebaut, was ich in dem Artikel als "Zartschmelzende API" bezeichne und den Evolutionsschritt 2 von 4 kennzeichnet. Im Artikel gehe ich also noch etwas weiter in der Materie.

Der Fluent Builder ist in einem Projekt bei einem großen deutschen Versicherungskonzern entstanden. Dort hatte ich alle vier Entwicklungsstadien, die im Artikel beschrieben sind, durchexerziert, sehr zum Leidwesen meiner Kollegen dort. Auf deren Drängen bei meinen ersten flüssigen Gehversuchen ("Bernd, das ist inkonsistent, da sind die Verantwortungen nicht getrennt, das ist B****hit!") bin ich überhaupt erst soweit gekommen. Und als ich dann endlich soweit war, dass ich auf so ziemlich alle Fragen zur Flüssigen Entwicklung Antworten geben konnte - da wollte ich das nicht jedesmal neu erzählen und hab's aufgeschrieben. "Zweifel nähren das Denkvermögen" (Peter Schumacher), und daher ist dann irgendwann jener Artikel in der ix draus geworden. Speziellen Dank also an Matthias und Andreas für fruchtbare Reibereien.

Danke, Jorina, Stefan, Urs und Matthias, fürs Reviewen. Dank auch an meine Redakteurin Kersten Auel für die sehr gute Betreuung.

Errata
12.05.2008: In Listing 4 müsste es in der ersten Zeile statt
public class AbschnittWertZuStelleDescriptor {
lauten
public class AbschnittWertZuStelleDescriptor<Rueckgabe> {
Danke an Carsten Siedentop für sein Feedback.

Freitag, 13. April 2007

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Conway's Game of Life - Quiz-Lösung in Groovy

Ich habe bei Matt Secoskes GroovyQuiz mitgemacht und eine Lösung für Conways Spiel des Lebens in Groovy erstellt. Dabei bin ich testgetrieben vorgegangen und hab' viel Spaß mit Groovy gehabt.

Zuerst habe ich eine Version erstellt, die ein Universum Tick für Tick auf der Konsole ausgibt:

  XXX
XXX

X
X X
X X
X

Danach habe ich eine Oberfläche mit dem SwingBuilder erstellt:



Ich habe ein Jar per Gant erzeugt, mit welchem das Progrämmchen ausgeführt werden kann. Da ich selbst immer wieder an den Einstellschrauben des Programms rumgespielt habe, dachte ich mir, dass das vielleicht auch für den Benutzer nett sein könnte. Wenn man also nun das Groovy-Programm per
java -jar conwaysgameoflife-64.jar -h

aufruft, dann gibt's das hier:
Usage: java -jar conwaysgame.jar [arguments]
-b <blockSize> sets the length of a block in
pixel;
default value is 10
-h prints this help text
-p <pattern> sets the inital pattern (try
TOAD, BLINKER or GUN);
default value is [' ',' XXX ',' XXX ',' ']
-t <tickInterval> sets the tick interval in
milliseconds;
default value is 200
-v <view> sets the view to <swing>
or <console>;
default value is swing


Die Blockgröße, der Tickintervall und die Ausgabeart sollten klar sein. Das Muster, mit dem das Programm startet, ist dagegen schon interessanter: Ich habe drei typische Muster (Toad, Blinker und Gun) quasi vorinstalliert, und man kann deren Namen angeben.
java -jar conwaysgameoflife-64.jar
-p BLINKER

Zusätzlich dazu kann man aber auch alternativ ein eigenes Muster angeben, etwa
java -jar conwaysgameoflife-64.jar
-p "['XX ','XX ',' XX',' XX']"

Die Syntax dazu sollte allen bekannt vorkommen, die schonmal mit Groovy rumgespielt haben :-)

Lessons learned habe ich auch eine ganze Menge:
  • Gant ist nett. Ich habe ein Buildskript für dieses Progrämmchen geschrieben und es in Eclipse per External Tools... eingebunden. Liest sich IMHO viel besser als antiges XML.
  • Closures isoliert zu testen ist nicht trivial. Man schaue sich hierzu an, welche Verrenkungen ich machen mußte, um den Ruler zu testen (siehe RulerTest.groovy). Ich bin da erstmal glücklich gewesen, überhaupt das machen zu können, was ich wollte - aber da sind noch nicht die letzten Zeilen Code und Blogwörter drüber geschrieben worden!
  • Der SwingBuilder ist hakelig, zumindest wenn man auf Graphics arbeiten möchte. Vielleicht mag da Danno Ferrin bald einiges schöner groovyfizieren. Im Nachhinein habe ich mich ja geärgert: Warum nicht gleich mit Grails machen...?!
  • Matt schlug vor, eine DSL für die Rules zu machen. Dafür war der von mir verwendete Builder fast schon überflüssig, denn die Rules sehen in Standard-Groovy-Notation schon fast genug DSLig aus:
    new Rule(from:Cell.LIVE,
    withLivingNeighbours:0..1,
    to:Cell.DEAD
    new Rule(from:Cell.LIVE,
    withLivingNeighbours:3..9,
    to:Cell.DEAD)
    new Rule(from:Cell.LIVE,
    withLivingNeighbours:2..3,
    to:Cell.LIVE)
    new Rule(from:Cell.DEAD,
    withLivingNeighbours:3,
    to:Cell.LIVE)

    Mit eigenem Builder ist's nur minimal besser lesbar:
    new RuleBuilder().rules {
    rule(from:Cell.LIVE,
    withLivingNeighbours:0..1,
    to:Cell.DEAD)
    rule(from:Cell.LIVE,
    withLivingNeighbours:3..9,
    to:Cell.DEAD)
    rule(from:Cell.LIVE,
    withLivingNeighbours:2..3,
    to:Cell.LIVE)
    rule(from:Cell.DEAD,
    withLivingNeighbours:3,
    to:Cell.LIVE)
    }
  • Ausführbare Jars mit integrierten Libs in Java zu erzeugen ist ein Tritt in den Hintern für jeden, der's versucht. One Jar ist da eine mögliche Antwort drauf. Ich habe es mir einfacher gemacht und das komplette groovy-all-1.0.jar per zipfileset in Gant in mein eigenes Jar gedrückt. Fein ist was anderes :-(
  • Lustig war dann wieder das Programmieren mit Methodenzeigern (ich haßte sie in C++!):
    def view = new GroovyShell(
    new Binding(Main:Main.class))
    .evaluate("Main.&${arguments.view}")
    view(universe, arguments)

    Es gibt zwei Methoden in der Main-Klasse, die die Ausgabeart darstellen: swing und console, und diese werden mit den Übergabeparametern -v swing oder -v console so wie oben direkt angesprochen.
  • Ähnlich einfach kann ich per GroovyShell das Muster der Startuniversen parsen, wenn ich das Programm aufrufe mit -p ['XX','XX']:
    static calculatePattern(arguments) {
    if(arguments.pattern[0] != '[')
    arguments.pattern =
    Main."${arguments.pattern}"
    return new GroovyShell(
    new Binding(Main:Main.class))
    .evaluate(arguments.pattern)
    }

    Wenn ich im Pattern-Argument kein sich öffnende eckige Klammer an erster Stelle entdecke, dann verweise ich direkt auf die entsprechenden Konstanten, also z.B. BLINKER, die so aussehen:
    static def BLINKER = "['   ','XXX','   ']"
    Und solch einen String kann man dann mit der GroovyShell auswerten lassen. Das ist Groovy! :-)

    Update 13.04.2007 21:30 h: GroovyShell ist groovy und einfach, aber noch grovier und noch einfacher geht's trotzdem noch. Die GroovyShell benutzt man eher aus Javaklassen heraus, um Groovy-Code auszuführen. Es gibt eine Klasse groovy.util.Eval, die die GroovyShell innerhalb von Groovyklassen bequemer zugreifbar macht. Statt
    return new GroovyShell(
    new Binding(Main:Main.class))
    .evaluate(arguments.pattern)
    schreibe ich da einfach
    return Eval.me(arguments.pattern)
    und fertig ist die Einfachheit. Okay, ist ein wenig geflunkert, denn das Binding habe ich nicht übernommen, denn es war hier tatsächlich überflüssig gewesen. Nicht so bei
    def view =
    new GroovyShell(
    new Binding(Main:Main.class))
    .evaluate("Main.&${arguments.view}")
    . Um hier das Binding aufrecht zu erhalten, schreibe ich nun
    def view =
    Eval.me('Main',
    Main.class,
    "Main.&${arguments.view}")
    . Und es geht noch einfacher: Eval bietet drei Methoden, x(), xy() und xyz(), die zwei bis vier Parameter erwarten, von denen das letzte der zu evaluierende Ausdruck sein muss und die übrigen direkt ins Binding gegeben werden und dann als x, y oder z verfügbar sind. Die view-Methode bekomme ich dann also mit
    def view =
    Eval.x(Main.class,
    "x.&${arguments.view}")
    . Ich liebe solch kleine Optimierungsschmankerl!

Hier gibt's das ausführbare Jar sowie die kompletten Sourcen samt Buildskript, Libs und Eclipse-Dateien. Wer nur mal die Sourcen überfliegen möchte, für den habe ich byteMyCode strapaziert und einfach alle Klassen nacheinander da reingekippt.

Danke an Matt Secoske für diese kurzweilige Programmieraufgabe, an Glen Pepicelli und Peter Ledbrook für Ihre Hilfen beim Problem mit ausführbaren Jars, an Danno Ferrin und Guillaume Laforge für Ihre Hilfen bei meinen Malversuchen im SwingBuilder und an Stefan Roock fürs Zuhören bei meinem Rumgeheule übers Closure-Testen.

Mittwoch, 7. März 2007

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Groovy im Fluss - ein Beispiel aus der Praxis

Wenn es einem Wasserzähler mal schlecht geht und er falsche Werte anzeigt, dann kann das daran liegen, dass er Mist misst. Dann wird er in der Regel auf Herz und Nieren überprüft. Wasserzählern aus Berlin passiert dies bald auf dynamische Art mit Groovy in einem Javaprojekt.

Ich arbeite für meine Firma akquinet in einem Projekt bei den Berliner Wasserbetrieben in Berlin als Entwickler. Die BWB nimmt sogenannte Befundprüfungen bei Wasserzählern vor, denen nicht mehr zugetraut wird, dass sie die korrekten qm Wasser anzeigen, die durch sie hindurch gerauscht sind. Und das geht so: Der Zähler wird in eine Apparatur eingebaut, den Prüfstand, und durch ihn definierte Mengen Wasser gepumpt. Die Abweichung, die bei der Messung zwischen der tatsächlichen und der erwarteten Anzeige des Zählers entstehen, werden in einer Datei notiert.

Das System, an dem mein Projekt arbeitet, verwaltet den Lebenszyklus solch eines Wasserzählers, und also verwalten wir auch den Weg des Zählers während der Befundprüfung. Da müssen dann auch Messurkunden erstellt werden, in welchen das Messverhalten der Zähler festgehalten wird. Unter anderem steht in einer solchen Urkunde die Messabweichungen in Prozent (die aus der Datei des Prüfstandes). Und es sollen die Durchflüsse in Liter pro Zeiteinheit da drin aufgeführt sein, die zu dieser Messabweichung geführt haben. Dummerweise stehen diese Durchflüsse aber nicht in der Datei vom Prüfstand - aber man kann sie errechnen aus einer Tabelle.

Jeder Wasserzähler hat eine Nennweite oder einen Nenndurchfluss (genauer beschrieben auf der-brunnen.de). Die Nennweite ist in m³/h angegeben und zeigt, dass der Zähler für eine Wassermenge von soundsoviel Wasser pro Stunde ausgelegt ist. Dann sind solche Zähler noch in Klassen eingeteilt, A bis C. Und schließlich gibt es die unterschiedlichen Prüfpunkte bei der Messung, Qmin, Qmax und Qtrenn genannt. Man schaue sich folgende Tabelle an:





















Nennweite < 15
Nennweite >= 15
Klasse AQmin: 0,04
Qtrenn: 0,10
Qmin: 0,08
Qtrenn: 0,30
Klasse BQmin: 0,02
Qtrenn: 0,08
Qmin: 0,03
Qtrenn: 0,20
Klasse CQmin: 0,01
Qtrenn: 0,015
Qmin: 0,006
Qtrenn: 0,015


In dieser Tabelle kann man nun, wenn man die Nennweite, die Klasse und den Prüfpunkt des Zählers kennt, zu einem Faktor gelangen. Wird nun dieser Faktor mit der Nennweite des Zähler multipliziert, dann ergibt sich der Durchfluss. Der Prüfpunkt Qmax fehlt, denn Qmax liefert immer den Faktor 2, ist also unabhängig von Klasse und Nennweite.

Ein Beispiel: Wir untersuchen einen Zähler der Klasse A mit einer Nennweite von 2,5 am Prüfpunkt Qmin und bekommen eine Messabweichung von 10%. Dann kann man in der Tabelle nachschlagen: Die Nennweite 2,5 m³/h ist kleiner als 15 m³/h, also linke Spalte anschauen. Klasse A ist gegeben, also obere linke Zelle anschauen. Am Prüfpunkt Qmin soll gemessen werden, also Faktor 0,04. Nennweite 2,5 m³/h mal Faktor 0,04 ergibt einen erwarteten Durchfluss von 0,1 m³/h oder 100 l/h (Liter pro Stunde). Eine Messabweichung von 10% bedeutet also, dass 110 l/h tatsächlich gemessen wurden.

Okay, so weit, so gut. Der Kunde wollte nun, dass unser System zum einen den tatsächlichen Durchfluss zu einer gegebenen Messabweichung errechnet. Und er wollte, dass die Faktorentabelle leicht änderbar bleiben sollte, da die Faktoren sich wohl ab und an ändern würden (er sprach von einer "Propertiesdatei" - muss sich zu oft mit Javaentwicklern unterhalten haben ;-) ).

Herrje, was tun? Unser Projekt ist ein reines Javaprojekt. Die Tabelle könnte man also tatsächlich in einer Propertiesdatei abbilden, aber das wäre nicht sehr schön, oder? So viele Zeilen mit so viel Redundanz drin. Abgesehen mal davon, dass man ein Problem hätte, kleiner oder größer gleich 15 abzubilden. Und was wäre, wenn zusätzliche Bereiche abgedeckt werden müßten, also z.B. kleiner 2, größer gleich 2 bis kleiner 15 und größer gleich 15? Nee, Propertiesdateien sind gut für andere Dinge.

Was ist mit XML? Jaja, abgesehen vom Klammerwahn, der mich in letzter Zeit immer öfter befällt, wenn ich mit XML-Dateien zu tun habe. Wenn man die Tabelle in XML gegossen hätte, dann könnte man doch gut per XPath auf die Daten zugreifen, oder? Naja, könnte man, wenn da nicht die Bereiche wie 0 bis 15 wären - wie greift man pragmatisch auf Bereiche in XML zu?

An diesem Punkt habe ich dann die GroovyConsole aufgemacht und ein wenig zu experimentieren begonnen. Groovy verfügt quasi von Haus aus über Ranges. Die Notation sieht so aus:
assert 1..3 == [1,2,3]

1..3 ist ein Range vom den Integerwerten 1 bis 3. Er kann wie eine Liste von 1, 2 und 3 verstanden werden.

Kleiner 15 und größer gleich 15 kann man sich auch als Ranges vorstellen: 0 bis 14,9999... und 15 bis unendlich bzw. bis zum größtmöglichen im System darstellbaren Fließkommawert. In Groovy kann man das so darstellen:
0.0..(15.0 - (Float.MIN_VALUE as BigDecimal))

15.0..(Float.MAX_VALUE as BigDecimal)

Warum mache ich soetwas wie 15.0 - (Float.MIN_VALUE as BigDecimal)? Weil, es gilt, in Java wie auch in Groovy:
assert 15 == 15 - Float.MAX_VALUE

Das kommt von der Bytereihenfolge im Rechner (engl. Endianess).

In Groovy ist ein Fließkommawert immer BigDecimal. Wenn ich nun also Float.MIN_VALUE in BigDecimal konvertiere (in Groovy elegant gelöst durch das Schlüsselwort as), dann ergibt sich der kleinste darstellbare Wert unterhalb von 15.

Gut, die oberste Tabellenzeile ist abgebildet. Man stelle sich das nun in einer Map vor. Maps werden von Groovy auch als First Class Citizens behandelt. Das sieht dann so aus:
def map =
[(0.0..(15.0 - (Float.MIN_VALUE as BigDecimal))):
'A',
(15.0..(Float.MAX_VALUE as BigDecimal)):
'B']

map.find{ eintrag -> 12.0 in eintrag.key}.value

Führt man dies in einer GroovyConsole aus, dann ist das Ergebnis 'A'. Über die find-Methode können die Einträge einer Map durchsucht werden. Die Closure, die ich dieser Methode mitgebe, wird zur Iteration über alle Einträge benutzt. Ein Eintrag besteht aus dem Schlüssel (key) und dem Wert (value). Wenn der Rückgabewert der Closure true ist, dann wird der entsprechende Eintrag zurückgegeben (beachte: return gibt es zwar noch als Schlüsselwort in Groovy, aber wenn's nicht angeben wird, dann gilt die letzte Anweisung im Codeblock automatisch als Rückgabewert). In obigem Beispiel ist 12.0 nur in dem Range enthalten, dem als Schlüssel der Wert 'A' entspricht.

Super, mit Maps hatte ich also recht schnell eine Struktur gefunden, mit der ich mir die Durchflüsse berechnen kann. Der Rest ging dann fast wie von selbst und heraus kam dann das hier:
class DurchflusstabelleImpl {

static def QMAX_FAKTOR = 2

static def tabelle = [
(0.0..(15.0 - (Float.MIN_VALUE as BigDecimal))):
[
A:[QMIN: 0.04,
QTRENN: 0.10,
QMAX: QMAX_FAKTOR],
B:[QMIN: 0.02,
QTRENN: 0.08,
QMAX: QMAX_FAKTOR],
C:[QMIN: 0.01,
QTRENN: 0.015,
QMAX: QMAX_FAKTOR]
],
(15.0..(Float.MAX_VALUE as BigDecimal)):
[
A:[QMIN: 0.08,
QTRENN: 0.30,
QMAX: QMAX_FAKTOR],
B:[QMIN: 0.03,
QTRENN: 0.20,
QMAX: QMAX_FAKTOR],
C:[QMIN: 0.006,
QTRENN: 0.015,
QMAX: QMAX_FAKTOR]
]
]

int get(float nennweite,
String metrologischeKlasse,
String gesuchterWert,
float messabweichungInProzent) {
def faktor = tabelle
.find{ eintrag -> // #1
(nennweite as BigDecimal) in eintrag.key
}.value
.find{ eintrag -> // #2
metrologischeKlasse == eintrag.key
}.value
.find{ eintrag -> // #3
gesuchterWert == eintrag.key
}.value
def durchfluss = // #4
nennweite * faktor * 1000
(durchfluss +
durchfluss * messabweichungInProzent / 100)
.round()
}
}


Erklärt sich jetzt fast von selbst, gell? Die Map-Verschachtelung ist die konsequente Weiterführung des obigen Beispieles: Die Nennweitenbereiche sind Schlüssel in Maps, deren Werte wiederum Maps mit Klassen als Schlüssel sind, deren Werte wiederum Maps mit Prüfpunkten als Schlüssel sind, deren Werte dann schließlich die Faktoren sind, um die es hier die ganze Zeit geht.

Das Heraussuchen geht dann auch sehr einfach: an Stelle #1 wird der Nennweiten-Bereich herausgesucht, an Stelle #2 die Klasse und an Stelle #3 dann der Prüfpunkt, der dann schließlich den gesuchten Faktor ausspuckt. An Stelle #4 wird dann der Durchfluss (in Liter) berechnet und an Stelle #5 dann schließlich die Messabweichung hinzuberechnet. Fertig. Man stelle sich vor, man hätte dies auf diese Weise in Java programmiert - brrrr, da schüttelt's mich.

(Anmerkung am Rande: Warum hab' ich da drei find-Methoden benutzt? Groovy erlaubt den Zugriff auf Maps viel eleganter, etwa so:
def map = [a:1]
assert 1 == map['a']
assert 1 == map.a

Es hätte also völlig gereicht, wenn ich die Abfragen #1, #2 und #3 so geschrieben hätte; um an den Faktor zu kommen:
def faktor = tabelle
.find{ eintrag -> // #1
(nennweite as BigDecimal) in eintrag.key
}.value[metrologischeKlasse][gesuchterWert]
// ^#2 ^#3

Da war ich auch hin- und hergerissen, ob ich das nicht tatsächlich wie in der zweiten Variante gezeigt hätte schreiben sollen. Aus Gründen der Lesbarkeit habe ich mich dagegen entschieden: Es sieht mit drei find-Methoden symmetrischer aus und man erkennt besser die drei Ebenen der Abfrage (Nennweitenbereich, Klasse und Prüfpunkt).

Wirklich fertig? Naja, fast. Das war eine Groovy-Lösung, und wir haben ein Javaprojekt. Genau, ist gar kein Problem, weil einfach zu lösen:

Die Tabelle ist in einer Groovydatei und dort schon gleich in einer Klasse namens DurchflusstabelleImpl. Bei uns im Projekt gilt die Konvention: XImpl ist die Default-Implementierung des Typs und damit Java-Interfaces X. Das Interface sieht in Java so aus:
public interface Durchflusstabelle {
int get(float nennweite,
String metrologischeKlasse,
String gesuchterWert,
float messabweichungInProzent);
}


Diesen Typ implementieren wir in Groovy, wie wir es in Java auch tun würden: per implements:
class DurchflusstabelleImpl
implements Durchflusstabelle {
/* Implementierung */
}

Damit hat die Groovyklasse das Javainterface implementiert.

Jetzt erklärt das auch, warum die Methode get eine typisierte Signatur hat. Statt
int get(float nennweite,
String metrologischeKlasse,
String gesuchterWert,
float messabweichungInProzent)

hätte ich ja auch einfach
def get(nennweite,
metrologischeKlasse,
gesuchterWert,
messabweichungInProzent)

schreiben können. Aber dann würde nicht die vom Interface vorgeschrieben Methode implementiert werden, sondern die Methode
Object get(Object nennweite,
Object metrologischeKlasse,
Object gesuchterWert,
Object messabweichungInProzent)


Genug der Details. Wie erzeuge ich nun ein Objekt vom Typ Durchflusstabelle? Hierzu gibt es in Groovy mehrere Mechanismen. Ich habe mich für den GroovyClassLoader entschieden. Eine Factory soll uns die Erzeugung des begehrten Objektes kapseln. In Java sieht das dann so aus:
import groovy.lang.GroovyClassLoader;

public class DurchflusstabelleFactory
{
private static final String DURCHFLUSSTABELLE_GROOVY =
"durchflusstabelle.groovy";

public static Durchflusstabelle createDurchflusstabelle()
{
Class clazz = DurchflusstabelleFactory.class;
ClassLoader parent = clazz.getClassLoader(); // #1
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass( #2
clazz.getResourceAsStream(DURCHFLUSSTABELLE_GROOVY));
try
{
// #3
return (Durchflusstabelle) groovyClass.newInstance();
}
catch (Exception programmierfehler)
{
throw new RuntimeException("Konnte Klasse " +
DURCHFLUSSTABELLE_GROOVY + " nicht laden!",
programmierfehler);
}
}
}


Erklärt sich eigentlich schon fast von selbst: An Stelle #1 besorgen wir uns den Java-Classloader, der die Factory geladen hat. Damit erzeugen wir einen neuen Kind-Classloader, den GroovyClassLoader. Bei #2 laden wir dann die Groovyklasse, übergeben sie dem Groovy-Klassenlader zum Parsen und erhalten ein Java-Class-Objekt zurück. Mit dem können wir dann bei #3 per normalem Java-Reflection eine Instanz erzeugen und damit hat die Factory ihre Aufgabe erfüllt.

Mit dem von der Factory erzeugten Objekt kann man übrigens wie mit jedem anderen Javaobjekt umgehen, es gibt da nichts weiter zu beachten. Durch die Factory und das Interface haben wir die Durchflusstabelle komplett gekapselt: Kein Benutzer dieser Groovy-Tabelle weiss etwas von ihrer wider-Java-natürlichen Herkunft, was ja durchaus ein Thema sein kann, wenn im Team noch nicht alle Groovy sprechen.

Der Clou an der Sache ist, wenn man sich nochmal die Anforderungen des Kunden anschaut, die da nach einer Art Propertiesdatei riefen: Dank Groovy haben wir eine ausführbare Spezifikation geschaffen, eine Plusquampropertiesdatei sozusagen. Ändern sich die Faktoren der Durchflusstabelle beim Kunden, dann kann er in die Datei direkt die neuen Faktoren schreiben. Ja mehr noch, sogar neue Klassen und Messpunkte kann er ohne großes Aufsehen da definieren (okay, okay, bei den Nennweiten-Bereichen würde ich ihn dann doch lieber an die Hand nehmen wollen, nur für alle Fälle).

Wir haben diese Story, den Arbeitspunkt, übrigens viermal so hoch eingeschätzt, wie wir dann tatsächlich dafür gebraucht haben. Das schreibe ich nicht der effizienteren Arbeitsweise dank Groovy zu - es könnte auch einfach die Euphorie und der Spaß gewesen sein, mit Groovy zu arbeiten ;-)

Die Klassen DurchflusstabelleFactory und DurchflusstabelleImpl habe ich bei ByteMyCode abgelegt.

Donnerstag, 22. Februar 2007

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Kilometerfresser

Was passiert, wenn ein Läufer sich aus einer Groovy Console, Inlinetests, Groovy Beans, der Meta-Objekt-Programmierung, Swing, GStrings, Regulären Ausdrücken und Closures einen Kilometerfresser baut? Es gibt einen kurzen Einblick in die Welt von Groovy...

Ich bin Läufer und führe ein Trainingstagebuch. Dort trage ich ein, wieviele Kilometer ich am Tag gelaufen bin. In welchem Tempo, wieviele Kalorien ich verbrannt habe, welches Paar Schuhe ich benutzt habe, meinen Durchschnittspuls, und noch ein paar Kleinigkeiten mehr, aber hauptsächlich eben die Kilometer. Wieviele Kilometer am Ende der Woche dann zusammen gekommen sind, gibt mir u. a. einen Hinweis darauf, wie gut ich noch im Plan bin.

Aber die Rechnerei ist oft nervig. Da tippt man sich etwas zusammen im Taschenrechner wie

2 * 2 + 6 * 0.3 + 5 * 0.1 + 14.2 ...
usw. Das liest sich dann so: 2 mal 2 km (fürs Ein- und Auslaufen) + 6 mal 300 m Sprint + 5 mal 100 m Trab + 14,2 km Wettkampf. Und das sind noch nichtmal zwei Tage der letzten Woche gewesen. Auch vertippe ich mich des öfteren und muss dann alles nochmal neu eintippen.

Grund genug, mit Groovy zu spielen! Die Groovy Console bietet schon die Möglichkeit, einen Ausdruck wie oben beschrieben genau so einzugeben und dann auswerten zu lassen. Da kommt dann 20.5 raus. Soweit, so gut. Aber lesbar ist diese Eingabe nicht. Außerdem muss man immer alles in km oder m angeben, aber kann nicht mischen, will man nicht immer umrechnen müssen. Schöner wäre zu lesen:
2 * 2.km + 6 * 300.m + 5 * 100.m + 14.2.km
In Groovy ist Alles ein Objekt, also auch ein Ausdruck wie 14.2. Es gilt
assert BigDecimal.class == 14.2.class
Dieser Inlinetest sichert zu, dass 14.2 vom Typ BigDecimal ist. Objekte können Methoden haben, Getter sind Methoden, also können Objekte Getter haben. Groovy-Getter sind da noch etwas Besonderes dank der Groovy Beans. Wenn ein Groovy-Objekt einen Getter hat, dann kann man so drauf zugreifen
14.2.getKm()
, also wie in Java, aber man kann den Groovyweg gehen und so drauf zugreifen
14.2.km
Gut, soweit die Theorie. Wie bekommt nun ein BigDecimal einen km-Getter? Groovys Meta-Objekt-Programmierung kann uns hier weiterhelfen. Zur Laufzeit können damit Klassen und Objekte um Konstruktoren, Methoden und Variablen ergänzt werden, also auch um Getter. Die einfachste Variante, in Groovy dies zu tun, bietet eine Category. Das ist, einfach gesprochen, eine Klasse, in der wir einem Objekt neue Dinge beibringen können.
class StreckenCategory {
static def getKm(selbst) {
selbst
}
}
In einer Category definieren die statischen Methoden das neue Verhalten, hier die Methode getKM(). Der Parameter selbst gibt hier an, auf welche Objekte das neue Verhalten anzuwenden ist. Wenn selbst vom Typ String wäre, so würden ab sofort nur Strings solch eine Methode haben. Wenn kein Typ angegeben ist, dann ist der Parameter vom Typ Object. Alle Objekte, auf die die Category angewendet werden, bekommen dann eine Methode getKm() Auf BigDecimal will ich den Parameter hier nicht beschränken, da auch die 2 im Eingangs erwähnten Beispiel mit neuer Methode ausgerüstet werden soll, und 2 ist vom Typ Integer.

Eine zweite Methode ist in dieser Category noch erforderlich, denn 0.3.m sollen ja schließlich auch noch berücksichtigt werden.
static def getM(selbst) {
selbst / 1000
}
Um einer Category mitzuteilen, welche Objekte sie beeinflussen soll, gibt es in Groovy das Schlüsselwort use. Der folgende Code demonstriert seine Anwendung:
use(StreckenCategory){
assert 20.5 ==
2 * 2.km + 6 * 300.m + 5 * 100.m + 14.2.km
}
So, an diesem Punkt wäre ich eigentlich schon fertig. Aber das reicht mir noch nicht so ganz, denn ich will ja nicht ständig mit der Category in der Groovy Console hantieren müssen, wenn ich mal ein paar Strecken berechne. Eine kleine Oberfläche wäre hier wohl ganz nett.

Groovy bietet für Oberflächen den SwingBuilder. Builder sind Klassen, mit denen sich elegant hierarchische Strukturen darstellen lassen. Mit dem SwingBuilder kann ich eine Swingoberfläche mit weitaus geringerem Aufwand erstellen als in Java.
import groovy.swing.SwingBuilder

def swing = new SwingBuilder()
def frame =
swing.frame(title:'Kilometerfresser') {
textField(columns:100, actionPerformed: {
event ->
use (StreckenCategory.class) {
event.source.setText(
werteAus(event.source.text)
)
}
})
}
frame.pack()
frame.show()
Was passiert hier? Zuerst erzeuge ich mir einen SwingBuilder. Mit dem baue ich dann ein JFrame mit dem Titel Kilometerfresser und einem Textfeld. Das Textfeld soll 100 Zeichen fassen können (ich laufe viel in der Woche). Wenn eine Aktion ausgelöst wird (actionPerformed), dann wird ein event ausgelöst. Dieses Event kann dann nach dem Inhalt des Textfeldes gefragt werden (event.source.text - ein Getter!). Die Methode werteAus(...) beschreibe ich gleich.


Ohne jetzt näher auf den SwingBuilder einzugehen erkennt man gut das Prinzip dahinter. Führt man den Code aus, poppt ein Fenster auf. Im Fenster ist ein Textfeld enthalten, in welches ich meine Kilometerberechnungen wie beschrieben eintippen kann. Die Entertaste löst die Aktion aus, meine Berechnungen werden ausgeführt und ich erhalte einen neue Kilometerangabe, und zwar dank der folgenden Methode:
def werteAus(string) {
def km = evaluate(string)
"${km}.km"
}
Die Methode ist kurz, aber hat's in sich: Evaluate(...) wertet mir beliebigen Groovycode aus. Die letzte Zeile macht vom impliziten Returnwert gebrauch (in Java würde da stehen return "${km}.km"). Und schließlich helfen hier die GStrings, eine Abwandlung der in Java bekannten Strings. In einem GString kann ich durch die Dollar-Schreibweise auf Variablen zugreifen, wie hier geschehen mit der Variablen km.

Aber so ganz toll ist das ja immer noch nicht, oder? Wer schreibt schon 14.2.km im deutschsprachigen Raum? 14.2 ist eher aus US/UK und .km schreibt man eigentlich mit Leerzeichen statt mit Punkt. Eigentlich möchte ich schreiben können:
2 * 2 km + 6 * 300 m + 5 * 100 m + 14,2 km
Hier kommen nun ein wenig Reguläre Ausdrücke ins Spiel, die von Groovy in besonderer Art und Weise unterstützt werden, wobei die Notation in Java und Groovy gleich ist. Die Methode parse(string) soll Kommata sowie Leerzeichen zwischen Zahl und Maßeinheit durch Punkte ersetzen. Die Methode formatiere(string) soll dagegen alle Dezimalpunkte durch Dezimalkommata ersetzen:
def parse(string) {
string
.replaceAll(/(\d+),(\d+)/, {
alle, km, m -> "${km}.${m}"
})
.replaceAll(/ (k*m)/, {
alle, km -> ".${km}"
})
}

def formatiere(string) {
string.replaceAll(/(\d+).(\d+)/, {
alle, km, m -> "${km},${m}"
})
}
Zum Einsatz kommt hier die Methode replaceAll(regexp, closure), die ein wenig anders funktioniert als die gleichnamige Methode an Java-Strings. Der erste Parameter ist der Reguläre Ausdruck, der zweite hingegen eine Closure. Eine Closure ist ein ausführbarer Codeblock und man kann ihn sich wie eine anonyme Klasse vorstellen. In Groovy-Closures sind das, was vor dem Pfeil (->) steht, die Variablen, die zum Einsatz kommen können. Jede Variable stellt hier eine Gruppenreferenz des Regulären Ausdrucks dar - also das, was man mit den Klammern "gefangen" hat. /(\d+).(\d+)/ stellt also drei Gruppen dar: die erste Klammerung (\d+) beschreibt eine oder mehrere Zahlen, die zweite, weil mit der ersten identisch, ebenfalls. Die erste Gruppe stellt also die Kilometer dar, die zweite die Meter. Implizit gibt es immer die Gruppe 0, die alles abdeckt, was der Reguläre Ausdruck beschreibt. Somit können wir in der Closure drei Variablen angeben: alle für die Gesamtübereinstimmung, km für die Kilometer und m für die Meter. Wir können alle nicht weglassen, da alle Gruppen der Closure übergeben werden, unabhängig davon, welche dann tatsächlich benutzt werden. Das, was nun nach dem Pfeil in der Closure steht, ist das, was anstelle der Gesamtübereinstimmung des Regulären Ausdrucks ersetzt wird; aus 14.2 wird also 14,2.

Die beiden Methoden zum Parsen und formatieren baue ich in die Methode zum Auswerten ein:
def werteAus(string) {
def km =
evaluate(parse(string)) as String
def formatierteKm = formatiere(km)
"${formatierteKm} km"
}

Fertig. Nun reicht's aber auch, sonst programmiere ich hier noch mein komplettes Lauftagebuch :-) Der Code kann bei ByteMyCode in Gänze und Farbe betrachtet werden und ist lauffähig mit Groovy 1.0 in der Groovy Console. Viel Spaß damit!