GUI Patterns

GUI-Patterns

Heute bewegen wir uns mal ein wenig weg von der Eclipse-Terminologie, und unterhalten uns direkt über einige Patterns, die im Zusammenhang mit der Entwicklung von Rich Client Anwendungen durchaus ihre Wichtigkeit haben.

Die einzelnen Patterns , die hier vorgestellt werden sollen sind

Model-View-Controller, Model-View-Presenter (bzw. Supervising-Controller und Passive-View nach Fowler) sowie Presentation-Model. Ich werde, auch wenn Martin Fowler den MVP mittlerweile offiziell in Rente geschickt hat, für die Erläuterung der Grundkonzepte trotzdem keine Unterscheiden zwischen Supervising-Controller und Passive-View vornehmen.

Was sind bzw. was machen jetzt diese GUI-Patterns und warum werden sie überhaupt GUI-Patterns genannt? Zu diesem Thema gibt es Literatur, Blogs und Tutorials ohne Ende im Netz, nichtsdestotrotz möchte ich hier einen kleinen Überblick zu dem Thema geben, vielleicht hilfts ja doch dem einen oder anderen bei der Entscheidung, nach welchem Pattern implementiert wird, und genauso wichtig, ob überhaupt nach einem Pattern implementiert wird.

Die Grundproblematik ist, wie beinahe immer:

Wie kann man den Code der einzelnen Layer (hier einfach: UI, Business-Logik) so trennen, dass jedes für sich unabhängig implementiert, gewartet und getestet werden kann?

Ein in meinen Augen offensichtliches Anti-Pattern, welches in diesem Zusammenhang erwähnt werden muss ist das „Smart-UI“-Pattern. Dieses Pattern wird leider oft für kleinere Anwendungen eingesetzt, die schnell Ergebnisse erzielen sollen und eine überschaubare Komplexität in der Business-Logik haben.

Software, die nach dem Smart-UI Pattern implementiert wird, hat jegliche Business-Logik direkt in die UI integriert. Da stellen sich bei mir schon mal alle Nackenhaare auf, denn die so oft gepriesenen Eigenschaften wie „Wartbarkeit und Testbarkeit“ bleiben hierbei komplett auf der Strecke. Um das ganze zu veranschaulichen ein einfaches und schon oft rezitiertes Beispiel:

Der Use-Case ist, über eine Benutzeroberfläche sollen User-Daten aus einer Datenbank sowohl gelesen und angezeigt werden. Wird dieser Use-Case nach dem Smart-UI Pattern implementiert könnte sich im Source-Code etwas wie folgendes Code-Fragment wiederfinden:

<code>

final Composite composite = new Composite(parent, SWT.NONE);

composite.setLayout(new GridLayout(2, false));

final Label errorLabel = new Label(composite, SWT.NONE);

GridDataFactory.generate(errorLabel, 2, 1);

final Text text = new Text(composite, SWT.BORDER);

text.setText(„user-id goes here“);

Button button = new Button(composite, SWT.PUSH);

button.setText(„Read Data“);

/*

* Daten anzeigen Widgets

*/

Composite dataContainer = new Composite(composite, SWT.NONE);

GridDataFactory.generate(dataContainer, 2, 2);

dataContainer.setLayout(new GridLayout(3,true));

GridData gd = new GridData();

gd.widthHint = 100;

final Label nameLabel = new Label(dataContainer, SWT.BORDER);

nameLabel.setLayoutData(gd);

final Label vornameLabel = new Label(dataContainer, SWT.BORDER);

vornameLabel.setLayoutData(gd);

final Label telefonLabel = new Label(dataContainer, SWT.BORDER);

telefonLabel.setLayoutData(gd);

button.addSelectionListener(new SelectionAdapter() {

@Override

public void widgetSelected(SelectionEvent e) {

String name = text.getText();

/*

* Content Validierung

*/

Pattern p = Pattern.compile(„[1-9]+“);

Matcher m = p.matcher(name);

if (m.find()) {

errorLabel

.setText(„Fehler, Name darf nur Buchstaben enthalten“);

return;

}

/*

* Content Conversion

*

* Name soll nur aus kleinbuchstaben bestehen

*/

name = name.toLowerCase();

/*

* Kommunikation mit dem Backend.

*

* Lade Benutzerdaten aus der „Datenbank“

*

* Hier könnte direkt ein SQL-Statement mittels JDBC o.ä.

* ausgeführt werden

*/

List<String> benutzerDaten = dataBase.get(name);

/*

* Daten checken

*/

if (benutzerDaten == null) {

errorLabel.setText(„Fehler, Datensatz nicht vorhanden“);

return;

}

/*

* Hier folgen weitere Integritätsprüfungen.

*/

nameLabel.setText(benutzerDaten.get(0));

vornameLabel.setText(benutzerDaten.get(1));

telefonLabel.setText(benutzerDaten.get(2));

}

}

);

</code>

Man sieht, dass hier innerhalb der UI sowohl Validierung, Konvertierung als auch die Kommunikation mit dem Backend implementiert ist. Wie soll man so einen Code testen, warten etc.. Schon dieses einfache Beispiel macht mich völlig fertig;-)

Nichtsdestotrotz „darf“ dieser Ansatz für Prototyping verwendet werden, da ein schnelles Erstellen von „ROK“-Code (Run-Once-And-Kick) möglich ist.

Das Beispiel-Plugin hierzu findet man hier. <TODO apply Plugin>

Natürlich ist das oben genannte Beispiel hoffnungslos vereinfacht, überzogen und schlecht programmiert;-) (autsch, das tat weh), allerdings sollte das Prinzip klar werden und eigentlich auch bekannt sein.

Kommen wir zu interessanterem: MVC!!

Was war das noch gleich wieder? Generationen von Programmierern haben sich, werden sich und wollen sich damit beschäftigen, der Urahn , der Yoda, der Royal-TS aller GUI-Patterns;-). Ein einfaches Diagram:

Die Pfeile deuten hierbei die Beziehung : „Ich kenn…“ an. D.h. der View kennt sowohl den Controller als auch das Model. Der Controller hingegen kennt das Model und zur Einfachheit meistens auch den View. Das Model an sich ist losgelöst und kennt weder den Controller noch den View. Najaaa, nun werden sie gleich wieder rufen, OK!! Das Model kennt auch den View, allerdings nicht als View sondern lediglich als PropertyChangeListener (Ich nehm hier einfach mal diese Bezeichnung…).

Wird jetzt in der UI ein Button betätigt, so befindet sich die Logik hierzu (Im Gegensatz zum zuvor beschriebenen Smart-UI-Pattern) nicht in der UI, sondern im Controller. Doch halt… wie kommt der Controller in den View?? Üblicherweise wird hierfür Constructor-Injection verwendet, d.h. in PseudoCode:

<code>

Model model = new Model()

View view = new View(new Controller(model))

model.addListener(view)

</code>

Wird jetzt in der UI ein Button betätigt, so befindet sich die Logik hierzu (Im Gegensatz zum zuvor beschriebenen Smart-UI-Pattern) nicht in der UI, sondern im Controller. Das sollte man sich mal auf der Zunge zergehen lassen;-). Der Controller widerum könnte, referenziert man das vorhergehende Beispiel, eine Methode zum Laden des Datensatzes vom Hans bereitstellen.

Also innerhalb des Views wieder in furchtbarem Pseudocode:

<code>

//den Controller haben wir per Constructor bekommen

private Controller controller;

button.do(){

lade Hans!

}

</code>

Der View hat sich ja als Listener beim Model registriert, also in diesem Fall beispielsweise bei einem UserObjekt names Hans. Wird dieses Objekt jetzt geändert, also beispielsweise wenn Hans heiratet, und das Weichei den Namen der Frau annimmt;-), dann würde Hans nicht mehr Mustermann sondern Müller heissen. Nun steht aber in der UI, namentlich dem Textfeld für den Nachnamen nach wie vor Mustermann. Nehmen wir also an, wir haben einen Button mit der Aufschrift „Heirate!“ der den Nachnamen innerhalb des Hans-Objektes in Müller ändert.

Diese Änderung muss der View mitgeteilt werden, woher sollte sie das auch wissen… also feuert das Hans-Objekt bei jeder Änderung an alle registierten Listener ein „Property wurde geändert“-Event mit den neuen Werten. Das Event wird also an die View weitergereicht und so kann diese sich aktualisieren.

Der grosse Vorteil hierbei ist, dass zum Einen beliebig viele verschiedene Views verwendet werden können, da der View ja weder vom Model noch vom Controller abhängt, ohne dass der andere Code geändert werden muss. Vergleicht man diesen Ansatz wieder mit dem Smart-UI-Ansatz, so sieht man dass das hier nicht möglich wäre. Wollte man hier einen anderen View implementieren müsste man die gesamte Logik per Copy-And-Paste mit rüberziehen und hätte fortan die doppelte Arbeit, den Code zu warten, „Ja guad Nacht um Sechse..“, das wollen wir vermeiden.

Weiterhin lässt sich sowohl der Controller als auch das Model separat testen, was enorm wichtig für gut funktionierenden Code ist. Da es im Netz bereits Milliarden Tutorials zum MVC gibt, lasse ich hier mal ein konkretes Beispiel weg, aber ich hoffe ich habe das so erklärt wie ich es damals in jungen Jahren gerne erklärt bekommen hätte.

Wer jetzt denkt, das ist schon der Gipfel – Oh nein! Es geht noch besser, kommen wir zu meinem Zweitliebsten GUI-Pattern – MVP! Model-View-Presenter ist eine Abwandlung des MVC und funktioniert ungefähr so, die Pfeile deuten wieder die Beziehung „Kennt..“ an.

Na, dämmerts schon? MVP kapselt den View im Gegensatz zum MVC komplett ab vom Model, also es besteht keine Observer-Beziehung mehr zwischen View und Model. Der View ist hierdurch komplett losgelöst von den anderen Schichten. Eine Schritt für Schritt-Anleitung könnte ungefähr so aussehen:

1. View implementiert ein View-Interface, welches alle wichtigen Attribute (also Textfelder, Labels, Buttons etc..) über öffentliche Methoden (Getter) bereitstellt.

2. Presenter erhält eine Instanz des View-Interfaces (also den View gekapselt über das Interface) als auch das Model (dies muss jedoch nicht zwangsläufig über Constructor-Injection passieren ) über Constructor-Injection:

IView view = new View();
Presenter presenter = new Presenter(view, model);

3. Der Presenter holt sich die entsprechenden Attribute direkt aus dem View über die öffentlichen Methoden des View-Interfaces.

4. Die View-Attribute werden direkt über DataBinding mit den entsprechenden Attributen im Presenter verbunden.

5. Der Presenter delegiert Aktionen analog zum MVC an das Model weiter und wiederum Änderungen am Model direkt zurück an den View.

Das ist im Prinzip MVP in 30 Sekunden;-), ein gutes Beispiel hierzu findet sich hier //TODO Link.

Wie bereits zuvor erwähnt, hat Martin Fowler dieses Pattern in zwei verschiedene Patterns aufgesplittet. „Supervising-Controller“ und „Passive-View“. Da diese Abwandlungen sehr schön auf der Fowler-Homepage beschrieben sind, werde ich an dieser Stelle nicht weiter darauf eingehen. Kurz gesagt, „Supervising-Controller“ lässt eine Minimal-Logik innerhalb des Views um die Presenter-Logik einfach zu halten während der „Passive-View“ jegliche Logik in den Presenter auslagert und im Prinzip dumm ist;-).

Das Dritte Pattern, das ich an dieser Stelle noch kurz vorstellen möchte ist das Presentation-Model. Dies ist ein weiteres Pattern, welches ebenfalls eine Trennung der verschiedenen Schichten ermöglicht und von mir zusammen mit MVP oft eingesetzt wird.

Presentation-Model kann vielleicht am einfachsten als Abstraktion eines Views in ein Model-Objekt beschrieben werden. Sinn des ganzen ist eine weitere Entkopplung des zuvor beschriebenen Presenters. Im MVP besteht immer noch eine Bindung des Presenters an eine View – Instanz, d.h. es ist nicht ohne weiteres möglich, einen Presenter völlig isoliert von der UI zu testen (natürlich lässt sich eine Art Mock-View implementieren), es geht aber noch einfacher.

Das Diagramm von oben auf das Presentation-Model abgeändert könnte ungefähr so aussehen:

Das Presentation-Model arbeitet noch stärker mit DataBinding als der MVP. Im Prinzip werden alle wichtigen Attribute aus dem View in das PM abstrahiert. Hier könnten sich also Methoden wie „isTextFeldVisible“ oder „isCheckBoxButton“ enabled. Der View erhält eine Instanz des Presentation-Models über Constructor-Injection, der Rest sollte sich mit dem bisher in diesem Artikel erworbenen Wissen herleiten lassenJ,ansonsten ist das Presentation-Model hier //TODO Link ganz gut beschrieben.

Der Vorteil der sich hieraus ergibt ist, dass das Presentation-Model völlig losgelöst vom View getestet werden kann.

Das war mal ein „Blick aus 10.000“ – Meter auf die in meinen Augen wichtigsten GUI-Patterns, die man also wirklich kennen sollte. Ich freue mich über Kommentare hierzu!

Advertisements

Ein Gedanke zu „GUI Patterns

  1. Marc

    Danke für den guten Artikel. Insbesondere der Hinweis auf den Presentation-Model Pattern.

    Ich versuche gerade eine anständige MVP Struktur für mein Testprojekt aufzubauen, hab da aber ein paar Probleme. Zum ersten glaube ich einen Supervising Presenter zu nutzen, da meine View eine Referenz auf das Model-Projekt erhält (ich brauch das für die JFace Viewers zur unterscheidung).

    Mein größtes Problem sind aber gerade die Command-Handler (org.eclipse.ui.handlers) die für die Commands benötigt werden. Diese sind ja in extra Klassen, und diese haben keine Referenz zu meiner View und somit auch nicht zum Presenter. Gibt es eine best-practise-Lösung wie man das macht? Eigentlich sollte in der execute-method etwas wie Presenter.tuWas() stehen.

    Grüße
    Marc

    Antwort

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s