Beschreibe hier die neue Seite. |
Verschoben aus Traits In dem obigen einführenden Papier identifizieren die Autoren mehrere Nachteile der MehrfachVererbung, die durch das Traits-Konzept behoben werden sollen: # Namens-Mehrdeutigkeiten, wenn namensgleiche Features aus verschiedenen Basisklassen geerbt werden. # Das "Diamond-Problem", wenn dieselbe Basisklasse mehrmals (auf verschiedenen Vererbungs-Pfaden) geerbt wird: Kommt diese in einem entsprechenden Objekt dann auch mehrfach als Bestandteil vor? # Das Problem, Methoden zu überschreiben, deren Basisversion man aus konkreten (nicht-abstrakten), aber nicht genau festgelegten Basisklassen erben möchte. Für die ersten beiden Probleme bietet das Traits-Konzept keine neuartige Lösung an. Namens-Mehrdeutigkeiten werden in den Sprachen mit Mehrfachvererbung, wie C++ oder Eiffel, normalerweise entweder durch eindeutige Qualifizierung mit dem Klassennamen (C++) , oder durch Umbenennungs- und Alias-Techniken (Eiffel) gelöst. In unserer SpracheLava ist die Situation bezüglich Namens-Mehrdeutigkeit bei MehrfachVererbung von vornherein dadurch entschärft, dass alle Lava-Entitäten eindeutige interne Identifier haben und daher der "sichtbare" Name gar nicht eindeutig sein muss. (Trotzdem wird auf drohende Mehrdeutigkeit warnend hingewiesen.) Das Einsetzen von Namensreferenzen erfolgt durch Auswahl aus Combo-Boxen, in denen die Namen in eindeutig qualifizierter Form stehen. Die eingesetzte Referenz ist allerdings der Kürze und besseren Lesbarkeit wegen nicht qualifiziert (könnte man aber natürlich auch dort machen); im Zweifelsfall kann man mit einem einzigen Mausklick zur Deklaration der betreffenden Entität springen und so ihre genaue Identität feststellen. Für die Lösung des "Diamond"-Problems hat man in C++ zwei Möglichkeiten: Man spezifiziert, dass man die fragliche Basisklasse "virtual" erben will; dann wird sie in den erbenden Klassen nur einmal repräsentiert, anderenfalls mehrfach. In Lava haben wir uns der Einfachheit halber auf den "virtual"-Fall beschränkt. In dem Traits-Konzept vermeidet man das Problem auf ähnliche Weise wie bei der MehrfachVererbung bei Java-Interfaces, nämlich dadurch, dass Traits und Java-Interfaces keine Member-Variablen enthalten können: sie sind reine Methoden-Kollektionen und also in der Datenstruktur eines Objektes ohnehin nicht repräsentiert. Dies hat aber den Nachteil, dass man mit ihnen keine eigenständigen Datenstrukturen mit eigenen, darauf operierenden Methoden beschreiben kann. In Java kann man, im Unterschied zu Traits, auch keine eigenständige Implementation zu einem Interface angeben, die dann von verschiedenen Klassen übernommen werden könnte, sondern die Implementierung eines Interface kann nur innerhalb jeder implementierenden Klasse (jeweils neu) erfolgen. Bei Traits kann dagegen eine eigenständige Implementation angegeben werden. Dies wird dadurch möglich, dass ein Trait ausdrücken kann, welche Methoden und Attribute es in seiner Einbettungs-Umgebung vorzufinden erwartet. Dies ist immerhin ein gravierender Fortschritt gegenüber den Java-Interfaces. Diese Ausdrucksmöglichkeit, einzelne Methoden und Attribute zu spezifizieren, die man (unabhängig von ihrer Klassenzugehörigkeit) in der Einbettungsumgebung erwartet, liefert dann einleuchtenderweise auch die Lösung des dritten Problems, dass man Methoden überschreiben möchte, deren Klassenzugehörigkeit man nicht genau festlegen möchte. In Lava lösen wir das dritte Problem einfach dadurch, dass eine Basisklasse nicht unbedingt genau festgelegt werden muss, sondern eine Parameterklasse (ein "virtual type") sein kann, die nur eventuell insofern eingeschränkt sein kann, dass sie ihrerseits wiederum aus einer gewissen (abstrakten oder konkreten) Basisklasse abgeleitet sein muss. Ganz allgemein denke ich, dass man die Programmierer nicht ohne Not mit zusätzlichen strukturellen Dimensionen und mehr als einer Vererbungs-Hierarchie konfrontieren sollte, die sie dann vor vermeidbare, künstlich provozierte Entscheidungskonflikte stellen: * Wo soll ich Klassen, wo Interfaces/Traits verwenden? * Wie strukturiere ich die Klassen-Vererbung, wie die Interface/Trait-Vererbung? * Wie beziehe ich diese beiden unabhängigen Vererbungs-Hierarchien aufeinander? Zusammenfassung der einschlägigen Lava-Eigenschaften: In Lava besteht eine Klasse aus (genau) einem Interface und einer (davon getrennt beschriebenen) Implementation, und es gibt nur einen Vererbungsbegriff, nämlich Mehrfachvererbung für Interfaces; Interfaces können eigenständig implementiert und diese Implementation von erbenden Klassen übernommen werden. Basisklassen sind im C++-Sinne immer "virtual" und müssen nicht genau festgelegt werden, sondern können Parameterklassen sein. Lava-Entitäten brauchen keinen eindeutigen Namen zu haben, sondern haben persistente eindeutige interne Identifier. --kg KategorieDiskussion |
In unserer SpracheLava ist die Situation bezüglich Namens-Mehrdeutigkeit bei MehrfachVererbung von vornherein dadurch entschärft, dass alle Lava-Entitäten eindeutige interne Identifier haben und daher der "sichtbare" Name gar nicht eindeutig sein muss. (Trotzdem wird auf drohende Mehrdeutigkeit warnend hingewiesen.) Das Einsetzen von Namensreferenzen erfolgt durch Auswahl aus Combo-Boxen, in denen die Namen in eindeutig qualifizierter Form stehen. Die eingesetzte Referenz ist allerdings der Kürze und besseren Lesbarkeit wegen nicht qualifiziert (könnte man aber natürlich auch dort machen); im Zweifelsfall kann man mit einem einzigen Mausklick zur Deklaration der betreffenden Entität springen und so ihre genaue Identität feststellen.
Für die Lösung des "Diamond"-Problems hat man in C++ zwei Möglichkeiten: Man spezifiziert, dass man die fragliche Basisklasse "virtual" erben will; dann wird sie in den erbenden Klassen nur einmal repräsentiert, anderenfalls mehrfach.
In Lava haben wir uns der Einfachheit halber auf den "virtual"-Fall beschränkt.
In dem Traits-Konzept vermeidet man das Problem auf ähnliche Weise wie bei der MehrfachVererbung bei Java-Interfaces, nämlich dadurch, dass Traits und Java-Interfaces keine Member-Variablen enthalten können: sie sind reine Methoden-Kollektionen und also in der Datenstruktur eines Objektes ohnehin nicht repräsentiert.
Dies hat aber den Nachteil, dass man mit ihnen keine eigenständigen Datenstrukturen mit eigenen, darauf operierenden Methoden beschreiben kann. In Java kann man, im Unterschied zu Traits, auch keine eigenständige Implementation zu einem Interface angeben, die dann von verschiedenen Klassen übernommen werden könnte, sondern die Implementierung eines Interface kann nur innerhalb jeder implementierenden Klasse (jeweils neu) erfolgen.
Bei Traits kann dagegen eine eigenständige Implementation angegeben werden. Dies wird dadurch möglich, dass ein Trait ausdrücken kann, welche Methoden und Attribute es in seiner Einbettungs-Umgebung vorzufinden erwartet. Dies ist immerhin ein gravierender Fortschritt gegenüber den Java-Interfaces.
Diese Ausdrucksmöglichkeit, einzelne Methoden und Attribute zu spezifizieren, die man (unabhängig von ihrer Klassenzugehörigkeit) in der Einbettungsumgebung erwartet, liefert dann einleuchtenderweise auch die Lösung des dritten Problems, dass man Methoden überschreiben möchte, deren Klassenzugehörigkeit man nicht genau festlegen möchte.
In Lava lösen wir das dritte Problem einfach dadurch, dass eine Basisklasse nicht unbedingt genau festgelegt werden muss, sondern eine Parameterklasse (ein "virtual type") sein kann, die nur eventuell insofern eingeschränkt sein kann, dass sie ihrerseits wiederum aus einer gewissen (abstrakten oder konkreten) Basisklasse abgeleitet sein muss.
Ganz allgemein denke ich, dass man die Programmierer nicht ohne Not mit zusätzlichen strukturellen Dimensionen und mehr als einer Vererbungs-Hierarchie konfrontieren sollte, die sie dann vor vermeidbare, künstlich provozierte Entscheidungskonflikte stellen: