Vererbung schief gelaufen


12

Ich habe einen Code, in dem ein gutes Vererbungsmodell nicht mehr funktioniert, und ich versuche zu verstehen, warum und wie das Problem behoben werden kann. Stellen Sie sich vor, Sie haben eine Zoo-Hierarchie mit:

class Animal  
class Parrot : Animal 
class Elephant : Animal 
class Cow : Animal

etc.

Sie haben Ihre Methoden eat (), run () usw. und alles ist gut. Dann kommt eines Tages jemand vorbei und sagt: Unsere CageBuilder-Klasse funktioniert hervorragend und verwendet animal.weight () und animal.height (), mit Ausnahme des neuen afrikanischen Bisons, der zu stark ist und die Wand zertrümmern kann eine weitere Eigenschaft für die Animal-Klasse - isAfricanBizon (). Verwenden Sie diese Eigenschaft bei der Auswahl des Materials und überschreiben Sie sie nur für die AfricanBizon-Klasse. Die nächste Person kommt und macht etwas Ähnliches, und als nächstes wissen Sie, dass Sie all diese Eigenschaften haben, die für eine Teilmenge der Hierarchie in der Basisklasse spezifisch sind.

Was ist ein guter Weg, um solchen Code zu verbessern / umzugestalten? Eine Alternative wäre hier, nur dynamic_casts zu verwenden, um nach den Typen zu suchen, aber das überfrachtet die Anrufer und fügt überall eine Menge Wenn-Dann-Anderer hinzu. Sie können hier spezifischere Schnittstellen haben, aber wenn Sie nur die Basisklassenreferenz haben, hilft das auch nicht viel. Irgendwelche anderen Vorschläge? Beispiele?

Vielen Dank!


@ James: dann müssen Sie Parser von Hand schreiben. : S
Matteo Italia

5
Offensichtlich handelt es sich um eine lächerliche Kundenanforderung. Es gibt keinen Bison in Afrika. Sie können keine Objektmodelle entwerfen, die keinen Bezug zur Realität haben. Es sei denn, diese Realität wird von Händen voller Dollars geschaffen. Welches löst das Problem.
Hans Passant

1
Den ganzen Bison essen? [Ich habe dies zuvor gepostet, aber es wurde aus irgendeinem Grund gelöscht, vermutlich durch humorlose Esel.]
James McNellis

Braucht CageBuilder eine eigene Klasse? Was ist, wenn es eine MakeCage-Standardmethode gibt, die von jeder einzelnen Klasse überschrieben werden kann?
Job

1
Sie erwähnen das Wenn-Dann-Sonst-Durcheinander als Nachteil für die Anrufer, aber sobald die Anrufer anfangen, isAfricanBizon () zu verwenden, überhäufen sie den Code automatisch mit Wenn-Dann-Sonst. Es ist also entweder eine Unordnung mit isAfricanBizon () oder eine Unordnung mit dynamischen Darstellungen.
Davidk01

Antworten:


13

Es scheint, als ob das Problem darin besteht, RequiresConcreteWall () nicht zu implementieren, sondern einen Flag-Aufruf IsAfricanBison () zu implementieren und dann die Logik dahingehend zu verschieben, ob sich die Wand außerhalb des Bereichs der Klasse ändern soll oder nicht. Ihre Klassen sollten Verhalten und Anforderungen offenlegen, nicht Identität; Ihre Konsumenten dieser Klassen sollten nach dem arbeiten, was ihnen gesagt wird, und nicht nach dem, was sie sind.


1
-1: Sagt nur, was nicht zu tun ist. Das OP weiß bereits, dass dies eine schlechte Idee war, daher die Frage.
Steven Evers

12

isAfricanBizon () ist nicht generisch. Angenommen, Sie erweitern Ihre Tierfarm mit einem Hyppopotamus, der ebenfalls zu stark ist, aber von isAfricanBizon () wahr zurückgegeben wird, um den richtigen Effekt zu erzielen, wäre einfach albern.

Sie möchten der Schnittstelle immer Methoden hinzufügen, die die spezifische Frage beantworten. In diesem Fall wäre dies etwa stärke ().


+1: Alle anderen scheinen das konzeptionelle Modell der Klasse (das nur die Eigenschaften verschiedener Tierarten einschließt) zu brechen, um diesem speziellen Anwendungsfall Rechnung zu tragen. Es strengthkönnte eine Methode abgefragt werden material.canHold(animal), die eine saubere Art der Unterstützung anderer Arten von Material als zulässt ConcreteWall.
Aidan Cully

Mir gefällt der Ansatz der Eigenschaft strength () besser als der Vorschlag anderer von RequiresConcreteWall (), da er flexibler ist, um zukünftige Anforderungen zu ermöglichen. Lassen Sie zunächst die CageBuilder-Klasse entscheiden, welche Materialien stark genug sind. Anschließend können Sie die Klasse problemlos mit neuen Materialien erweitern.
jhocking

3

Ich denke, Ihr Problem ist Folgendes: Sie haben verschiedene Clients der Bibliothek, die nur an einer Teilmenge der Hierarchie interessiert sind, denen jedoch ein Zeiger / Verweis auf die Basisklasse übergeben wurde. Das ist in der Tat das Problem, das dynamic_cast <> lösen muss.

Es ist eine Frage des Designs der Clients, die Verwendung von dynamic_cast <> zu minimieren. Sie sollten es verwenden, um festzustellen, ob für das Objekt eine spezielle Behandlung erforderlich ist, und wenn ja, alle Operationen an der herabgegossenen Referenz durchführen.

Wenn Sie über Funktionssammlungen vom Typ "Mix-In" verfügen, die für mehrere separate Unterhierarchien gelten, möchten Sie möglicherweise das von Java und C # verwendete Schnittstellenmuster verwenden. Verfügen Sie über eine virtuelle Basisklasse, die eine rein virtuelle Klasse ist, und verwenden Sie dynamic_cast <>, um festzustellen, ob eine Instanz eine Implementierung dafür bereitstellt.


1

Eine Sache, die Sie tun können, ist, die explizite Prüfung des Typs isAfricanBison()durch die Prüfung der Eigenschaften zu ersetzen, an denen Sie tatsächlich interessiert sind, dh isTooStrong().


1
isTooStrong () für was? Sie fügen der Tierklasse käfigspezifischen Code hinzu.
Steven Evers

1

Tiere sollten sich nicht um Betonwände kümmern. Vielleicht können Sie es mit einfachen Werten ausdrücken.

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

Ich vermute, dass das nicht tragfähig ist. Das ist jedoch das Problem mit Spielzeugbeispielen.

Ich würde auf keinen Fall RequiresConcreteWalls () oder Linien und Linien von dynamischen Zeigern sehen wollen.

Dies ist normalerweise eine kostengünstige Lösung. Es ist einfach zu pflegen und zu konzipieren. Und wirklich, das Problem besagt, dass es sowieso an den Tiertyp gebunden ist.

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

Dies schließt auch nicht aus, dass Sie gemeinsam genutzten Code verwenden. Verschmutzen Sie Animal nur ein bisschen.

Aber wie der Käfig gebaut wird, kann eine Politik eines anderen Systems sein, und vielleicht haben Sie mehr als eine Art Käfigbauer pro Tier. Es gibt viele seltsame und verschlungene Kombinationen, die Sie sich einfallen lassen können.

Ich habe Component Based Design zu guten Zwecken verwendet. Das Hauptproblem dabei ist, dass es problematisch sein kann, wenn das Eigentum von Animal geteilt wird. Wie man es vermeidet, Destruktoren einzuschleudern, ist der Schmerzpunkt.

Double Dispatch ist eine weitere Option, obwohl ich immer zurückhaltend war, mich darauf einzulassen.

Darüber hinaus ist das Problem schwer zu erraten.


0

Na sicher haben alle Tiere die inhärente Eigenschaft von attemptEscape(). Während einige der Methoden falsein allen Szenarien ein Ergebnis liefern können, können andere aufgrund von Heuristiken ihrer anderen intrinsischen Merkmale wie sizeund eine Chance haben weight. Dann wird es sicherlich irgendwann attemptEscape()trivial, da es mit Sicherheit zurückkehren wird true.

Ich fürchte, ich verstehe Ihre Frage nicht ganz. Alle Tiere haben verwandte Handlungen und Eigenschaften. Tierspezifische sollten dort eingeführt werden, wo es passt. Der Versuch, Bison direkt mit Papageien in Beziehung zu setzen, ist keine gute Vererbung und sollte in einem ordnungsgemäßen Design eigentlich kein Problem darstellen.


-1

Eine andere Möglichkeit wäre die Verwendung einer Fabrik, in der für jedes Tier geeignete Käfige hergestellt werden. Ich denke, dies kann besser sein, wenn die Bedingungen für jeden von ihnen sehr unterschiedlich sind. Aber wenn es nur diese eine Bedingung ist, wird die oben erwähnte RequiresConcreteWall()Methode es tun.


-1

wie wäre es mit RecommendCageType () als oppsed RequiresConcreteWall ()


-2

Warum nicht so etwas tun?

class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison

Mit der Klasse HeavyAnimals können Sie die African Bison-Klasse erstellen, indem Sie die Klasse HeavyAnimals erweitern.

Nun können Sie die Elternklasse (Animals), mit der andere Basisklassen wie die HeavyAnimal-Klasse erstellt werden können, verwenden, um die African Bison-Klasse und andere Heavy Animals zu erstellen. Mit dem African Bison haben Sie nun Zugriff auf die Methoden und Eigenschaften der Klasse Animal (dies ist die Basis für alle Tiere) und auf die Klasse HeavyAnimals (dies ist die Basis für schwere Tiere).


2
Das mag als Mix-In oder Merkmal funktionieren, aber sicherlich nicht als Unterklasse. Dies bittet nur um Mehrfachvererbung, wenn das nächste Mal eine andere Eigenschaft benötigt wird.
Ordentliche
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.