Das Fluent Interface im Kontext von Vererbung und Polymorphie

Gepostet am: 12. April 2016

Von

Der Einsatz von Fluent Interfaces und Method Chaining erfreut sich großer Beliebtheit und findet immer mehr Einzug in die APIs aktueller Produkte und Bibliotheken. Solche Funktionalitäten zu implementieren kann aber mitunter recht komplex sein und kollidiert mit manchen althergebrachten Paradigmen und Best-Practices. Dieser Beitrag beschreibt die Probleme, die vor allem im Kontext von Vererbung und Polymorphie auftreten.

Vererbung und Polymorphie

Die Konzepte der Vererbung und der damit einhergehenden Polymorphie sind für Entwickler, die Wert auf Objektorientierung legen, seit langer Zeit ganz selbstverständliche Werkzeuge, um Wiederverwendung zu erreichen, die verhasste Duplikation bei der Entwicklung zu vermeiden und damit die Wartbarkeit des Codes zu erhalten und zu verbessern – zumindest bei der Arbeit mit Java oder anderen Sprachen mit einem Schwerpunkt auf OO.

Allerdings gibt es viele Beispiele, bei denen die entstandenen Superklassen mehr einem Sammelkasten nützlicher Funktionen für alle ihre Subklassen ähneln und der Aspekt der Polymorphie dabei viel zu kurz kommt. Ähnlich wie bei der Entwicklung gegen Schnittstellen soll man auch hier davon profitieren, dass Instanzen von Subklassen überall dort eingesetzt werden können, wo ihre Superklassen erwartet werden. Und wenn es nur um die Wiederverwendbarkeit von Funktionen geht, so bietet sich die Delegation als passende Alternative zur Vererbung an. Dabei ist es jedem überlassen, ob adhoc-Instanzen oder statische Methoden an abstrakten Helferklassen bevorzug werden.

Erweiterung von Klassen mit Fluent Interface

Nun bin ich kürzlich auf ein Problem gestoßen, bei dem ich mit einem Fluent Interface arbeiten wollte, aber die gewohnte Nutzung von Polymorphie nicht so recht funktioniert hat.

Ich habe es mit mehreren Klassen zu tun, die alle bestimmte Werte als Strings serialisieren sollen und dafür jeweils einen StringBuilder verwenden. Ähnlich dem ToStringHelper der guava Bibliothek sollen die Werte jeweils mit einer Beschreibung und bestimmten Verbindungszeichen aufgelistet werden, aber jeweils nur, wenn sie nicht Null sind. Dafür gibt es eine Methode in einer gemeinsamen Superklasse, welche die Beschreibung, den Wert und den StringBuilder entgegennimmt.

Neben den Werten, die von der addValueIfNotNull() Methode eingefügt werden, gibt es teilweise noch einige direkte Aufrufe am Fluent Interface des StringBuilder und ich finde, dass es besser wäre, dieses Konzept hier durchgängig anzuwenden. Also ist der naheliegende Schritt eine Erweiterung der Klasse StringBuilder, um die Methode appendIfNotNull(String caption, Object value) in das Fluent Interface zu integrieren und intern die vielen überschriebenen append(type) Methoden zu nutzen, die bereits vorhanden sind. Natürlich soll dieser erweiterte StringBuilder auch weiterhin überall eingesetzt werden können, wo bisher mit StringBuilder-Instanzen gearbeitet wurde. Vererbung und Polymorphie sollten das ja ohne Weiteres ermöglichen.

StringBuilder kann nicht erweitert werden

Leider musste ich feststellen, dass die Klasse StringBuilder als final deklariert ist. Es gibt zwar noch eine abstrakte Superklasse (die auch von StringBuffer erweitert wird) aber diese ist ebenfalls nicht zweckdienlich.

Bei genauerer Betrachtung wird klar, warum der StringBuilder final ist, denn wie ich schon bei der Arbeit mit dem Builder Pattern gelernt habe, lässt sich ein Fluent Interface nur sehr schlecht mit Vererbung kombinieren und führt zu einer nicht ohne weiteres erweiterbaren Typhierarchie. In diesem Fall geht es um ein naives Fluent Interface, also ohne Grammatik, bei dem alle dafür relevanten Methoden immer denselben Typ zurückliefern. Zusätzliche Methoden (z.B. long StringBuilder.length()) lassen wir hier außen vor. Jede Methode des StringBuilder liefert auch einen solchen zurück. Würde man jetzt append(String) an einem CustomStringBuilder aufrufen, käme kein CustomStringBuilder mehr zurück und die zusätzlichen Methoden wären nicht mehr verfügbar.

Subklasse als Typparameter in allen Superklassen

Um die Methoden einer Superklasse so zu gestalten, dass sie wieder den Typ der konkreten Subklasse zurückliefern, muss man dem Curiously-Recurring Generics Pattern folgen. Dabei erhalten alle Superklassen einen Typparameter F, der von allen konkreten Subklassen mit sich selbst belegt werden muss. Dann kann die Signatur der Superklassen diesen Typparameter als Rückgabetyp verwenden. Zusätzlich kann man bereits eine Methode F getThis() bereitstellen, in der ein ungeprüfter Cast der aktuellen Instanz auf F durchgeführt wird. Eine Konsequenz davon ist, dass alle gemeinsamen Superklassen in einer solchen Typhierarchie abstrakt sein müssen und alle konkreten Subklassen final. Leider bietet die Sprachdefinition von Java keine Möglichkeit wirklich sicherzustellen, dass alle abstrakten Klassen einer Typhierarchy den Typparameter an ihre Superklassen durchreichen und alle konkreten Subklassen ihn auch wirklich mit sich selbst belegen und nicht mit einer anderen Subklasse.

Da nun keine Vererbungsbeziehung zwischen den konkreten Subklassen bestehen kann, ist der Einsatz von Polymorphie hier etwas eingeschränkt. Man kann also vornehmlich die Wiederverwendung gemeinsamer Methoden durch Vererbung erreichen, aber nicht die Erweiterung von bereits instanziierbaren Klassen wie dem StringBuilder.

Ganz davon abgesehen, dass die Klassenhierarchie des StringBuilder in der Java Runtime nicht in dieser Form vorliegt, hätte ich auch nicht die Möglichkeit gehabt, StringBuilder als abstrakt zu erklären und eine neue Ebene mit zwei neuen konkreten Subklassen einzuarbeiten. Außerdem ist damit immer noch keine Polymorphie zwischen StringBuilder und CustomStringBuilder möglich.

Letztlich bleibt in diesem Fall nur die Delegation von einem CustomStringBuilder zu einer internen StringBuilder-Instanz ohne jede Typbeziehung als Lösung. Solange ich dieses Objekt nur lokal verwende und nicht an andere Akteure übergebe, die einen StringBuilder erwarten, kann ich wenigstens ein ununterbrochenes Fluent Interface genießen.

We’re hiring!

Tapetenwechsel gefällig? Wir sind auf der Suche nach begeisterten Software-Entwicklern, die uns im Umfeld von Java, .NET und JavaScript unterstützen und auch von extravaganteren Sprachen wie Go, Elixir und Clojure nicht zurückschrecken. Jetzt Bewerben!

Weiterlesen

Weitere Information zu unseren Leistungen gibt es auf unserer Website, der direkte Kontakt zu uns ist telefonisch unter +49 721 619 021-0 oder jederzeit per E-Mail an list-blog@inovex.de möglich.

2017-11-28T17:23:26+00:00