Multidispatch (Multimethoden): verpassen wir etwas

Objektorientierte Programmiersprachen werden nach meiner Meinung eigentlich erst wirklich objektorientiert, wenn sie Polymorphie unterstützen. Das bedeutet, dass ein Stück Progammcode mit einem Objekt gewisse Manipulationen durchführt und dabei Methoden des Objekts aufruft. Diese tun etwas verschiedenes, je nach dem welches Objekt hier angesprochen wird, denn es ist eine Eigenschaft des Objekts, welche Methode wirklich aufgerufen wird. In vielen Objektorientierten Programmiersprachen, wie zum Beispiel Java oder C# oder C++ ist das so gelöst, dass das Objekt einer Klasse angehört und die Klasse die Methodenimplementierung enthält, aber es können natürlich auch Methoden direkt an einzelnen Objekten aufgehängt sein. Für die Freunde von C# und C++ sei daran erinnert, dass „virtual“ hier nicht fehlen darf.

Das ist alles wunderbar und man kann damit sehr viel anfangen. Nun wird die heile Welt gestört, wenn man sich irgendwelche Konzepte aus nie wirklich Mainstream gewordenen Programmiersprachen wie Common Lisp mit CLOS anschaut, wo es sogenanntes Multidispatch gibt. Die aufgerufene Methode hängt also nicht nur vom ersten speziellen Parameter, also dem Objekt, das oft vor dem „.“ steht, ab, sondern von allen Parametern. Das macht es unübersichtlich und niemand braucht so etwas, könnte man meinen. Das mag ja alles stimmen, aber wir werden doch mit Situationen konfrontiert, wo das Thema sehr relevant wird.

Nehmen wir an, man führt einen neuen numerischen Typ ein, z.B. Rational für rationale Zahlen \Bbb Q=\{ \frac{a}{b} : a, b \in \Bbb Z \wedge b > 0 \}. Nun braucht man Methoden für die Grundrechenarten, was sehr schön funktioniert, wenn man z.B. zwei rationale Zahlen addieren will. Nun soll es aber auch funktionieren, eine ganze Zahl zu einer rationalen Zahl zu addieren. Dafür kann man die „+“-Methode oder weniger schön die „add“-Methode noch einmal mehr überladen und einen ganzzahligen Parameter verstehen oder innerhalb der Methode diesen Fall zur Laufzeit erkennen. Der erste Ansatz greift leider zu kurz, weil wegen Vererbungshierarchie und gerade wegen der Polymorphie der genaue Type zur Compilezeit nicht bekannt ist und daher auf die allgemeine Methode, etwa so etwas wie

+(x : Numeric) ,

zurückgefallen wird, die wiederum abhängig vom realen Typ von x auf die richtige Methode oder den richtigen Methodenteil führen sollte.
Das ist alles noch gut machbar, man darf es nur nicht vergessen.

Aber was passiert nun, wenn q rational und n ganzzahlig ist und wir nicht q+n, sondern n+q schreiben? Nun müsste man Integer (oder BigInteger) oder was auch immer der Typ ist, hoffentlich ohne diesen absurden Überlauf bei 2^{31}-1 wie die java-ints, nachträglich beibrigen, rational zu verstehen. In Ruby kann man zumindest noch nachträglich die Methode + von Integer ändern und durch eine eigene ersetzen, die auch Rational versteht, aber der Ansatz stößt irgendwann an Grenzen. In Ruby behilft man sich oft damit, dass die +-Methode der linken Seite eine „coerce“-Methode der rechten Seite mit der linken Seite als Parameter aufruft, um Konvertierungen zu machen. Damit kommt man recht weit, aber man muss sehr genau hinschauen, was man tut.

Nun kommt sicher der Einwand, dass Operatorüberladen sowieso „böse“ ist. Das hilft aber hier nichts, die Probleme treten in identischer Form auf, wenn man es „add“ statt „+“ nennt.

Der zweite Einwand ist vielleicht, dass man nicht jeden Tag neue numerische Typen schreibt. Aber man schreibt doch hoffentlich ab und zu neue Klassen und nutzt Vererbung. Da muss man dann eine equals-Methode oder so etwas ähnliches implementieren. Und schon ist das Problem sehr relevant. In der Regel habe ich das dadurch gelöst, dass equals zuerst die Klassen von linker Seite (self oder this) und rechter Seite vergleicht und nur wenn die gleich sind, überhaupt weiter vergleicht. Das ist meistens ein guter Ansatz. Aber manchmal muss man wirklich Gleichheit zwischen Objekten zulassen, die nicht derselben Klasse angehören. Dann ist es schon eine Herausforderung, das korrekt und halbwegs solide zu lösen. Man sieht also, dass wir hier wirklich etwas verpassen… Aber solange man die Konzepte kennt, kann man sie grundsätzlich in jeder Programmiersprache anwenden, der Aufwand ist nur etwas größer.

Java ist hier tatsächlich etwas schlechter gestellt als z.B. Scala oder Ruby, weil man sehr schlechte Möglichkeiten hat, die eingebaute equals-Methode zu ersetzen. Während es bei SortedSet die Wahlmöglichkeit gibt, Comparable oder einen Comparator zu verwenden, setzen HashSet und HashMap fast immer auf die interne hashCode() und equals()-Methode und man muss sich mit Wrappern oder geeigneten inneren Klassen behelfen, um eine zweites Interface für dasselbe Objekt mit anderen equals()- und hashCode()-Methoden zu liefern.

Share Button

Beteilige dich an der Unterhaltung

1 Kommentar

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*