In der objektorientieren Programmierung kommt es manchmal zu kaum zu lösenden Situationen. Beispielsweise wenn das Exemplar einer Interface-Implementierung über eine Fabrik geholt wird - der Benutzer allerdings ein geändertes Verhalten benötigt. In einer solchen Situation gibt es mehrere Lösungen. Zum Beispiel:
- Die Fabrik bietet die Möglichkeit an, Exemplare anderer Implementierungen zu erzeugen.
- Um die Instanz der Fabrik wird ein Decorator gelegt, der die entscheidenen Methoden abfängt.
Die zweite Lösung muss verwendet werden, wenn die erste nicht möglich ist. Handelt es sich dabei um komplexe APIs mit vielen Methoden, ist das Schreiben des Decorators stumpfe Arbeit.
Java bietet eine einfache und effektive Möglichkeit das Problem zu lösen. Angelpunkt ist die Klasse Proxy aus dem Package java.lang.reflect. Im Folgenden wird anhand eines kleinen, konstruierten Beispiels gezeigt, wie das Proxy-API zum Einsatz kommt.
Die PrinterFactory liefert ein Printer-Exemplar:
public final class PrinterFactory {
public static Printer newPrinter() {
return new PrinterImpl();
}
}
Das Printer Interface ist einfach gehalten. Implementierungen geben nur die jeweiligen Daten auf der Konsole aus:
public interface Printer {
void printDate();
void printTime();
void printUser();
}
Konsolenausgabe:
e178114109:~/tmp sk$ java -jar proxyexample.jar 2006-11-18 11:33:36 User: sk
Der Kunde möchte jetzt allerdings, dass vor dem Datum und der Uhrzeit auch noch eine Bezeichnung ausgegeben wird. Die Fabrik kann nicht geändert werde, sie ist z.B. eine Fremdbibliothek. In einem solchen Fall kommt das Decorator-Pattern zum Einsatz. Bei einem Interface mit 3 Methoden ist das schreiben per Hand kein Problem. Allerdings gibt es durchaus Interface mit über 100 Methoden - was wiederum von schlechem Stil zeugt.
Lösung
Zum Einsatz kommt die Proxy-Klasse. Mit ihr kann ein Exemplar eines Interfaces erzeugt werden, ohne dass eine konkrete Implementierung vorliegt. Das Proxy-Exemplar delegiert alle Aufrufe an einen InvocationHandler. Der InvocationHandler ist ebenfalls ein Interface aus java.lang.reflect und hat nur eine Methode. Die Implementierung des PrinterInvocationHandler bekommt im Konstruktor das Printer Exemplar mitgegeben, welches dekoriert werden soll:
public class PrinterInvocationHandler implements InvocationHandler {
private final Printer target;
public PrinterInvocationHandler(final Printer printer) {
this.target = printer;
}
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
return method.invoke(this.target, args);
}
}
Die Implementierung der invoke-Methode ist die einfachste mögliche Art. Sie ruft einfach nur im zu dekorierenden Objekt die übergebene Methode auf.
Zu dekorierende Methoden müssen im InvocationHandler abgefangen werden. Das übergebene Method Objekt liefert hierfür alle nötigen Informationen. Um die beiden Printer-Methoden printDate und printTime abzufangen, wird nur der Name benötigt. Dieser Wird in eiem kleinen if-else-Block geprüft. Stimmt der Name überein, wird ein erklärender Text ausgegeben. Anschliessend wir die Originalmethode aufgerufen.
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
final String name = method.getName();
if ("printDate".equals(name)) {
System.out.print("Date: ");
} else if ("printTime".equals(name)) {
System.out.print("Time: ");
}
return method.invoke(this.target, args);
}
Die Verwendung des Konstruktes ist ebenfalls sehr einfach:
final Printer printer = PrinterFactory.newPrinter();
final PrinterInvocationHandler handler =
new PrinterInvocationHandler(printer);
final Thread thread = Thread.currentThread();
final ClassLoader cl = thread.getContextClassLoader();
final Class<?>[] clazz = new Class<?>[] {Printer.class};
final Printer decorator = (Printer) Proxy.newProxyInstance(cl, clazz, handler);
decorator.printDate();
decorator.printTime();
decorator.printUser();
Fazit
Mit wenigen Zeilen Auwand können komplexe Interfaces dekoriert werden, um das Verhalten von Objekten zu ändern.
Download
Funktionsfähiger Code mit Sourcen aus dem Beispiel: proxydecorator.jar
Ein Dashboard Widget welches endlich klar macht, warum es der Trend zum DoppelkernQuadcore-Rechner geht: Desksaver Plus
