Ich bin kürzlich über ein Stück Code gestolpert, der mich zum nachdenken brachte. Um zu illustrieren, welcher Umstand dafür verantwortlich war, benötige ich diesemal am schon Anfang ein paar Zeilen Code. Die folgende Hilfsklasse dient nur dazu, bei dem einzig möglichen Methodenaufruf eine Liste von Daten zu erzeugen:
package de.wicketpraxis.web.blog.pages.migration.model.property;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
public class Generator implements Serializable
{
public List<String> findAll()
{
return Arrays.asList("fun","with","wicket");
}
}
Der Generator dient hier als Platzhalter für eine wie auch immer gelagerte Datenschicht.
package de.wicketpraxis.web.blog.pages.migration.model.property;
import java.util.List;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import de.wicketpraxis.wicket.model.Models;
import de.wicketpraxis.wicket.model.transformation.Function1;
public class UseOrNotUsePropertyModelPage extends WebPage
{
public UseOrNotUsePropertyModelPage()
{
final Generator gen=new Generator();
IModel<List<String>> model = new LoadableDetachableModel<List<String>>()
{
protected List<String> load()
{
return gen.findAll();
};
};
model=new PropertyModel<List<String>>(gen,"findAll");
add(new ListView<String>("list",model)
{
@Override
protected void populateItem(ListItem<String> item)
{
item.add(new Label("value",item.getModel()));
}
});
}
}
Da es nicht meine Art ist, unvollständigen Code abzubilden, kommt auch hier erst das Markup und dann die Erklärung:
<html>
<head>
<title>PropertyModel Usage Page</title>
</head>
<body>
<h1>Liste</h1>
<ul>
<li wicket:id="list"><span wicket:id="value"></span></li>
</ul>
</body>
</html>
Was ist an diesem Beispiel bemerkenswert?
Am Anfang erzeuge ich ein Model über eine LoadableDetachableModel-Implementierung, die die Daten aus dem Methodenaufruf zurück gibt. Das ist nichts neues und wurde bereits oft behandelt. Diese Model-Instanz wird in der folgenden Zeile mit einer PropertyModel-Instanz überschrieben. Es ist unstrittig, dass dieser Ansatz funktioniert. Die Methode wird per Reflection aufgerufen und die Werte werden per getObject() zurückgeliefert. Doch wo liegt das Problem?
Refactoring
Die Zeile Code, die man erhält, wenn man das PropertyModel benutzt und die Methode aufzurufen ist angenehm kurz. Aber diese Kürze gibt es nicht umsonst, sondern man muss dafür zahlen. In Bezug auf das Thema Refactoring sogar zwei mal. Das erste Problem ist offensichtlich: Wenn sich der Methodenname ändert (oder Parameter hinzukommen) wird der Aufruf per Reflection nicht mehr funktionierten (oder sogar eine falsche Methode aufrufen), und man erhält zur Laufzeit eine Fehlermeldung.
Der zweite Punkt ist weniger offensichtlich, aber vielleicht noch schwer wiegender, weil zur Laufzeit der Fehler an einer anderen Stelle zu Tage tritt: Der Rückgabewert der Methode wird angepasst. Der Fehler würde in unserem Fall dann vermutlich nur in der Darstellung der ListView auftreten, so dass statt "fun with wicket" möglicherweise "SomeData@65b778 SomeData@32ac12" angezeigt wird. Es kommt also noch nicht mal zu einem Laufzeitfehler.
Performance
In einem anderen Bereich (der in unserem Beispiel nebensächlich ist) funktioniert die PropertyModel-Variante nicht wie erwartet. Wenn die Methode getObject() aufgerufen wird, wird immer die entsprechende Methode per Reflection aufgerufen. Das bedeutet, die Ergebnisse werden nicht wie beim LoadableDetachedModel zwischengespeichert. Würde ich das Model an zwei ListView-Komponenten binden, würde die Liste zweimal erzeugt werden. Je nach Aufwand der Erzeugung ist das ein eher unerwünschtes Ergebnis. Außerdem kann es natürlich dazu kommen, dass sich die jeweiligen Rückgabewerte unterscheiden, so dass der Nutzer im Zweifel inkonsistente Daten angezeigt bekommt.
Das PropertyModel ist nützlich
Man sollte nach dem Lesen der Zeilen die Verwendung des PropertyModel nicht verbieten, denn Aufwand und Nutzen sind für die Entstehung dieser Model-Klasse verantwortlich. Um die Eingaben aus einem Formularfeld in einem Attribut einer JavaBean zu speichern sollte man das PropertyModel einsetzen, weil der Mehraufwand für eine typsichere (und damit durch normale Refactoring-Operationen greifbare) Variante enorm hoch ist. Ich würde mir wünschen, das Java Sprachmittel bereitstellt, die es ermöglicht, Methoden nicht nur per Reflection zu ermitteln, so dass man gerade für diesen Fall kompakten und trotzdem typsicheren Code schreiben kann. Bis dahin sollte man diese Model-Klasse überlegt verwenden, dann kann eigentlich gar nicht soviel schief gehen.