2016-03-30 12 views
1

Diese Frage ist eine Folge zu this. Ich versuche, Klassenherarchien zu definieren, an denen mehrere Basenpaare beteiligt sind. Als anschauliches Beispiel nehme ich an, dass ich eine Klasse Animal und eine Klasse Food habe. Animal hat eine rein virtuelle Funktion, um seine Essensvorliebe zu markieren, ein Essen als Parameter nehmend.Liskov-Substitutionsprinzip und mehrere Hierarchien

Ich muss Code schreiben, der nur mit diesen Basisklassen befasst und die reine virtuelle Funktion aufruft. Zum Beispiel habe ich eine ZooManager Klasse, die Essensvorlieben für jedes Tier festlegt.

class ZooManager 
{ 
    vector<Aninal*> animals; 
    public: 
    void setAllPreferences(vector<Food *> foods) { 
     assert(animals.size() == foods.size()); 
     for(int i =0;i<animals.size();i++) { 
      animals[i]->setFoodPreference(foods[i]); 
     } 
    } 
}; 

So weit so gut. Nun ist das Problem, dass es viele verschiedene abgeleitete Klassen für Food und Animal gibt. Food hat abgeleitete Klassen Fruit und Meat, und Animal hat abgeleitete Klassen Carnivore und Herbivore. Herbivore kann nur Fruit als Nahrungsmittelpräferenz annehmen, und Carnivore kann nur Meat annehmen.

Kann ich eine Klassenhierarchie dafür erstellen, ohne das Liskow-Substitutionsprinzip zu verletzen? Obwohl ich in dieser Frage C++ verwende, würde ich auch Java-spezifische Antworten begrüßen.

Antwort

2

Zuerst muss Ihre setFoodPreference die Option zu fail haben. Das lässt setFoodPreference eine Food* nehmen und die Nachbedingung haben, entweder die Nahrungsmittelpräferenz zu setzen oder zu versagen.

Die dynamische Besetzung kann auch ein Fehler von LSP sein, aber wenn Sie Ihre Typ Invarianten vage genug einrichten, ist es technisch nicht ein Fehler.

Im Allgemeinen bedeutet dynamic_cast, dass der Typ des Arguments übergeben wurde und seine Eigenschaften nicht ausreichen, um zu ermitteln, ob das Argument bestimmte Eigenschaften hat.

Im Prinzip sollte setFoodPreference(Food*) in Bezug darauf angegeben werden, was Food* Eigenschaften das übergeben Argument muss, damit die Einstellung erfolgreich sein; Der dynamische Typ der Food* ist keine Food* Eigenschaft.

Also: LSP besagt, dass jede Unterklasse von Food alle Food Invarianten gehorchen muss. Ähnlich für Animal. Sie können eine LSP-Verletzung vermeiden, indem Sie die Invarianten vage und das Verhalten von Methoden unvorhersehbar machen; im Grunde mit dem Spruch "es kann aus unbestimmten Gründen scheitern". Das ist ... nicht sehr befriedigend.

Jetzt können Sie einen Schritt zurück und entscheiden nehmen, dass der dynamische Typ Ihres Food* ist Teil der Food* ‚s-Schnittstelle; das macht die Schnittstelle lächerlich breit und macht LSP zu einem Spott.

Der Punkt von LSP ist, dass Sie über eine Food* Grund denken können, ohne über seine Unterklasse-Typen nachzudenken; Sie sind "wie es funktioniert als Food". Ihr Code bindet eng an die Unterklasse-Typen und umgeht damit den LSP-Punkt.

Es gibt Möglichkeiten, dies zu umgehen.Wenn Food eine enum hatte, die angibt, welche Art von Essen es war, und Sie nie dynamisch auf Meat hinunterwerfen, sondern fragte die Food, wenn es Fleisch war, vermeiden Sie es. Jetzt können Sie das setFoodPreference Verhalten in Bezug auf die Schnittstelle Food spezifizieren.

+0

Ich mag die Enum-Lösung nicht in erster Linie, weil es so oft verwendet wird, um OO-Programmierung zu missbrauchen, im Wesentlichen setzen Kind Klasseninformationen in eine Basisklasse. Ich habe Code gesehen, wo 'Base' eine Aufzählung für' Type' mit den Werten 'CHILD1, CHILD2, CHILD3' etc. hat. Das ermutigt dann einen Haufen Code, eine' Base * 'zu akzeptieren, und fährt fort, um den zugrundeliegenden Typ zu überprüfen . Ein solcher Code spiegelt jemanden wider, der denkt, dass er alle Vorteile des Polymorphismus mit keinem der Nachteile nutzen kann. 'dynamic_cast' weg ist auch nicht viel besser. – AndyG

+0

@AndyG Ich habe nicht gesagt, dass es eine * großartige * Lösung war; Aber wie oben beschrieben, kümmert sich jede Tierart darum, was für eine Art Nahrung sie bekommt. Grundsätzlich leckt die Abstraktion hinter der Food-Klasse *, denn Food ist eine ziemlich nutzlose Abstraktion; Die Handhabung von rohem Fleisch auf die gleiche Weise, wie du mit Heu hantierst, ist eine sehr fragwürdige Sache. – Yakk

+0

Einverstanden. Ich denke, OP hat sich selbst in eine Ecke gebaut. Mit meinem Kommentar wollte ich Ihnen nicht widersprechen, aber Sie sollten betonen, warum die "Lösungen" für das OP-Problem nicht wünschenswert sind. – AndyG

1

Ihr Ansatz zur Gestaltung Ihrer Hierarchien ist falsch. OO-Klassen stellen Gruppen eng gekoppelter Regeln dar, wobei eine Regel eine Funktion ist und eng verbundene Daten gemeinsame Daten sind. OO-Klassen repräsentieren KEINE realen Objekte. Die Leute liegen falsch, wenn sie das sagen.

Wenn Sie auf die konkrete Art von Essen angewiesen sind, verletzen Sie das Liskov-Substitutionsprinzip. Zeitraum.

Um Ihre Klassen korrekt zu gestalten, damit Sie nicht gezwungen sind, den LSP zu verletzen, müssen Sie Regeln in die Food-Klasse einfügen, die von der Animal-Klasse verwendet werden können, um ihre eigenen Regeln zu erfüllen. Oder entscheiden, ob Essen sollte sogar eine Klasse sein. Was Ihr Beispiel zeigt, ist im Grunde genommen wirklich ein schlechter String-Vergleich.

Verwandte Themen