Die GUI-Monade: Monaden in der Praxis
Dieser Artikel zeigt den Einsatz von Monaden in der Praxis um GUI-Fenster zu beschreiben.
Für einen Kunden, der in der Sozialpädagogik tätig ist, entwickelt die ActiveGroup eine Software zur Dokumentation von Therapiemaßnahmen. Die Software erfasst die Stammdaten von Personen, die sich einer Therapie unterziehen, lässt die Personen Fragebögen ausfüllen, um den aktuellen Zustand und die Fortschritte der Therapie zu dokumentieren, und erlaubt das Erstellen zahlreicher Berichte.
Zur Entwicklung der Fragebögen verwenden wir eine Monade, welche die Darstellung des Fragebogens beschreibt und die Antworten einsammelt. Wieso eine Monade? Wir sehen folgende Vorteile:
-
Komponierbarkeit Die Elemente des Fragebogens lassen sich unabhängig voneinander beschreiben und anschließend beliebig miteinander kombinieren. Damit lassen sich unterschiedliche Logiken im Ablauf eines Fragebogens darstellen, z.B. wenn Frage 1 mit ‚ja‘ beantwortet wurde, geht es weiter auf Seite 2, ansonsten auf Seite 3.
-
Testbarkeit Da die Monade den Fragebogen nur beschreibt, lässt er sich relativ leicht testen. Statt Maus-Klicks zu synthetisieren, wertet man die Callbacks der Knöpfe innerhalb der Monade aus und testet ihre Auswirkungen auf die Beschreibung.
In diesem Artikel möchte die Komponierbarkeit anhand eines Beispiels illustrieren; die Testbarkeit und Implementierung überlasse ich späteren Artikeln. Als Beispiel dient ein kleiner Fragebogen, der festellt, ob der Befragte am Montag Zeit hat und falls ja, um wieviel Uhr.
Beschreibung einer Seite eines Fragebogens
Die erste Seite des Fragebogens stellt fest, ob der Befragte überhaupt am Montag Zeit hat:
Diese vier Scala-Zeilen setzen in die erste Zeile den Fragetext, gefolgt von zwei Knöpfen für „Ja“ und „Nein“:
Die put
-Methode setzt das Inhaltselement in die angegebene Zeile und
Spalte. Das zugrunde liegende Layoutmodell zur Darstellung des
Fragebogens basiert auf Swings‘
GridBagLayout,
welches in Scala mit einem Panel zu einem
GridBagPanel
kombiniert ist. Anderer Render-Möglichkeiten sind durchaus denkbar
(z.B. als HTML-Seite), doch die haben wir bisher nicht umgesetzt.
Die >>
-Methode ist eine der beiden bind
-Methoden der Monade: sie
verknüpft zwei Monaden, wobei sie das Ergebnis der ersten verwirft.
Die put
-Methode setzt also nicht wirklich etwas, sondern liefert
eine Monade, welche das Setzen eines Inhaltelements darstellt. Man
spricht auch von einem Monaden-Operator, da die Methode in der Monade
agiert. Mit >>
verknüpfen wir das Setzen mehrerer Inhaltselemente.
Hier sieht man schon die ersten Anzeichen der Komponierbarkeit: jeder
put
-Aufruf steht gewissermaßen für sich und erst bind
komponiert
die put
. Ganz stimmt das natürlich nicht, denn die put
achten
ja darauf, Elemente nicht in die selbe GridBag-Zelle zu platzieren.
Deutlicher wird es, wenn wir die optionale Frageseite nach der Uhrzeit
implementieren.
Als Inhaltselemente tauchen im obigen Beispiel Text
und Button
auf. Wie die Namen schon suggerieren, stellt ersteres einen
(nicht-editierbaren) Text dar, während letzteres einen Knopf mit einer
Beschriftung und einem Callback darstellt. Beides sind nicht Teil von
Swing, sondern Platzhalter unser GUI-Monade. Erst bei der Darstellung
werden die Swing-Elemente erzeugt.
Als Callback verwenden wir im obigen Beispiel addAnswer
. Die
addAnswer
-Methode fügt der Monade eine Antwort hinzu. Ich habe also
die GUI-Monade um die Funktionalität erweitert, sich Antworten auf
Fragen zu merken. Im obigen Beispiel geschieht dies für die Frage
montagZeitQ
. montagZeitQ
ist als Auswahlfrage definiert mit den
Auswahlmäglichkeiten Ja
und Nein
:
Die Zahlen geben die Repräsentation der Auswahlmöglichkeiten und der Frage an, wenn sie in einer Datenbank gespeichert werden.
Die fertige „Umfrage“ können wir dann wie folgt definieren und starten:
Hier setzen wir noch den Titel der Umfrage (setTitle
ist wieder ein
Monaden-Operator) und teilen der Monade mit ask
mit, dass wir nun
fertig mit der Beschreibung sind und die GUI aktualisert und
dargestellt werden kann. showSurvey
macht auch genau dies: sie
wertet die Manipulationen aus, welche wir mit put
und setTitle
an
der Monade vorgenommen haben und stellt die resultierende GUI dar.
Logik im Ablauf eines Fragebogens
Wenn die erste Frage mit „ja“ beantwortet wurde, soll auf der zweiten Seite nach der Uhrzeit gefragt werden:
Die zweite Seite stellt die Freitext-Frage nach der Uhrzeit
(uhrzeitQ
vom Typ EnterTextQ
) und gibt ein Textfeld (TextField
)
an, in dem die Text-Antwort (EnteredText
) dafür eingegeben werden
kann:
Das Textfeld hat einen Callback onValueChanged
, der immer
aufgerufen wird, wenn sich der Inhalt des Textfeldes ändert. Als
Parameter erhält der Callback den aktuellen Textinhalt und als
Ergebnis liefert er eine Monade.
In diesem Beispiel trägt der Callback die Antwort auf die
Freitext-Frage ein und bleibt mit ask
auf der aktuellen Frageseite.
Weiter geht es mit dem „Weiter“-Knopf, der nichts tut
(Monaden-Operator doNothing
) und damit die Kontrolle an die Stelle
zurückgibt, die seite2
irgendwo eingefügt hat.
Hier sehen wir einen weiteren Aspekt der Komponierbarkeit: wo es nach
seite2
weitergeht ergibt sich erst durch entsprechende Komponierung
mit dem bind
-Operator. Im bekannten
Model-View-Controller-Pattern
(MVC) würde dies im Controller stattfinden. Statt eines expliziten
Controllers ergibt sich der Programablauf hier durch das
Zusammenstellen der Komponenten mit bind
.
Nun zur Programmablauf-Logik: Die zweite Seite wollen wir ja nur
darstellen, falls die erste Frage mit „ja“ beantwortet wurde. Wie die
erste Frage beantwortet wurde, können wir über den Monaden-Operator
getAnswer
feststellen. Die kleine Umfrage erweitern wir also wie
folgt:
Falls die erste Frage mit „Ja“ beantwortet wurde, löschen wir mit
clear
die GUI und zeigen die zweite Seite an. Andernfalls tun wir
nichts. kleineUmfrage1
konnten wir also sehr leicht erweitern,
ohne dass wir deren Definition ändern mussten. showSurvey
sollte
nun natürlich kleineUmfrage2
darstellen.
Interaktion mit der Umgebung
Obschon wir mit Monaden arbeiten, hindert uns nichts daran, Seiteneffekt zu produzieren, falls gewünscht. Zum Beispiel könnten wir am Schluss der Umfrage das Ergebnis in eine Datenbank schreiben oder, wie im folgenden Beispiel, auf der Konsole ausgeben:
Mit getAnswers
erhalten wir alle Antworten als eine Map
von Frage
auf Antwort. Dabei ist >>=
der bind
-Operator der Monade, welche
das Ergebnis der vorherigen Monade liefert. Falls die Antwort auf die
erste Frage, ob man am Montag Zeit habe, mit „Ja“ beantwortet wurde,
holen wir uns die eingegebene Uhrzeit von Seite zwei mit
answers(uhrzeitQ)
und geben einen entsprechenden Text aus.
Andernfalls geben wir aus, dass der Befragte am Montag keine Zeit
hatte.
Auch diese Operation lässt sich mit dem bestehenden Fragebogen kombinieren zu:
Nach diesem Prinzip haben wir der Software zur Dokumentation von
Therapiemaßnahmen vier weitere Fragebögen hinzugefügt, die teilweise
über 100 Fragen auf entsprechend vielen Frageseiten stellen. Die
Fragebögen haben wir dabei von „unten nach oben“ aufgebaut: die
einzelnen Seiten werden programmatisch sowohl erzeugt als auch
anschließend zum gesamten Fragebogen zusammengesetzt. Obschon wir MVC
nicht klassisch umgesetzt haben, sind das Modell (in der Monade), die
Präsentation (GridBagLayout) und die Kontrolle (Callbacks und >>=
)
voneinander getrennt und lassen sich relativ einfach erweitern.
Mit dem vereinfachten Beispiel aus der Praxis habe ich den Vorteil der Komponierbarkeit unserer Architektur mit GUI-Monaden illustriert. Falls gewünscht, kann ich in späteren Artikeln noch auf die Testbarkeit und die Implementierung eingehen. Wer Lust hat, kann gerne in den Kommentaren eine alternative Architektur in der Programmiersprache seiner Wahl skizzieren. Über Fragen, Anregungen oder Anmerkungen freue ich mich ebenfalls, sei es per Kommentar oder per Email.