Objektorientiertes Design

23

Angenommen, Sie haben Folgendes:

     +--------+     +------+
     | Animal |     | Food |
     +-+------+     +----+-+
       ^                 ^
       |                 |
       |                 |
  +------+              +-------+
  | Deer |              | Grass |
  +------+              +-------+

Deererbt von Animalund Grasserbt von Food.

So weit, ist es gut. AnimalObjekte können FoodObjekte essen .

Jetzt mischen wir es ein bisschen durch. Fügen wir ein hinzu, von Liondem erbt Animal.

     +--------+     +------+
     | Animal |     | Food |
     +-+-----++     +----+-+
       ^     ^           ^
       |     |           |
       |     |           |
  +------+ +------+     +-------+
  | Deer | | Lion |     | Grass |
  +------+ +------+     +-------+

Jetzt haben wir ein Problem, weil wir Lionbeides essen können Deerund Grass, aber es Deerist nicht Foodso Animal.

Wie können Sie dieses Problem lösen, ohne Mehrfachvererbung und objektorientiertes Design zu verwenden?

Zu Ihrer Information: Ich habe http://www.asciiflow.com verwendet , um die ASCII-Diagramme zu erstellen.

Michael Irey
quelle
14
Das Modellieren der realen Welt ist in der Regel früher oder später ein Problem, da immer etwas Merkwürdiges vor sich geht (wie ein fliegender Fisch, ein Fisch oder ein Vogel? Aber ein Pinguin ist ein Vogel, kann nicht fliegen und frisst Fisch). Was @Ampt sagt, klingt plausibel, ein Tier sollte eine Sammlung von Dingen haben, die es frisst.
Rob van der Veer
2
Ich denke, Tier sollte von Essen erben. Wenn etwas versucht, einen Löwen zu fressen, lasse einfach eine InvalidOperationException auslösen.
RalphChapin
4
@RalphChapin: Alle Arten von Dingen fressen Löwen (Geier, Käfer usw.). Ich denke, Tier und Futter sind künstliche Unterscheidungen, die sich auflösen, weil sie nicht breit genug sind (alle Tiere sind irgendwann Futter für andere Tiere). Wenn Sie sich für "LivingThing" entschieden hätten, müssten Sie sich nur mit den Randfällen von Pflanzen befassen, die nicht lebende Dinge (Mineralien usw.) fressen, und es würde nichts kaputt machen, LivingThing.Eat (LivingThing) zu haben.
Satanicpuppy
2
Das Teilen Ihrer Forschung hilft allen. Sagen Sie uns, was Sie versucht haben und warum es nicht Ihren Bedürfnissen entsprach. Dies zeigt, dass Sie sich die Zeit genommen haben, um sich selbst zu helfen, es erspart uns, offensichtliche Antworten zu wiederholen, und vor allem hilft es Ihnen, eine spezifischere und relevantere Antwort zu erhalten. Siehe auch Wie man fragt
Mücke
9
Diese Frage wurde vom Spiel Age of Empire III beantwortet. ageofempires.wikia.com/wiki/List_of_Animals Deer und Gazelle implementieren IHuntable, Sheep and Cow sind IHerdable(von Menschen kontrollierbar), und Lion implementiert nur IAnimal, was keine dieser Schnittstellen impliziert. AOE3 unterstützt die Abfrage der Schnittstellen, die von einem bestimmten Objekt (ähnlich wie instanceof) unterstützt werden, wodurch ein Programm seine Funktionen abfragen kann.
Rwong

Antworten:

38

Ist eine Beziehung = Vererbung

Löwe ist ein Tier

Hat eine Beziehung = Zusammensetzung

Auto hat ein Rad

CAN-DO-Beziehungen = Schnittstellen

Ich kann essen

Robert Harvey
quelle
5
+1 Das ist so einfach und doch so eine gute Zusammenfassung der 3 verschiedenen Beziehungstypen
Dreza
4
Alternative: ICanBeEatenoderIEdible
Mike Weller
2
Kann Haz-Beziehungen = Lolcats
Steven A. Lowe
1
Wie beantwortet dies die Frage?
user253751
13

OO ist nur eine Metapher, die sich an die reale Welt anlehnt. Aber Metaphern gehen nur so weit.

Normalerweise gibt es keinen richtigen Weg, um etwas in OO zu modellieren. Es gibt einen richtigen Weg, dies für ein bestimmtes Problem in einer bestimmten Domäne zu tun, und Sie sollten nicht erwarten, dass es gut funktioniert, wenn Sie Ihr Problem ändern, auch wenn die Domänenobjekte gleich sind.

Ich denke, dies ist ein häufiges Missverständnis der meisten Comp. Eng. Studenten haben in ihren ersten Jahren. OO ist keine universelle Lösung, sondern nur ein anständiges Tool für einige Probleme, mit denen Ihre Domain einigermaßen gut modelliert werden kann.

Ich habe die Frage nicht beantwortet, weil uns Domain-Informationen fehlen. Vor diesem Hintergrund können Sie jedoch möglicherweise etwas entwerfen, das Ihren Anforderungen entspricht.

DPM
quelle
3
+1 OO ist ein Werkzeug, keine Religion.
mouviciel
Ich bin damit einverstanden, dass es möglicherweise keine perfekte Lösung gibt, wenn sich dieses Problem weiter ändert und weiterentwickelt. Fehlt diesem Problem im aktuellen Status die Domain-Information, um eine Lösung zu finden?
Michael Irey
Denken Sie ernsthaft, dass eine reale Welt in OP modelliert wird? Ein Beziehungsmodell wird anhand eines Beispiels dargestellt.
Basilevs
@Basilevs Das ist eigentlich eine Art Implikation, da er erwähnt, wie sich Tiere im wirklichen Leben verhalten. Man muss sich darum kümmern, warum man dieses Verhalten im Programm IMO berücksichtigen muss. Trotzdem wäre es nett von mir gewesen, ein mögliches Design vorzuschlagen.
DPM
10

Sie möchten die Tiere weiter in ihre Unterklassen aufteilen (oder zumindest, soweit dies für Ihre Tätigkeit sinnvoll ist). Da Sie mit Tieren und zwei Arten von Lebensmitteln (Pflanzen und Fleisch) arbeiten, ist es sinnvoll, Fleischfresser und Pflanzenfresser zu verwenden, um ein Tier genauer zu definieren und getrennt zu halten. Folgendes habe ich für Sie entworfen.

             +----------------+                   +--------------------+
             |    Animal      |                   |      Food          |
             |----------------|<--+Interfaces+--->|--------------------|
             |                |                   |                    |
             +----------------+                   +--------------------+
                +           +                       +                 +
                |           |    Abstract Classes   |                 |
                |           |        |          |   |                 |
                v           v        v          v   v                 v
   +-----------------+  +----------------+     +------------+      +------------+
   |   Herbivore     |  |  Carnivore     |     |   Plant    |      |   Meat     |
   |-----------------|  |----------------|     |------------|      |------------|
   |Eat(Plant p)     |  |Eat(Meat m)     |     |            |      |            |
   |                 |  |                |     |            |      |            |
   +-----------------+  +----------------+     +------------+      +------------+
            +                    +                    +                   +
            |                    |                    |                   |
            v                    v                    v                   v
   +-----------------+  +----------------+     +------------+      +------------+
   |  Deer           |  |   Lion         |     |  Grass     |      |  DeerMeat  |
   |-----------------|  |----------------|     |------------|      |------------|
   |DeerMeat Die()      |void Kill(Deer) |     |            |      |            |
   +-----------------+  +----------------+     +------------+      +------------+
                                 ^                    ^
                                 |                    |
                                 |                    |
                              Concrete Classes -------+

Wie Sie sehen können, legen beide eine Eat-Methode offen, aber was sie essen, ändert sich. Der Löwe kann jetzt einen Hirsch töten, der Hirsch kann sterben und DeerMeat zurückgeben, und die ursprüngliche Frage, wie ein Löwe einen Hirsch, aber kein Gras fressen darf, wird beantwortet, ohne ein gesamtes Ökosystem zu konstruieren.

Natürlich wird dies sehr schnell interessant, da ein Hirsch auch als Fleischsorte angesehen werden kann. Um die Dinge einfach zu halten, würde ich eine Methode namens kill () unter deer erstellen, die ein Hirschfleisch zurückgibt, und das als a setzen Betonklasse erweitern Fleisch.

Ampt
quelle
Würde Deer dann eine IMeat-Schnittstelle verfügbar machen?
Dan Pichelman
Fleisch ist keine Schnittstelle, es ist eine abstrakte Klasse. Ich fügte hinzu, wie ich das für Sie umsetzen würde
Ampt
Eat(Plant p)und Eat(Meat m)beide verletzen LSP.
Tulains Córdova
Wie so @ user61852? Ich habe Eat absichtlich nicht in der Tieroberfläche verfügbar gemacht, damit jeder Tiertyp seine eigene Fressmethode hat.
Ampt
1
TCWL (zu komplex, wird auslaufen). Das Problem ist verteilt und im Entstehen begriffen, und Ihre Lösung ist statisch, zentralisiert und vordefiniert. TCWL.
Tulains Córdova
7

Mein Design würde so aussehen:

  1. Lebensmittel werden als Schnittstellen deklariert. Es gibt eine IFood-Schnittstelle und zwei davon abgeleitete Schnittstellen: IMeat und IVegetable
  2. Tiere setzen IMeat und Gemüse IVegetable ein
  3. Tiere haben zwei Nachkommen, Fleischfresser und Hebivoren
  4. Fleischfresser haben die Eat-Methode, die eine Instanz von IMeat empfängt
  5. Pflanzenfresser haben die Eat-Methode, die eine Instanz von IVegetable erhält
  6. Löwe stammt von Fleischfresser ab
  7. Hirsche stammen von Herbivore ab
  8. Gras stammt von Gemüse ab

Da Tiere IMeat implementieren und Hirsche ein (Pflanzenfresser-) Tier sind, kann Löwe, ein (Fleischfresser-) Tier, das IMeat fressen kann, auch Hirsche fressen.

Hirsche sind Pflanzenfresser, deshalb kann sie Gras fressen, weil sie pflanzlich sind.

Fleischfresser können kein Gemüse essen und Pflanzenfresser können kein Fleisch essen.

AlexSC
quelle
1
Ich sehe hier eine Menge Aufzählungstypen, die Vererbung verwenden, um einzugrenzen, wann immer die geerbten Typen nichts implementieren ... Immer wenn Sie Typen herstellen, die überhaupt keine Funktionalität implementieren, ist es ein Werbegeschenk. Sie haben ein Modell im Typensystem erweitert, das keinen Wert für die Benutzerfreundlichkeit im Code liefert
Jimmy Hoffa
Denken Sie daran, dass es Allesfresser wie Menschen, Affen und Bären gibt.
Tulains Córdova
Wie fügt man also hinzu, dass sowohl Löwen als auch Hirsche Säugetiere sind? :-)
Johannes
2
@JimmyHoffa Diese werden "Markierungsschnittstellen" genannt und sind eine absolut gültige Verwendung der Schnittstelle. Es muss eine Codeüberprüfung durchgeführt werden, um zu entscheiden, ob die Verwendung gerechtfertigt ist. Es gibt jedoch viele Anwendungsfälle (z. B. dieser, bei denen ein Löwe, der versucht, Gras zu essen, eine NoInterface-Ausnahme auslöst). Die Markierungsschnittstelle (oder das Fehlen von) dient dazu, eine Ausnahme vorherzusagen, die ausgelöst wird, wenn eine Methode mit nicht unterstützten Argumenten aufgerufen wird.
rwong
1
@rwong Ich verstehe das Konzept und habe es noch nie zuvor formalisiert. Nur meine Erfahrung war, dass eine Codebasis, in der ich gearbeitet habe, die Dinge immer komplexer und schwieriger zu warten macht. Vielleicht war meine Erfahrung aber nur dort, wo die Leute sie falsch benutzt haben.
Jimmy Hoffa
5

Welches Futter ein Tier essen kann, bildet eigentlich keine Hierarchie. In diesem Fall ist die Natur unentschuldbar gegen eine einfache objektorientierte Modellierung verstoßen.

Das Wissen darüber, welche Lebensmittel ein Tier essen kann, kann mit keiner der Klassen vollständig vereinbar sein. Daher kann es nicht ausreichen, sich nur auf ein Mitglied der Lebensmittelhierarchie zu beziehen, um zu sagen, welche Dinge Sie essen können.

Es ist eine Beziehung von vielen zu vielen. Dies bedeutet, jedes Mal, wenn Sie ein Tier hinzufügen, müssen Sie herausfinden, was es fressen kann, und jedes Mal, wenn Sie ein Futter hinzufügen, müssen Sie herausfinden, was es fressen kann. Ob es eine weitere Struktur gibt, die genutzt werden kann, hängt davon ab, welche Tiere und Lebensmittel Sie modellieren.

Mehrfachvererbung löst dies auch nicht wirklich gut. Sie brauchen eine Art Sammlung von Dingen, die ein Tier essen kann, oder von Tieren, die ein Lebensmittel essen können.

bA
quelle
Wie sie über Regex sagen: "Ich hatte ein Problem, also habe ich Regex verwendet, jetzt habe ich zwei Probleme." d folgen Sie diesem eitlen, den Sie hier anstupsten, obwohl das Essen weiß, was es essen kann, dies vereinfacht das Modell tatsächlich eine Tonne. Abhängigkeitsinversion FTW.
Jimmy Hoffa
1

Ich werde das Problem von einer anderen Seite angehen: Bei OOP geht es um Verhalten. Müssen Sie in Ihrem Fall Grassein Kind sein Food? In deinem Fall wird es also keine GrassKlasse geben, oder zumindest wird es nicht von ihr geerbt Food. Auch wenn Sie erzwingen müssen, wer was zur Kompilierungszeit essen kann, ist es fraglich, ob Sie eine AnimalAbstraktion benötigen . Es kommt auch nicht selten vor, dass Fleischfresser Gras fressen , wenn auch nicht für den Unterhalt.

Also würde ich dies so gestalten (ohne mich mit ASCI-Kunst zu beschäftigen):

IEdiblemit Eigenschaft Type, die enum aus Fleisch, Pflanzen, Kadavern usw. besteht (dies ändert sich nicht oft und weist kein spezifisches Verhalten auf, daher ist es nicht erforderlich, dies als Klasse hiearchy zu modellieren).

Animalmit Methoden CanEat(IEdible food)und Eat(IEdible food), die logisch sind. Dann können bestimmte Tiere überprüfen, wann immer sie unter bestimmten Umständen Nahrung zu sich nehmen können, und dann diese Nahrung zu sich nehmen, um sich zu ernähren oder etwas anderes zu tun. Außerdem würde ich die Klassen Carnivore, Herbivore, Omnivore als Strategiemuster modellieren , als Teil der Tierhierarchie.

Euphorisch
quelle
1

TL; DR: Entwurf oder Modell mit einem Kontext.

Ich denke, Ihre Frage ist schwierig, weil es keinen Zusammenhang mit dem eigentlichen Problem gibt, das Sie zu lösen versuchen. Sie haben einige Modelle und Beziehungen, aber es fehlt der Rahmen, in dem es funktionieren muss. Ohne Kontext funktionieren Modellierung und Metaphern nicht gut, sodass mehrere Interpretationen möglich sind.

Ich halte es für produktiver, sich darauf zu konzentrieren, wie die Daten verarbeitet werden. Sobald Sie das Muster der Datennutzung kennen, können Sie einfacher auf die Modelle und Beziehungen zurückgreifen.

Beispielsweise erfordern detailliertere Anforderungen unterschiedliche Objektbeziehungen:

  • Unterstützung Animals eatnicht FoodmögenGastroliths
  • Unterstützung Chocolatewie Poisonfür Dogs, aber nicht fürHumans

Wenn wir mit der Modellierung der dargestellten einfachen Beziehung beginnen, ist das Food Interface möglicherweise das beste. und wenn das die Gesamtsumme ist, wie die Verhältnisse im System dann deine Geldstrafe. Nur ein paar zusätzliche Anforderungen oder Beziehungen können sich jedoch erheblich auf die Modelle und Beziehungen auswirken, die im einfacheren Fall funktionierten.

dietbuddha
quelle
Ich stimme zu, aber es ist nur ein kleines Beispiel und ich habe nicht versucht, die Welt zu modellieren. Zum Beispiel können Sie einen Hai haben, der Reifen und Nummernschilder frisst. Sie können einfach eine übergeordnete abstrakte Klasse mit einer Methode erstellen, die jede Art von Objekt isst, und Food könnte diese Abstact-Klasse erweitern.
Hagensoft
@ Hagensoft: Einverstanden. Manchmal bin ich hin und weg, weil ich ständig sehe, wie Entwickler auf der Grundlage einer Metapher modellieren, die sie sofort aufgegriffen haben, anstatt zu prüfen, wie die Daten konsumiert und verwendet werden müssen. Sie heiraten mit einem OO-Entwurf, der auf einer anfänglichen Idee basiert, und versuchen dann, das Problem zu erzwingen, damit es zu ihrer Lösung passt, anstatt dass ihre Lösung zum Problem passt.
Dietbuddha
1

ECS-Ansatz "Zusammensetzung über Vererbung":

An entity is a collection of components.
Systems process entities through their components.

Lion has claws and fangs as weapons.
Lion has meat as food.
Lion has a hunger for meat.
Lion has an affinity towards other lions.

Deer has antlers and hooves as weapons.
Deer has meat as food.
Deer has a hunger for plants.

Grass has plant as food.

Pseudocode:

lion = new Entity("Lion")
lion.put(new Claws)
lion.put(new Fangs)
lion.put(new Meat)
lion.put(new MeatHunger)
lion.put(new Affinity("Lion"))

deer = new Entity("Deer")
deer.put(new Antlers)
deer.put(new Hooves)
deer.put(new PlantHunger)

grass = new Entity("Grass")
grass.put(new Plant)

Natureist eine Funktion system, die diese Entitäten durchläuft und über eine verallgemeinerte Abfragefunktion nach ihren Komponenten sucht. NatureEntitäten mit Hunger nach Fleisch werden andere Entitäten angreifen, die Fleisch als Nahrung mit ihren Waffen haben, es sei denn, sie haben eine Affinität zu dieser Entität. Wenn der Angriff erfolgreich ist, ernährt sich die Entität von ihrem Opfer. An diesem Punkt verwandelt sich das Opfer in einen fleischlosen Leichnam. NatureEntitäten mit Hunger nach Pflanzen werden sich von Entitäten ernähren, die Pflanzen als Nahrung haben, sofern sie existieren.

Nature({lion, deer, grass})

Nature(entities)
{
    for each entity in entities:
    {
       if entity.has("MeatHunger"):
           attack_meat(entity, entities.with("Meat", exclude = entity))
       if entity.has("PlantHunger"):
           eat_plants(entity, entites.with("Plant", exclude = entity))
    }
}

Vielleicht wollen wir das GrassBedürfnis nach Sonnenlicht und Wasser erweitern, und wir wollen Sonnenlicht und Wasser in unsere Welt einführen. Ich Grasskann sie jedoch nicht direkt suchen, wie es nicht der Fall ist mobility.AnimalsVielleicht brauchen sie auch Wasser, können es aber aktiv suchen, seit sie es haben mobility. Es ist ziemlich einfach, dieses Modell zu erweitern und zu ändern, ohne das gesamte Design zu beschädigen, da wir einfach neue Komponenten hinzufügen und das Verhalten unserer Systeme (oder die Anzahl der Systeme) erweitern.


quelle
0

Wie können Sie dieses Problem lösen, ohne Mehrfachvererbung und objektorientiertes Design zu verwenden?

Wie bei den meisten Dingen kommt es darauf an .

Es hängt davon ab, was Sie unter "diesem Problem" verstehen.

  • Ist es ein allgemeines Implementierungsproblem , z. B. wie Sie das Fehlen einer Mehrfachvererbung auf der von Ihnen ausgewählten Plattform umgehen können?
  • Ist es ein Designproblem?Handelt nur für diesen speziellen Fall um, z. B. wie man die Tatsache modelliert, dass Tiere auch Lebensmittel sind?
  • Ist es ein philosophisches Problem mit dem Domänenmodell , z. B. sind „Nahrung“ und „Tier“ gültig, notwendig und ausreichend klassifiziert für die beabsichtigte praktische Anwendung?

Wenn Sie nach dem allgemeinen Implementierungsproblem fragen, hängt die Antwort von den Fähigkeiten Ihrer Umgebung ab. IFood- und IAnimal-Schnittstellen könnten funktionieren, wobei eine EdibleAnimal-Unterklasse beide Schnittstellen implementiert. Wenn Ihre Umgebung keine Schnittstellen unterstützt, lassen Sie Animal einfach von Food erben.

Wenn Sie nach diesem speziellen Designproblem fragen, lassen Sie Animal einfach von Food erben. Es ist die einfachste Sache, die möglicherweise funktionieren könnte.

Wenn Sie nach diesen Konstruktionskonzepten fragen, hängt die Antwort stark davon ab, was Sie mit dem Modell tun möchten. Wenn es sich um ein Hund-fress-Hund-Videospiel handelt oder um eine Anwendung, mit der Fütterungspläne in einem Zoo verfolgt werden können, reicht es möglicherweise aus, um zu arbeiten. Wenn es sich um ein konzeptionelles Modell für Verhaltensmuster von Tieren handelt, ist es wahrscheinlich ein bisschen oberflächlich.

Steven A. Lowe
quelle
0

Vererbung sollte für etwas verwendet werden, das immer etwas anderes ist und sich nicht ändern kann. Gras ist nicht immer Nahrung. Zum Beispiel esse ich kein Gras.

Gras spielt für bestimmte Tiere die Rolle eines Nahrungsmittels.

Neil McGuigan
quelle
Es ist nur eine Abstraktion. Wenn dies eine Anforderung ist, können Sie weitere Unterteilungen erstellen, die die abstrakte Klasse Plant erweitern und Menschen dazu bringen, eine abstrakte Klasse wie 'HumanEatablePlants' zu essen, die die Pflanzen, die Menschen essen, in konkrete Klassen gruppiert.
Hagensoft
0

Sie sind gerade auf die grundlegende Einschränkung von OO gestoßen.

OO funktioniert gut mit hierarchischen Strukturen. Aber wenn Sie einmal strengen Hierarchien entkommen, funktioniert die Abstraktion nicht mehr so ​​gut.

Ich weiß alles über Metamorphose-Kompositionen usw., die verwendet werden, um diese Einschränkungen zu umgehen, aber sie sind ungeschickt und führen, was noch wichtiger ist, zu undurchsichtigem und schwer zu verfolgendem Code.

Relationale Datenbanken wurden in erster Linie erfunden, um den Beschränkungen strenger hierarchischer Strukturen zu entkommen.

Zum Beispiel könnte Gras auch ein Baumaterial, ein Rohstoff für Papier, ein Bekleidungsmaterial, ein Unkraut oder eine Ernte sein.

Ein Hirsch kann ein Haustier, ein Vieh, ein Zootier oder eine geschützte Art sein.

Ein Löwe kann auch ein Zootier oder eine geschützte Art sein.

Das Leben ist nicht einfach.

James Anderson
quelle
0

Wie können Sie dieses Problem lösen, ohne Mehrfachvererbung und objektorientiertes Design zu verwenden?

Welches Problem? Was macht dieses System?Bis Sie das beantworten, habe ich keine Ahnung, welche Klassen erforderlich sein können. Versuchen Sie, eine Ökologie mit Fleisch- und Pflanzenfressern und Pflanzen zu modellieren, die Artenpopulationen in die Zukunft projiziert? Versuchen Sie, den Computer 20 Fragen spielen zu lassen?

Es ist Zeitverschwendung, mit dem Design zu beginnen, bevor Anwendungsfälle definiert wurden. Ich habe gesehen, dass dies zu lächerlichen Extremen geführt hat, als ein Team von ungefähr zehn Mitarbeitern begann, ein OO-Modell einer Fluggesellschaft mithilfe von Software anhand von Bildern zu produzieren. Sie arbeiteten zwei Jahre lang am Modellieren, ohne das eigentliche Geschäftsproblem im Auge zu behalten. Schließlich hatte der Kunde das Warten satt und bat das Team, ein tatsächliches Problem zu lösen. All diese Modellierung war völlig nutzlos.

Kevin Cline
quelle