Kann das Diamant-Problem wirklich gelöst werden?
Ein typisches problem in OO-Programmierung ist das Diamant-problem. Ich habe Elternteil der Klasse A mit zwei sub-Klassen B und C. A hat eine abstrakte Methode, B und C umzusetzen. Jetzt habe ich eine sub-D-Klasse, erbt von B und C. Das Diamant-problem ist nun, was die Umsetzung ist D verwenden, die ein B oder ein C?
Leute behaupten, Java kennt keine Diamant-problem. Ich kann nur Mehrfachvererbung mit interfaces und da Sie keine Implementierung haben, habe ich kein diamond-problem. Ist das wirklich wahr? Ich denke nicht so. Siehe unten:
[entfernt Fahrzeug Beispiel]
Ist ein Diamant-problem ist immer die Ursache von schlechtem Klassen-design und etwas, was weder der Programmierer noch der compiler braucht, um zu lösen, weil es nicht in den ersten Platz?
Update: Vielleicht ist mein Beispiel war schlecht gewählt.
Sehen Sie dieses Bild
Natürlich kann man virtuelle Person in C++ und somit haben Sie nur eine Instanz von person in Erinnerung, aber das eigentliche problem besteht IMHO. Wie würden Sie implementieren getDepartment() für GradTeachingFellow? Bedenken, er könnte student in einer Abteilung und lehren in einer anderen. So können Sie entweder eine Abteilung oder das andere; es ist keine perfekte Lösung des Problems und die Tatsache, dass keine Umsetzung könnte vererbt werden (z.B. Schüler und Lehrer könnten beide Schnittstellen) scheint nicht das problem zu lösen für mich.
InformationsquelleAutor der Frage Mecki | 2009-02-18
Du musst angemeldet sein, um einen Kommentar abzugeben.
Was Sie sehen, ist, wie Verstöße gegen die Liskov-Substitution-Prinzip machen es wirklich schwer zu haben, eine funktionierende, logische Objekt-orientierte Struktur.
Im Grunde, (öffentlich) Vererbung sollte nur der schmale Zweck der Klasse ist, nicht verlängern. In diesem Fall, durch Erben von zwei Arten von Fahrzeugen Sie sind in der Tat die Verlängerung der Zweck, und wie Sie bemerkt, es funktioniert nicht - bewegen sollte ganz anders werden für ein Wasser-Fahrzeug als für ein road-Fahrzeug.
Sie könnte stattdessen Aggregat ein Wasser-Fahrzeug und eine Boden-Fahrzeug-Objekt in Ihrem Amphibienfahrzeug und entscheiden extern, welche von den beiden wird es werden je nach Aktueller situation.
Alternativ könnte man entscheiden, dass das "Fahrzeug" - Klasse ist unnötig Generika und Sie werden über getrennte Schnittstellen für die beiden. Das löst nicht das problem für Ihre Amphibienfahrzeug auf seine eigene, obwohl - wenn Sie rufen Sie die Bewegung, die die Methode "move" in beide Schnittstellen haben, werden Sie noch Probleme haben. Also ich würde vorschlagen, aggregation statt Vererbung.
InformationsquelleAutor der Antwort Joris Timmermans
C#
hat explizite interface-Implementierung teilweise den Umgang mit diesem. Zumindest in dem Fall, wo Sie haben eine der zwischengeschalteten Schnittstellen (ein Objekt davon..)Allerdings, was wohl passiert ist, dass die AmphibianVehicle Objekt weiß, ob er gerade auf dem Wasser oder zu land, und das richtige tut.
InformationsquelleAutor der Antwort Douglas Leeder
In deinem Beispiel
move()
gehört zu denVehicle
- Schnittstelle und definiert den Vertrag "gehen von Punkt A zu Punkt B".Wenn
GroundVehicle
undWaterVehicle
erweiternVehicle
Sie Erben implizit dieses Vertrages (Analogie:List.contains
erbt seinen Vertrag ausCollection.contains
- stellen Sie sich vor, wenn es etwas anderes angegeben!).So, wenn der Beton
AmphibianVehicle
implementiertmove()
der Vertrag, es muss wirklich RespektVehicle
's. Es ist ein Diamant, aber den Vertrag nicht ändern, ob Sie erwägen, eine Seite, die Diamant-oder die andere (oder ich würde anrufen, dass ein design-problem).Wenn Sie den Vertrag von "verschieben" zu verkörpern, der Begriff der Oberfläche, nicht definieren, es in einer Art, die nicht Modell dieser Vorstellung:
(Analogie:
get(int)
's Vertrag definiert, durch dieList
- Schnittstelle. Könnte es nicht vielleicht sein, definiert durchCollection
als Sammlungen sind nicht notwendigerweise geordnete)Oder umgestalten generische Schnittstelle zum hinzufügen von Begriff:
Das einzige problem sehe ich bei der Implementierung mehrerer Schnittstellen ist bei beiden Methoden von völlig unabhängigen Schnittstellen passieren zu kollidieren:
Dann aber wäre das nicht ein Diamant. Mehr wie eine upside-down-Dreieck.
InformationsquelleAutor der Antwort Olivier
ja, denn Sie Steuern die Implementierung der Schnittstelle in D. Die Signatur der Methode ist die gleiche zwischen den beiden Schnittstellen (B/C), und zu sehen, wie die Schnittstellen haben keine Implementierung, keine Probleme.
InformationsquelleAutor der Antwort Steven Evers
Ich weiß nicht Java, aber ob die Schnittstellen B und C Erben von der Schnittstelle A -, und D-Klasse implementiert die Interfaces B und C, dann D-Klasse nur implementiert die move-Methode einmal, und es ist A. Bewegen, die es umsetzen sollten. Wie Sie sagen, der compiler hat kein problem mit dieser.
Aus dem Beispiel, das Sie geben in Bezug auf die AmphibianVehicle Umsetzung GroundVehicle und WaterVehicle, dies könnte leicht gelöst werden, indem das speichern einer Referenz auf die Umwelt, zum Beispiel, und freilegen einer Oberfläche Eigenschaft auf die Umwelt, dass die Move-Methode von AmphibianVehicle würde inspizieren. Keine Notwendigkeit, für diese als parameter übergeben werden.
Du hast Recht in dem Sinne, dass es etwas ist, was für den Programmierer zu lösen, aber es ist zumindest kompiliert und sollte nicht sein 'problem'.
InformationsquelleAutor der Antwort Patrick McDonald
Gibt es kein Diamond-Problem mit Interface-Vererbung.
Mit Klasse-basierte Vererbung, die mehrere erweiterte Klassen können unterschiedliche Implementierung einer Methode, so gibt es Unklarheit darüber, welche Methode tatsächlich zur Laufzeit verwendet.
Mit Interface-Vererbung gibt es nur eine Implementierung der Methode, so dass es keine Zweideutigkeit.
EDIT: Eigentlich das gleiche würde gelten für Klasse-basierte Vererbung für die deklarierten Methoden als Abstract in der Superklasse.
InformationsquelleAutor der Antwort Clayton
Würden Sie unterstützen die Implementierung geeignet für
AmphibianVehicle
s.Wenn ein
GroundVehicle
bewegt sich "anders" (D. H. andere Parameter akzeptiert, als eineWaterVehicle
), dannAmphibianVehicle
erbt zwei verschiedene Methoden, eine für Wasser, eine für auf dem Boden. Wenn dies nicht möglich ist, dannAmphibianVehicle
sollte nicht Erben ausGroundVehicle
undWaterVehicle
.Wenn es aufgrund von schlechtem Klassen-design, ist es der Programmierer braucht, um es zu lösen, da der compiler nicht weiß, wie.
InformationsquelleAutor der Antwort eljenso
Das problem, das Sie sehen, in der Schüler /Lehrer-Beispiel ist einfach, dass dein Datenmodell falsch ist, oder zumindest nicht ausreichend.
Die Schüler und Lehrer der Klassen sind die Vermischung von zwei verschiedenen Konzepten von "Abteilung" mit den gleichen Namen für jeden von Ihnen. Wenn Sie verwenden möchten diese Art der Vererbung, sollten Sie stattdessen definieren, so etwas wie "getTeachingDepartment" Lehrer und "getResearchDepartment" Student. Ihre GradStudent, die sowohl ein Lehrer und ein Schüler, implementiert.
Natürlich, angesichts der Realitäten der graduate school, auch dieses Modell ist wahrscheinlich nicht ausreichend.
InformationsquelleAutor der Antwort asdfwera
Ich glaube nicht, dass die Verhinderung der konkreten mehrfache Vererbung ist in Bewegung das problem, dass der compiler den Programmierer. In dem Beispiel, das Sie Gaben, wäre es immer noch notwendig sein, für die der Programmierer angeben, um dem compiler die Implementierung zu verwenden. Es gibt keinen Weg, der compiler könnte erraten, welche die richtige ist.
Für Ihre Amphibien-Klasse, könnte man hinzufügen, eine Methode, um zu bestimmen, wenn das Fahrzeug zu Wasser oder zu land und die Nutzung dieser entscheidet über die move-Methode verwenden. Dies wird die Erhaltung der parameterlosen Schnittstelle.
InformationsquelleAutor der Antwort Dave Turvey
In diesem Fall wäre es wahrscheinlich am günstigsten zu haben AmphibiousVehicle eine Unterklasse von Fahrzeug (Geschwister von WaterVehicle und LandVehicle), so ganz zu vermeiden das problem in den ersten Platz. Es wäre wahrscheinlich mehr in jedem Fall richtig, da ein Amphibienfahrzeug ist nicht ein Wasser-Fahrzeug oder ein land Fahrzeug, es ist eine andere Sache.
InformationsquelleAutor der Antwort Kibbee
Wenn move() semantische Unterschiede basierend auf Boden oder Wasser (eher als GroundVehicle und WaterVehicle Schnittstellen sowohl sich selbst als Erweiterung GeneralVehicle Schnittstelle, die den move () - Signatur), aber es wird erwartet, dass Sie mix und match-Boden und Wasser-Implementierer dann ist dein Beispiel ist wirklich einer der schlecht entwickelten api.
Das eigentliche Problem ist, wenn der name Kollision, effektiv, zufällig.
zum Beispiel (sehr synthetisch):
Wenn Sie eine Jacke, die Sie wünschen, werden beide ein Kleid und zerstörbare Sie haben einen Namen Kollision auf der (berechtigterweise genannt) tragen Methode.
Java hat keine Lösung für dieses (das gleiche gilt für mehrere andere statisch typisierten Sprachen). Dynamische Programmier-Sprachen haben ein ähnliches Problem, auch ohne das diamond oder Erbe, es ist nur ein name Kollision (eine inhärente Problem mit Duck Typing).
.Net hat das Konzept der explizite Schnittstellenimplementierungen wobei eine Klasse definieren können zwei Methoden den gleichen Namen und Signatur so lange, wie beide markiert sind, zu zwei verschiedenen Schnittstellen. Die Bestimmung der jeweiligen Methode zu nennen ist, basierend auf compile-Zeit bekannt-interface die variable (oder wenn durch Reflexion von der expliziten Wahl der angerufene)
Dass vernünftige, wahrscheinlich, Kollisionen mit Namen, die sind so schwer zu bekommen und, dass java wurde nicht an den Pranger gestellt als unbrauchbar für die Bereitstellung nicht die explizite Schnittstellenimplementierungen würde vorschlagen, dass das problem nicht eine Bedeutung für die Reale Welt verwenden.
InformationsquelleAutor der Antwort ShuggyCoUk
Ich weiß, es ist eine bestimmte Instanz, nicht eine Allgemeine Lösung, aber es klingt wie Sie brauchen ein zusätzliches system, um festzustellen, Staat und entscheiden, welche Art von move() das Fahrzeug führen würde.
Es scheint, dass im Falle einer amphibischen Fahrzeug, der Anrufer (sagen wir mal "throttle") hätte keine Ahnung von dem Zustand des Wasser/Boden, aber ein intermediate-Bestimmung-Objekt wie "übertragung" in Verbindung mit "traction control" könnte es herausfinden, dann rufen Sie move() mit dem richtigen parameter bewegen(Räder) oder verschieben(prop).
InformationsquelleAutor der Antwort willoller
Das problem wirklich existiert. In der Stichprobe der AmphibianVehicle-Klasse benötigen weitere Informationen - die Oberfläche. Meine bevorzugte Lösung ist das hinzufügen einer getter - /setter-Methode für die AmpibianVehicle-Klasse, um die Oberfläche verändern-Mitglied (enumeration). Die Umsetzung könnte jetzt das richtige zu tun und der Klasse bleiben gekapselt.
InformationsquelleAutor der Antwort p2u
Können Sie das Diamant-problem in C++ erlaubt Mehrfachvererbung), aber nicht in Java oder in C#. Es gibt keine Möglichkeit für den Erben von zwei Klassen. Zwei Schnittstellen implementiert, die mit der gleichen Methode, die Erklärung nicht impliziert in dieser situation, da die konkrete Implementierung kann nur in der Klasse.
InformationsquelleAutor der Antwort Victor Rodrigues
Das Diamant-problem in C++ ist bereits gelöst: Einsatz der virtuellen Vererbung. Oder noch besser, seien Sie nicht faul und zu Erben, wenn es nicht nötig ist (oder unvermeidlich). Wie für das Beispiel, das Sie Gaben, könnte dies gelöst werden, indem Sie neu definieren, was es bedeutet, in der Lage zu fahren, auf der Erde oder im Wasser. Hat die Fähigkeit, sich im Wasser zu bewegen wirklich zu definieren, eine Wasser-basierte KFZ-oder ist einfach nur etwas, das Fahrzeug ist in der Lage, das zu tun? Ich würde eher denken, dass die move () - Funktion, die Sie beschrieben hat, irgendeine Art von Logik dahinter, die sich fragt "wo bin ich und kann ich eigentlich hierher gezogen?" Das entspricht einem
bool canMove()
Funktion, die abhängig vom aktuellen Zustand und die inhärenten Fähigkeiten des Fahrzeugs. Und Sie brauchen keine mehrfache Vererbung um dieses problem zu lösen. Verwenden Sie einfach ein mixin, das beantwortet die Frage in verschiedener Weise, je nachdem, was möglich ist und nimmt die übergeordnete Klasse als template-parameter, damit die virtuelle Funktion canMove wird sichtbar durch die Vererbungskette.InformationsquelleAutor der Antwort Michel
Eigentlich, wenn
Student
undTeacher
sind beide Schnittstellen, tut es in der Tat Ihr problem lösen. Wenn Sie Schnittstellen, danngetDepartment
ist einfach eine Methode, die erscheinen müssen, in IhremGradTeachingFellow
Klasse. Die Tatsache, dass sowohl dieStudent
undTeacher
Schnittstellen erzwingen kann, dass die Schnittstelle nicht zu einem Konflikt überhaupt. Die UmsetzunggetDepartment
in IhremGradTeachingFellow
Klasse würde draussen beide Schnittstellen ohne Diamant-problem.ABER, wie bereits in einem Kommentar, bedeutet dies nicht lösen das problem der
GradStudent
Lehre/eine TA in einer Abteilung, und als student in einem anderen. Kapselung ist wahrscheinlich das, was Sie hier wollen:Ist es egal, als
GradStudent
nicht konzeptionell "haben" ein Lehrer-und Schüler - encapsulation ist immer noch der Weg zu gehen.InformationsquelleAutor der Antwort B T
Schnittstelle Ein
{
void add();
}
interface B extends A
{
void add();
}
interface C extends A
{
void add();
}
class D implements B,C
{
}
Ist es nicht Diamant-problem.
InformationsquelleAutor der Antwort Nirupma