Frage

Ein typisches Problem in der OO-Programmierung ist der Diamant Problem. Ich habe eine Elternklasse mit zwei Unterklassen B und C. A hat eine abstrakte Methode, B und C, um es umzusetzen. Jetzt habe ich eine Unterklasse D, die von B erbt und C. Der Diamant Problem ist jetzt, was für die Umsetzung soll D Verwendung, die eines von B oder ein von C?

Leute behaupten Java kein Diamant Problem kennt. Ich kann nur die Mehrfachvererbung mit Schnittstellen hat und da sie keine Implementierung haben, ich habe keinen Diamanten Problem. Ist das wirklich wahr? Ich glaube nicht. Siehe unten:

[removed Fahrzeug Beispiel]

Ist ein Diamant-Problem immer die Ursache für schlechte Klasse Design und etwas weder Programmierer noch Compiler lösen muss, weil es nicht in erster Linie existieren sollte?


Update:. Vielleicht war mein Beispiel schlecht gewählt

Sehen Sie dieses Bild


(Quelle: suffolk.edu )

Natürlich können Sie Person virtuellen machen in C ++ und damit werden Sie nur eine Instanz der Person in Erinnerung haben, aber das eigentliche Problem weiterhin besteht IMHO. Wie würden Sie getDepartment () für GradTeachingFellow implementieren? Betrachten Sie, er könnte seinen Schüler in einer Abteilung und lehrt in einem anderen. So können Sie entweder zurückgeben eine Abteilung oder die andere; es gibt keine perfekte Lösung für das Problem und die Tatsache, dass keine Umsetzung vererbt werden könnte (zum Beispiel Student and Teacher beide Schnittstellen sein könnten) scheint nicht das Problem für mich zu lösen.

War es hilfreich?

Lösung

Was Sie sehen, ist, wie Verstöße gegen die Liskov Substitutionsprinzip es wirklich schwer zu machen hat eine Arbeits, logische objektorientierte Struktur.
Grundsätzlich (öffentlich) Vererbung sollte nur den Zweck der Klasse verengt, erweitert es nicht. In diesem Fall kann durch zwei Arten von Fahrzeugen vererben Sie sind in der Tat den Zweck erstreckt, und wie Sie bemerkt haben, funktioniert es nicht -. Schachzug für ein Wasserfahrzeug ganz anders sein sollte als für ein Straßenfahrzeug
Sie könnten stattdessen ein Wasserfahrzeug und ein Bodenfahrzeug Objekt in Ihrem Amphibienfahrzeug aggregieren und entscheiden, von außen, welche der beiden auf die aktuelle Situation angemessen sein wird.
Alternativ können Sie entscheiden, dass das „Fahrzeug“ Klasse unnötig generisch ist und werden Sie separate Schnittstellen für beide haben. Das bedeutet nicht, das Problem für Ihr Amphibienfahrzeug auf seinem eigenen obwohl lösen - wenn Sie die Bewegung Methode „move“ rufen in beiden Schnittstellen, werden Sie immer noch Probleme haben. So würde ich vorschlagen, Aggregation statt Vererbung.

Andere Tipps

C# hat explizite Schnittstellenimplementierung teilweise damit umgehen. Zumindest in dem Fall, in dem Sie eine der Zwischen Schnittstellen haben (ein Objekt davon ..)

Doch wahrscheinlich das, was passiert ist, dass das Objekt AmphibianVehicle weiß, ob es zur Zeit auf dem Wasser oder Land, und das Richtige tut.

In Ihrem Beispiel gehört move() zum Vehicle Schnittstelle und definiert den Vertrag „gehe von Punkt A nach Punkt B“.

Wenn GroundVehicle und WaterVehicle Vehicle verlängern, sie diesen Vertrag implizit erben. (Analogie: List.contains erbt seinen Vertrag von Collection.contains - vorstellen, wenn es etwas anderes angegeben)

So, wenn der Beton AmphibianVehicle move() implementiert, muss der Vertrag es wirklich respektieren ist Vehicle ist. Es ist ein Diamant, aber der Vertrag ändert sich nicht, ob Sie eine Seite des Diamanten oder andere betrachten (oder ich nennen würde, dass ein Design-Problem).

Wenn Sie den Vertrag von „moving“ müssen den Begriff der Oberfläche zu verkörpern, nicht definieren es in einer Art, die nicht diesen Begriff nicht Modell:

public interface GroundVehicle extends Vehicle {
    void ride();
}
public interface WaterVehicle extends Vehicle {
    void sail();
}

(Analogie:. get(int) der Auftrag durch den List Schnittstelle definiert ist, kann es nicht möglicherweise durch Collection definiert werden, wie Sammlungen sind nicht unbedingt geordnet)

oder Refactoring Ihre generische Schnittstelle den Begriff hinzuzufügen:

public interface Vehicle {
    void move(Surface s) throws UnsupportedSurfaceException;
}

Das einzige Problem, das ich sehe, wenn mehrere Schnittstellen Implementierung ist, wenn zwei Methoden völlig unabhängig Schnittstellen kollidieren geschehen:

public interface Vehicle {
    void move();
}
public interface GraphicalComponent {
    void move(); // move the graphical component on a screen
}
// Used in a graphical program to manage a fleet of vehicles:
public class Car implements Vehicle, GraphicalComponent {
    void move() {
        // ???
    }
}

Aber dann wäre das nicht ein Diamant sein. Mehr wie ein umgedrehtes Dreieck.

  

Leute behaupten Java kein Diamant Problem kennt. Ich kann nur die Mehrfachvererbung mit Schnittstellen hat und da sie keine Implementierung haben, ich habe keinen Diamanten Problem. Ist das wirklich wahr?

ja, weil Sie die Implementierung der Schnittstelle in D. Die Methodensignatur Steuerung ist die gleiche zwischen den beiden Schnittstellen (B / C), und da Schnittstellen keine Implementierung haben -. Keine Probleme

Ich weiß nicht, Java, aber wenn Schnittstellen B und C von Schnittstelle A erben und Klasse D implementiert Schnittstellen B und C, dann wird die Klasse D implementiert nur den Umzug Methode einmal, und es ist A.Move dass es umsetzen sollten . Wie Sie sagen, hat der Compiler kein Problem mit diesem.

Aus dem Beispiel Sie AmphibianVehicle geben in Bezug auf die Umsetzung GroundVehicle und WaterVehicle, dies leicht durch Speichern eines Verweises auf Umwelt, beispielsweise gelöst werden könnten, und eine Oberfläche Eigenschaft Umwelt ausgesetzt wird, dass die Move-Methode von AmphibianVehicle untersuchen würde. Keine Notwendigkeit für diese als Parameter übergeben werden.

Sie sind in dem Sinne richtig, dass es etwas für den Programmierer zu lösen, aber zumindest kompilieren und sollen nicht als ‚Problem‘ sein.

Es gibt keinen Diamanten Problem mit Interface-basierenden Vererbung.

Mit Klasse-basierte Vererbung, die mehreren erweiterten Klassen unterschiedliche Implementierung eines Verfahrens haben kann, so gibt es Unklarheit darüber, welche Methode tatsächlich zur Laufzeit verwendet wird.

Mit Schnittstellen-basierten Vererbung gibt es nur eine Implemenation des Verfahrens, also gibt es keine Zweideutigkeit.

EDIT:. Eigentlich wäre das gleiche für Methoden der Klasse-basierte Vererbung gilt als Zusammenfassung in der übergeordneten Klasse deklarieren

  

Wenn ich weiß, eine AmphibianVehicle haben   Schnittstelle, die von erbt   GroundVehicle und WaterVehicle, wie   würde ich seine Bewegung () -Methode implementieren?

Sie würde die Umsetzung geeignet für AmphibianVehicles liefern.

Falls ein GroundVehicle moves „anders“ (d nimmt verschiedene Parameter als ein WaterVehicle), dann erbt AmphibianVehicle zwei verschiedene Methoden, einen für Wasser auf, einen für auf dem Boden. Wenn dies nicht möglich ist, dann sollte AmphibianVehicle nicht erbt von GroundVehicle und WaterVehicle.

  

Ist ein Diamant-Problem immer die Ursache   Schlechtklasse-Design und etwas   weder Programmierer noch Compiler Bedürfnisse   zu lösen, weil es sollte nicht existieren   in erster Linie?

Wenn es um schlechte Klasse Design aufgrund ist, ist es der Programmierer, die es lösen muss, da der Compiler würde nicht wissen, wie.

Das Problem, das Sie in dem Schüler / Lehrer Beispiel sind zu sehen, ist einfach, dass Ihr Datenmodell falsch ist, oder zumindest nicht ausreichend.

Die Schüler und Lehrer Klassen sind conflating zwei verschiedene Konzepte der „Abteilung“ durch den gleichen Namen für jeden von ihnen. Wenn Sie diese Art der Vererbung verwenden möchten, sollten Sie stattdessen so etwas wie „getTeachingDepartment“ an den Lehrer und „getResearchDepartment“ in Studenten definieren. Ihre GradStudent, die sowohl ein Lehrer und ein Student ist, implementiert beide.

Natürlich ist die Realitäten der Graduiertenschule gegeben, auch dieses Modell ist wahrscheinlich nicht ausreichend.

Ich glaube nicht, dass konkrete Mehrfachvererbung zu verhindern ist das Problem von den Compiler für den Programmierer zu bewegen. Im Beispiel gab man es noch nötig wäre, für den Programmierer an den Compiler zu spezifizieren, die Implementierung zu verwenden. Es gibt keine Möglichkeit der Compiler konnte ahnen, was richtig ist.

Für Ihre Amphibie Klasse, könnten Sie eine Methode hinzufügen, um zu bestimmen, ob das Fahrzeug auf Wasser oder zu Land ist und dieses in Bewegung Methode entscheiden, verwenden zu können. Dadurch wird die parameterlos Schnittstelle erhalten.

move()
{

  if (this.isOnLand())
  {
     this.moveLikeLandVehicle();
  }
  else
  {
    this.moveLikeWaterVehicle();
  }
}

In diesem Fall wäre es wahrscheinlich die meisten von Vorteil sein, haben AmphibiousVehicle eine Unterklasse von Fahrzeug sein (Geschwister von WaterVehicle und LandVehicle), um das Problem vollständig in erster Linie zu vermeiden. Es wäre wohl richtiger sowieso, da ein Amphibienfahrzeug kein Wasserfahrzeug oder ein Landfahrzeug, es ist eine andere Sache überhaupt.

Wenn move () hat semantische Unterschiede darauf basierende Boden oder Wasser sein (statt GroundVehicle und WaterVehicle Schnittstellen sowohl sich selbst erstreckt GeneralVehicle Schnittstelle, die den Zug zur Verfügung hat () Unterschrift), aber es wird erwartet, dass Sie und Boden übereinstimmen mischen und Wasser Implementierer dann ein Ihr Beispiel ist wirklich ein von schlecht gestalteten api.

Das eigentliche Problem ist, wenn der Name Kollision ist, effektiv, zufällig. zum Beispiel ( sehr synthetisch):

interface Destructible
{
    void Wear();
    void Rip();
}

interface Garment
{
    void Wear();
    void Disrobe();
}

Wenn Sie eine Jacke, die Sie wollen beide ein Kleidungsstück zu sein, und zerstörbare Sie einen Namen Kollision haben auf die (zu Recht genannt) tragen Methode.

Java hat keine Lösung für diese (gilt auch für einige andere statisch typisierten Sprachen wahr ist). Dynamische Programmiersprachen wird ein ähnliches Problem hat, auch ohne den Diamanten oder Erbschaft, es ist nur ein Name Kollision (ein inhärentes potenzielles Problem mit Duck Typing).

Net hat das Konzept der expliziten Schnittstellenimplementierungen , wobei eine Klasse zwei Verfahren mit dem gleichen Namen und die Unterschrift definieren kann, solange beide an zwei verschiedene Schnittstellen gekennzeichnet sind. Die Bestimmung der Methode ist auf der Kompilierung bekannt Schnittstelle der Variablen (durch die explizite Wahl des Angerufenen oder wenn durch Reflexion) zu nennen basierend

Das vernünftig, wahrscheinlich Namenskollisionen so schwer zu bekommen ist und dass Java wurde als unbrauchbar für die Bereitstellung der nicht expliziten Schnittstellenimplementierungen würde vorschlagen, dass das Problem für reale Welt Verwendung ist keine große Liebe nicht den Pranger gestellt.

Ich weiß, dass dies eine bestimmte Instanz, nicht eine allgemeine Lösung, aber es klingt wie Sie ein zusätzliches System benötigen Zustand zu bestimmen und auf welcher Art von Bewegung zu entscheiden () würde das Fahrzeug durchführen.

Es scheint, dass im Fall eines Amphibienfahrzeuges, der Anrufer (sie „Drossel“ sagen) keine Ahnung von dem Zustand des Wasser / Bodens haben würde, aber ein Zwischen Bestimmung Objekt wie „Übertragung“ in Verbindung mit „Traktion control“könnte es herauszufinden, dann rufen move () mit den richtigen Parametern move (Räder) oder verschieben (prop).

Das Problem wirklich existiert. In der Probe muß die AmphibianVehicle-Klasse eines weiteren Informationen - die Oberfläche. Meine bevorzugte Lösung ist, einen Getter / Set-Methode auf der AmpibianVehicle Klasse hinzuzufügen, um das Oberflächenelement zu ändern (Enumeration). Die Umsetzung könnte jetzt das Richtige tun und die Klasse Aufenthalt verkapselt.

Sie können den Diamanten Problem in C ++ haben (das die Mehrfachvererbung), aber nicht in Java oder in C #. Es gibt keine Möglichkeit von zwei Klassen für vererben. Implementieren Sie zwei Schnittstellen mit der gleichen Methode Erklärung nicht in dieser Situation nicht impliziert, da die konkrete Umsetzung Methode kann nur in der Klasse gemacht werden.

Der Diamant Problem in C ++ ist bereits gelöst: Verwenden Sie virtuelle Vererbung. Oder noch besser, seien Sie nicht faul und erben, wenn es nicht notwendig ist (oder unvermeidbar). Wie für das Beispiel geben Sie, könnte dies durch eine Neudefinition gelöst werden, was es bedeutet, auf dem Boden oder im Wasser des Fahrens in der Lage ist. Hat die Fähigkeit, durch Wasser zu bewegen, wirklich ein wasserbasiertes Fahrzeug definiert oder ist nur etwas, das Fahrzeug in der Lage ist zu tun? Ich würde denken eher, dass der Umzug () Funktion, die Sie hinter sich hat irgendeine Art von Logik beschrieben, die fragt: „Wo bin ich und kann ich eigentlich hier bewegen?“ Die äquivalent eine bool canMove() Funktion, die über den aktuellen Zustand und die inhärenten Fähigkeiten des Fahrzeugs abhängt. Und Sie brauchen keine Mehrfachvererbung, dieses Problem zu lösen. Verwenden Sie einfach ein mixin, die die Frage auf unterschiedliche Weise beantwortet je nachdem, was möglich ist, und nimmt die übergeordnete Klasse als Template-Parameter, so dass die virtuelle canMove Funktion sichtbar durch die Vererbungskette sein wird.

Eigentlich, wenn Student und Teacher beiden Schnittstellen sind, ist es in der Tat löst Ihr Problem. Wenn sie Schnittstellen sind, dann getDepartment ist einfach eine Methode, die in Ihrer GradTeachingFellow Klasse erscheinen. Die Tatsache, dass sowohl die Student und Teacher Schnittstellen, die eine Schnittstelle erzwingen ist kein Konflikt überhaupt. Die Implementierung getDepartment in Ihrer GradTeachingFellow Klasse würde satify beiden Schnittstellen ohne Diamanten Problem.

Aber, wie in einem Kommentar darauf hingewiesen, das löst nicht das Problem einer GradStudent Lehre / Sein ein

Schnittstelle A {   void add (); }

Schnittstelle B erstreckt sich ein {   void add (); }

Schnittstelle C erstreckt A {   void add (); }

Klasse D implementiert B, C {

}

Ist es nicht Diamant Problem.

scroll top