Archiv der Kategorie: gui patterns

RCP UI Testing mal ganz ohne Framework

Das Testen von UI Funktionalitäten ist oftmals eine nicht ganz einfache Aufgabe.

Ein Framework, dass die Arbeit hier sehr erleichtert ist beispielsweise SWTBot.

Manchmal lässt die Projektsituation aber einfach nicht zu, ein weiteres Framework in die Systemarchitektur zu integrieren.

Jetzt hat man prinzipiell zwei Möglichkeiten

  1. Wir verzichten auf UI-Tests
  2. Man testet ohne Framework

Dass die erste Option nicht unbedingt zu empfehlen ist, sollte jedem Entwickler klar sein, ebenso aber auch dass die Umsetzung der zweiten Option nicht ganz trivial ist. Hier führen mit Sicherheit mehrere Wege nach Rom, einen davon (nicht zwangsläufig den besten oder kürzesten) soll dieser Artikel beleuchten.

Bevor sich ein Wandersmann auf den Weg macht, sollte die Ausrüstung überprüft werden. Statt gutem Schuhwerk und wasserfester Kleidung benötigen wir jedoch folgendes:

  • Databinding Framework
  • Eclipse Adapter Framework
  • Motivation

Machen wir uns also auf den Weg.

Die ersten Schritte sind meist die schwersten (bzw. die am wenigsten spannenden). Für einen Artikel ist dies meist die Umsetzung einer mehr oder minder spannenden Demoapplikation. Für unsere Zwecke brauchen wir also eine Anwendung mit Benutzeroberfläche, die es zu testen gilt.

Was sind aber testbare UI-Funktionalitäten?

  • Validierungslogik
  • Konvertierungslogik
  • Enablement / Disablement von Widgets
  • Visibility / Invisibility von Widgets

Diese Liste könnte wahrscheinlich endlos weitergeführt werden, aber das soll erstmal reichen.

Wir verwenden hierfür eine sehr einfache Demoanwendung. Das zugrundeliegende Modell besteht nur aus einer Klasse „Person“,

die in etwa so aussieht:

public class Person {

private String name;
private String surname;
private String email;
private String title;

private PropertyChangeSupport support = new PropertyChangeSupport(this);

/**
* Constructor
* */
public Person(String name, String email, String title) {
super();
this.name = name;
this.email = email;
this.title = title;
}

public void addPropertyChangeListener(String attribute,
PropertyChangeListener listener) {
support.addPropertyChangeListener(attribute, listener);
}

public void removePropertyChangeListener(String attribute,
PropertyChangeListener listener) {
support.removePropertyChangeListener(attribute, listener);
}

public String getName() {
return name;
}

public void setName(String name) {
String oldName = this.name;
this.name = name;
support.firePropertyChange(„name“, oldName, name);
}

public String getSurname() {
return surname;
}

public void setSurname(String surname) {
String oldSurname = this.surname;
this.surname = surname;
support.firePropertyChange(„surname“, oldSurname, surname);
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
String oldEmail = this.email;
this.email = email;
support.firePropertyChange(„email“, oldEmail, email);
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
String oldTitle = this.title;
this.title = title;
support.firePropertyChange(„title“, oldTitle, title);
}

}

Damit das Modellobjekt korrekt mit dem Databinding-Framework zusammenarbeitet, implementieren wir den PropertyChangeSupport derart, dass jede Setter-Methode ein PropertyChangeEvent feuert. Hierdurch ist sichergestellt, dass wann immer sich ein Modellattribut ändert, die Änderung an die UI propagiert wird (Ist diese Anforderung nicht zwingend kann auf die Implementierung des PropertyChangeSupport verzichtet werden).

Im Folgenden wird auf die Erläuterung der Standardfunktionalitäten verzichtet, diese sollte man entweder kennen oder nachschlagen.Erstellen wir eine passende Benutzeroberfläche hierfür. Wir erzeugen ein einfaches Demoprojekt als RCP Anwendung.

Zunächst erzeugen wir einen Editor mit einem passenden EditorInput.

Der Code des EditorInputs könnte in etwa so aussehen:

public class PersonEditorInput implements IEditorInput {

private Person model;

public PersonEditorInput(Person person) {
this.model = person;
}

@Override
public boolean exists() {
// TODO Auto-generated method stub
return false;
}

@Override
public ImageDescriptor getImageDescriptor() {
// TODO Auto-generated method stub
return null;
}

@Override
public String getName() {
return „PersonEditor“;
}

@Override
public IPersistableElement getPersistable() {
// TODO Auto-generated method stub
return null;
}

@Override
public String getToolTipText() {
return getName();
}

@Override
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(model, adapter);
}

public Person getInput() {
return model;
}

}

Die einzig interessante Codezeile ist folgende:

@Override
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(model, adapter);
}

Darauf kommen wir später noch zu sprechen.

Startet man die Anwendung, könnte das in etwa so aussehen:

pluginwithaview

Hier erstellen wir uns jetzt eine passende Benutzeroberfläche (auf die Erstellung gehe ich nicht näher ein).

view_mit_widgets

Das sieht doch schon gar nicht mal schlecht aus. Zunächst definieren wir Logik, die in diesem Zusammenhang getestet werden könnte (ohne bestimmte Reihenfolge).

  • Der Save-Button sollte nur enabled sein, wenn das Emailfeld korrekt befüllt ist oder die Checkbox deselektiert
  • Das Emailfeld sollte nur enabled sein, wenn die Checkbox selektiert ist
  • Wir die Checkbox abgewählt, sollte eine evtl. zuvor eingegebene Emailadresse gelöscht werden

Derartige Funktionalitäten zu testen ist schwer, da wir nicht einfach eine View für einen JUnit-Test instantiieren können, und selbst wenn das möglich wäre, müssten die einzelnen Felder (jetzt als private deklarierten Felder) irgendwie zugänglich gemacht werden, damit wir sie in einem JUnit-Test abdecken können (sieht man mal von der wenig schönen Möglichkeit ab, das Ganze über die Reflection-API zu lösen).

Gehen wir´s an!

Es gibt einen wunderschönen Design-Pattern, der uns die Testbarkeit zurückgibt, die uns im Verlauf des Weges abhanden gekommen ist, das Presentation Model, ursprünglich definiert von Martin Fowler, hunderte Male adaptiert und jetzt schliesslich hier gelandet. Auf die Philosophie des Design-Patterns, als auch auf mögliche Vor- und Nachteile werde ich nicht näher eingehen und verweise auf entsprechende Fachliteratur oder Online-Artikel. Uns interessiert an dieser Stelle primär die Implementierung.

Ein Presentation-Modell kann als eine Art Abstraktion für unsere View gesehen werden. Das klingt jetzt vielleicht schwierig, ist aber eigentlich sehr einfach.

Gibt es beispielsweise in der View eine Checkbox, dann bietet das PresentationModell hierfür Methoden wie

  • isCheckBoxSelected()
  • isCheckBoxEnabled()
  • isCheckBoxVisible()

Für ein Textfeld könnte das PresentationModell u.a. folgende Methoden bieten:

  • isTextFieldVisible()
  • isTextFieldEnabled()
  • isTextFieldEditable

Im Prinzip machen wir also bestimmte Eigenschaften unsere (natürlich privaten) Widgets nach aussen hin zugänglich, aber nur indirekt gekapselt durch das PresentationModel.Würde man sich das Ganze in einem Klassendiagramm visualisieren, würde das so aussehen:

pm_klassendiagramm

D.h. der View kennt das PresentationModell, und das PresentationModell kennt das Modellobjekt, das wars.

Da wir aber RCP verwenden, wollen wir das ganze noch ein wenig „loser“ koppeln.

Jetzt definieren wir uns das PresentationModel, gemäss den obigen Testanforderungen könnte eine Implementierung wie folgt aussehen, der Einfachheit halber beschränken wir uns auf das Testen des Emailfeldes und der damit zusammenhängenden Funktionalitäten, mögliche Validierungen für die anderen Felder überlasse ich dem Leser.

Ein Interface für das PresentationModel könnte so aussehen:

public interface IPersonPresentationModel {

public IObservableValue isEmailFieldVAlid();

public IObservableValue isEmailButtonChecked();

public IObservableValue isEmailFieldEnabled();

public IObservableValue isSaveButtonEnabled();

public IObservableValue getEmailTextValue();

public IObservableValue getNameValue();

public IObservableValue getTitleValue();

}

Wie man sieht, gibt es eine Methode die den Status der Checkbox überwacht, eine Methode für das Enablement des Emailfeldes (dies hängt von der Selektion der Checkbox ab), sowie eine Methode die das Enablement des Save-Buttons überwacht.Zusätzlich gibt es Getter für alle Observables zur Überwachung der Inhalte, also beispielsweise getNameValue() zur Überwachung des Textes in einem Textfeld.

Da der Editor, den wir hier überwachen, von der Eclipse-Plattform zur Laufzeit instantiiert wird, brauchen wir eine einfache Möglichkeit, das PresentationModel dem Editor bekannt zu machen.

Wir verwenden das Adapter-Framework von Eclipse. Adaptiert wird das Model-Objekt direkt, und zwar aus dem EditorInput. Hier kommen wir wieder zurück auf die zuvor definierte Zeile.

@Override
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(model, adapter);
}

Wir definieren hierzu eine Extension für den Extension-Point „org.eclipse.core.runtime.adapters“ wie folgt:

<extension
point=“org.eclipse.core.runtime.adapters“>
<factory
adaptableType=“de.pentasys.uitesting.demo.model.Person“
class=“de.pentasys.uitesting.demo.adapters.PersonViewAdapterFactory“>
<adapter
type=“de.pentasys.uitesting.demo.pm.IPersonPresentationModel“>
</adapter>
</factory>
</extension>

Die entsprechende Implementierung der Adapterfactory ist sehr einfach und sieht so aus:

public class PersonViewAdapterFactory implements IAdapterFactory {

public static final Class<?>[] ADAPTERS = new Class<?>[] { Person.class};

@Override
@SuppressWarnings(„unchecked“)
public Object getAdapter(Object adaptableObject, Class adapterType) {

return new PersonViewPresentationModel((Person)adaptableObject);

}

@Override
public Class<?>[] getAdapterList() {
return ADAPTERS;
}

}

Die Klasse adaptiert also die Klasse Person, und liefert ein hierfür passenden PresentationModel zurück.

Der nächste Schritt besteht jetzt darin, sowohl die Widgets in der UI an die Observables aus dem PresentationModell als auch die Observables des PresentationModels an die Attribute des Models zu binden. Im Editor könnte das so aussehen:

private void initBindings() {
DataBindingContext context = new DataBindingContext();
context.bindValue(SWTObservables.observeText(emailText, SWT.FocusOut),
pm.getEmailTextValue(), null, null);

context.bindValue(SWTObservables.observeText(nameText, SWT.FocusOut),
pm.getNameValue(), null, null);

context.bindValue(ViewersObservables
.observeSingleSelection(titleViewer), pm.getTitleValue(), null,
null);

context.bindValue(SWTObservables.observeSelection(emailButton), pm
.isEmailButtonChecked(), null, null);

context.bindValue(SWTObservables.observeEnabled(emailText), pm
.isEmailFieldEnabled(), new UpdateValueStrategy(
UpdateValueStrategy.POLICY_NEVER), null);

context.bindValue(SWTObservables.observeEnabled(saveButton), pm
.isSaveButtonEnabled(), new UpdateValueStrategy(
UpdateValueStrategy.POLICY_NEVER), null);

}

Im PresentationModel könnte das Ganze so aussehen:

context.bindValue(emailFieldValue, BeansObservables.observeValue(
person, „email“), null, null);

context.bindValue(titleValue, BeansObservables.observeValue(person,
„title“), null, null);

context.bindValue(nameValue, BeansObservables.observeValue(person,
„name“), null, null);

Startet man das Ganze jetzt mit einer Demoperson, kann man erkennen, dass die Daten bereits an die Widgets gebunden sind. Bisher ist nichts gewonnen, definieren wir also die restlichen Bindings.

Schauen wir uns einige interessantere Bindings im Presentation-Model an:

context
.bindValue(emailFieldEnabledValue, emailButtonCheckedValue,
new UpdateValueStrategy(
UpdateValueStrategy.POLICY_NEVER), null);

Hier binden wir das emailFieldEnabledValue an den EmailButton, daraus ergibt sich, dass das Email-Feld nur enabled ist, wenn die Checkbox aktiviert ist.

Der Save Button soll nur Enabled sein, wenn eine gültige Email oder keine Email eingegeben wurde.

context.bindValue(saveButtonEnabledValue, emailFieldValue,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER),
new UpdateValueStrategy().setConverter(new Converter(
String.class, Boolean.class) {
@Override
public Object convert(Object fromObject) {
String str = fromObject.toString();
if („“.equals(str) || str
.matches(„[a-zA-Z0-9\\.]+@[a-zA-Z0-9]+\\.[a-z]{2,3}“)) {
return true;
}
return false;
}
}));

Wir verwenden hier einen Converter und eine Regexp zur Validierung der Email-Adresse.

Hier haben wir 2 Funktionlitäten, die es wert sind, getestet zu werden.

  1. Ist der SaveButton aktiviert, wenn keine Emailadresse eingegeben wurde?
  2. Ist der SaveButton aktiviert, wenn eine gültige Emailadresse eingegeben wurde?
  3. Ist der SaveButton deaktiviert, wenn eine ungültige Emailadresse eingegeben wurde?
  4. Ist das Emailfeld aktiviert, wenn die Checkbox aktiviert wurde?

Das schöne dabei ist, dass wir nur noch die Inhalte der entsprechenden Observables testen müssen, da der View (also unser Editor) im Prinzip

komplett dumm ist, und keine Logik enthält, sondern stattdessen seine Widgets nur gegen die Observables im PresentationModel bindet.

Definieren wir also ein Fragment, welches die Tests für unser Plugin definiert.

Die oben festgelegten Funktionalitäten lassen sich nun sehr einfach testen, ein Testfall könnte in etwa so aussehen:

public class PresentationModelTest {

private IPersonPresentationModel model;

private Person person;

@Before
public void setUp() {
person = new Person(null,null,null);

model = new PersonViewPresentationModel(person);

}

@After
public void tearDown() {
person = null;

model = null;
}

@Test
public void testSaveInitialEnabled(){
assertTrue((Boolean)model.isSaveButtonEnabled().getValue());
}

@Test
public void testSaveEnabledLegalEmail(){
model.isSaveButtonEnabled().setValue(false);
person.setEmail(„test@test.de“);
assertTrue((Boolean)model.isSaveButtonEnabled().getValue());
}
@Test
public void testSaveEnabledEmptyEmail(){
model.isSaveButtonEnabled().setValue(false);
person.setEmail(„“);
assertTrue((Boolean)model.isSaveButtonEnabled().getValue());
}
@Test
public void testSaveDisabledIllegalEmail(){
person.setEmail(„test@de“);
assertFalse((Boolean)model.isSaveButtonEnabled().getValue());
}
@Test
public void testEmailFieldEnabledCheckBox(){
model.isSaveButtonEnabled().setValue(false);
assertFalse((Boolean)model.isEmailButtonChecked().getValue());
assertFalse((Boolean)model.isEmailFieldEnabled().getValue());

model.isEmailButtonChecked().setValue(true);
assertTrue((Boolean)model.isEmailFieldEnabled().getValue());
}
}

Lässt man die Tests laufen, sieht man sofort:

unittests

Ah, grün!! wunderbar, bauen wir doch mal einen Bug ein, um das Ganze in Aktino zu sehen, nehmen wir an,

es schleicht sich ein Fehler im Validator ein, so dass dieser immer true liefert, egal was man für eine Emailadresse eingibt.

context.bindValue(saveButtonEnabledValue, emailFieldValue,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER),
new UpdateValueStrategy().setConverter(new Converter(
String.class, Boolean.class) {
@Override
public Object convert(Object fromObject) {
if(true)
return true;

String str = fromObject.toString();
if („“.equals(str) || str
.matches(„[a-zA-Z0-9\\.]+@[a-zA-Z0-9]+\\.[a-z]{2,3}“)) {
return true;
}
return false;
}
}));

Jetzt lassen wir die Tests nochmals laufen, und sehen wie erwartet:

unittests_fail

Dieser Artikel liefert mit Sicherheit keine Brandneuen Erkenntnisse, noch sind die Themen die er behandelet neu. Die Herangehensweise ist

aber dennoch interessant und düfrte für so manches Projekt von Belang sein. Ich hoffe, ich konnte euch ein wenig für das Thema begeistern, viel Spass damit, über Feedback würde ich mich freuen.

Den Source-Code zu der Demoanwendung findet man übrigens hier.

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!