Conception orientée objet

23

Supposons que vous ayez les éléments suivants:

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

Deerhérite Animalet Grasshérite de Food.

Jusqu'ici tout va bien. Animalles objets peuvent manger des Foodobjets.

Permet maintenant de mélanger un peu. Ajoutons un Lionqui hérite de Animal.

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

Maintenant, nous avons un problème parce que Lionpeut manger à la fois Deeret Grass, mais ce Deern'est pas Foodle cas Animal.

Sans utiliser l'héritage multiple et la conception orientée objet, comment résoudre ce problème?

Pour info: j'ai utilisé http://www.asciiflow.com pour créer les diagrammes ASCII.

Michael Irey
la source
14
La modélisation du monde réel est généralement un problème tôt ou tard, car il se passe toujours quelque chose d'étrange (comme le poisson volant, un poisson ou un oiseau? Mais un pingouin est un oiseau, ne peut pas voler et mange du poisson). Ce que @Ampt dit semble plausible, un animal devrait avoir une collection de choses qu'il mange.
Rob van der Veer
2
Je pense que l'animal devrait hériter de la nourriture. Si quelque chose essaie de manger un Lion, il suffit de lancer une InvalidOperationException.
RalphChapin
4
@RalphChapin: Toutes sortes de choses mangent le lion (vautours, insectes, etc.). Je pense que les animaux et les aliments sont des distinctions artificielles qui se briseront car ils ne sont pas assez larges (tous les animaux sont éventuellement d'autres aliments pour animaux). Si vous vous classiez sur "LivingThing", vous n'auriez à gérer les cas marginaux qu'avec des plantes qui mangent des choses non vivantes (minéraux, etc.), et cela ne casserait rien d'avoir LivingThing.Eat (LivingThing).
Satanicpuppy
2
Partager vos recherches aide tout le monde. Dites-nous ce que vous avez essayé et pourquoi cela n'a pas répondu à vos besoins. Cela démontre que vous avez pris le temps d'essayer de vous aider, cela nous évite de répéter des réponses évidentes et, surtout, cela vous aide à obtenir une réponse plus spécifique et plus pertinente. Voir aussi Comment demander
gnat
9
Cette question a été répondue par le jeu Age of Empire III. ageofempires.wikia.com/wiki/List_of_Animals Deer et Gazelle implémentent IHuntable, Sheep et Cow sont IHerdable(contrôlables par l'homme), et Lion implémente uniquement IAnimal, ce qui n'implique aucune de ces interfaces. AOE3 prend en charge l'interrogation de l'ensemble des interfaces prises en charge par un objet particulier (similaire à instanceof) qui permet à un programme d'interroger ses capacités.
rwong

Réponses:

38

EST une relation = héritage

Lion est un animal

A des relations A = composition

La voiture a une roue

Relations CAN DO = Interfaces

Je peux manger

Robert Harvey
la source
5
+1 C'est si simple et pourtant un si bon résumé des 3 différents types de relations
dreza
4
Alternative: ICanBeEatenouIEdible
Mike Weller
2
Relations CAN HAZ = lolcats
Steven A. Lowe
1
Comment cela répond-il à la question?
user253751
13

OO n'est qu'une métaphore qui s'inspire du monde réel. Mais les métaphores ne vont que si loin.

Normalement, il n'y a pas de bonne façon de modéliser quelque chose dans OO. Il existe une bonne façon de le faire pour un problème particulier dans un domaine particulier et vous ne devriez pas vous attendre à ce que cela fonctionne bien si vous modifiez votre problème, même si les objets du domaine sont les mêmes.

Je pense que c'est une idée fausse la plupart des Comp. Eng. les étudiants ont dans leurs premières années. L'OO n'est pas une solution universelle, juste un outil décent pour certains types de problèmes qui peuvent modéliser raisonnablement votre domaine.

Je n'ai pas répondu à la question, précisément parce que nous manquons d'informations sur le domaine. Mais avec ce qui précède à l'esprit, vous pourriez être en mesure de concevoir quelque chose qui convient à vos besoins.

DPM
la source
3
+1 OO est un outil, pas une religion.
mouviciel
Je suis d'accord, il n'y a peut-être pas de solution parfaite si ce problème continue de changer et d'évoluer. Dans son état actuel, ce problème manque-t-il d'informations sur le domaine pour trouver une solution?
Michael Irey
Pensez-vous sérieusement qu'un monde réel est modélisé dans OP? Un modèle de relation est représenté par un exemple.
Basilevs
@Basilevs C'est en quelque sorte l'implication, car il mentionne comment les animaux se comportent dans la vraie vie. Il faut se demander pourquoi a-t-on besoin que ce comportement soit pris en compte dans le programme de l'OMI. Cela dit, il aurait été bien de ma part de suggérer une conception possible.
DPM
10

Vous voulez décomposer davantage les animaux en leurs sous-classes (ou du moins dans la mesure où cela a du sens pour ce que vous faites). Étant donné que vous travaillez avec ce qui ressemble à des animaux de base et à deux types de nourriture (plantes et viande), il est logique d'utiliser des carnivores et des herbivores pour mieux définir un animal et le garder séparé. Voici ce que j'ai rédigé pour vous.

             +----------------+                   +--------------------+
             |    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 -------+

Comme vous pouvez le voir, ils exposent tous deux une méthode de manger, mais ce qu'ils mangent change. Le Lion peut maintenant tuer un cerf, le cerf peut mourir et retourner DeerMeat, et la question originale des OP sur la façon de permettre à un lion de manger un cerf mais pas de l'herbe est résolue sans concevoir un écosystème entier.

Bien sûr, cela devient très rapidement intéressant, car un cerf peut également être considéré comme un type de viande, mais pour garder les choses simples, je créerais une méthode appelée kill () sous le cerf, qui renvoie une viande de cerf, et la mettre comme un classe de béton prolongeant la viande.

Ampt
la source
Deer exposerait-il alors une interface IMeat?
Dan Pichelman
La viande n'est pas une interface, c'est une classe abstraite. J'ai ajouté comment je mettrais cela en œuvre pour vous
Ampt
Eat(Plant p)et les Eat(Meat m)deux violent LSP.
Tulains Córdova
Comment ça @ user61852? Je n'ai pas délibérément exposé Eat dans l'interface animale afin que chaque type d'animal puisse avoir sa propre méthode de manger.
Ampt
1
TCWL (Trop complexe, fuira). Le problème est distribué et émergent et votre solution est statique, centralisée et prédéfinie. TCWL.
Tulains Córdova
7

Ma conception serait comme ceci:

  1. Les aliments sont déclarés comme interfaces; il y a une interface IFood et deux interfaces dérivées: IMeat et IVegetable
  2. Les animaux mettent en œuvre IMeat et les légumes mettent en œuvre IVegetable
  3. Les animaux ont deux descendants, les carnivores et les hébivores
  4. Les carnivores ont la méthode Eat qui reçoit une instance de IMeat
  5. Les herbivores ont la méthode Eat qui reçoit une instance de IVegetable
  6. Lion descend de Carnivore
  7. Le cerf descend d'Herbivore
  8. L'herbe descend du légume

Parce que les animaux mettent en œuvre IMeat et le cerf est un animal (herbivore), le lion, qui est un animal (carnivore) qui peut manger IMeat, peut également manger du cerf.

Le cerf est un herbivore, il peut donc manger de l'herbe car il met en œuvre IVegetable.

Les carnivores ne peuvent pas manger IVegeable et les herbivores ne peuvent pas manger IMeat.

AlexSC
la source
1
Je vois beaucoup de types d'énumération ici utilisant l'héritage juste pour contraindre lorsque les types hérités n'implémentent rien ... Chaque fois que vous vous trouvez à créer des types qui n'implémentent aucune fonctionnalité, c'est un cadeau quelque chose est un pied; vous avez développé un modèle dans le système de type qui ne donne aucune valeur à l'utilisabilité dans le code
Jimmy Hoffa
N'oubliez pas qu'il existe des omnivores, comme les humains, les singes et les ours.
Tulains Córdova
Alors, comment ajoutez-vous que les lions et les cerfs sont des mammifères? :-)
johannes
2
@JimmyHoffa Celles-ci sont appelées "interfaces marqueurs" et constituent une utilisation totalement valide de l'interface. Il doit être revu par le code pour décider si l'utilisation est justifiée, mais il existe de nombreux cas d'utilisation (comme celui-ci, où un Lion essayant de manger de l'herbe lèverait une exception NoInterface). L'interface marqueur (ou son absence) sert à prédire une exception qui sera levée si une méthode est appelée avec des arguments non pris en charge.
rwong
1
@rwong Je comprends le concept, je ne l'ai jamais entendu formalisé auparavant; mon expérience a été chaque fois qu'une base de code dans laquelle je travaillais les rend plus complexes et plus difficiles à maintenir. Peut-être que mon expérience vient tout juste d'être là où les gens les ont mal utilisés.
Jimmy Hoffa
5

Quels aliments un animal peut manger ne forment pas réellement une hiérarchie, dans ce cas, la nature n'a pas pu se conformer inexcusablement à une modélisation orientée objet simple (notez que même si c'était le cas, l'animal devrait hériter de la nourriture, car c'est de la nourriture).

La connaissance des aliments qu'un animal peut manger ne peut pas vivre entièrement avec l'une ou l'autre des classes, donc simplement avoir une référence à un membre de la hiérarchie alimentaire ne peut pas être suffisant pour vous dire ce que vous pouvez manger.

C'est une relation plusieurs à plusieurs. Cela signifie que chaque fois que vous ajoutez un animal, vous devez déterminer ce qu'il peut manger et chaque fois que vous ajoutez un aliment, vous devez déterminer ce qui peut le manger. L'existence d'une structure supplémentaire à exploiter dépend des animaux et des aliments que vous modélisez.

L'héritage multiple ne résout pas non plus vraiment très bien cela. Vous avez besoin d'une sorte de collection de choses qu'un animal peut manger, ou d'animaux qui peuvent manger un aliment.

psr
la source
Comme ils disent à propos de l'expression régulière "J'ai eu un problème alors j'ai utilisé l'expression régulière, maintenant j'ai deux problèmes", MI est dans la ligne de "J'ai eu un problème alors j'ai utilisé MI, maintenant j'ai 99 problèmes" Si j'étais vous, je ' Si vous suivez ce vain que vous piquez ici si la nourriture sait ce qui peut être mangé, cela simplifie en fait le modèle d'une tonne. Inversion de dépendance FTW.
Jimmy Hoffa
1

J'aborderai le problème sous un angle différent: la POO concerne le comportement. Dans votre cas, a-t Grass-il un comportement dont être enfant Food? Donc, dans votre cas, il n'y aura pas de Grassclasse, ou du moins, il ne sera pas hérité de Food. De plus, si vous avez besoin de savoir qui peut manger quoi au moment de la compilation, il est douteux que vous ayez besoin d' Animalabstraction. De plus, il n'est pas rare de voir des carnivores manger de l'herbe , mais pas pour se nourrir.

Donc, je concevoir cela comme (ne va pas déranger avec l'art ASCI):

IEdibleavec propriété Type, qui est une énumération de viande, de plante, de carcasse, etc. (cela ne changera pas souvent et n'a pas de comportement spécifique, il n'est donc pas nécessaire de le modéliser comme recherche de classe).

Animalavec des méthodes CanEat(IEdible food)et Eat(IEdible food), qui sont logiques. Ensuite, des animaux spécifiques peuvent vérifier chaque fois qu'ils peuvent manger de la nourriture donnée dans des circonstances données, puis manger cette nourriture pour se nourrir / faire autre chose. De plus, je modéliserais les classes Carnivore, Herbivore, Omnivore comme modèle de stratégie , plutôt que dans le cadre de la hiérarchie animale.

Euphorique
la source
1

TL; DR: Conception ou modèle avec un contexte.

Je pense que votre question est difficile parce qu'elle manque de contexte du problème réel que vous essayez de résoudre. Vous avez des modèles et des relations, mais vous n'avez pas le cadre dans lequel il doit fonctionner. Sans contexte, la modélisation et les métaphores ne fonctionnent pas bien, laissant la porte ouverte à de multiples interprétations.

Je pense qu'il est plus productif de se concentrer sur la façon dont les données seront consommées. Une fois que vous avez le modèle d'utilisation des données, il est plus facile de revenir en arrière sur ce que devraient être les modèles et les relations.

Par exemple, des exigences plus détaillées nécessiteront différentes relations d'objet:

  • soutenir Animals eating non FoodsimilairesGastroliths
  • soutien Chocolatecomme Poisonpour Dogs, mais pas pourHumans

Si nous commençons par l'exercice de modélisation de la relation simple présentée, l'interface alimentaire peut être la meilleure; et si c'est la somme totale de la façon dont les relations dans le système, alors votre amende. Cependant, quelques exigences ou relations supplémentaires peuvent considérablement affecter les modèles et les relations qui fonctionnaient dans le cas le plus simple.

Dietbuddha
la source
Je suis d'accord, mais ce n'est qu'un petit exemple et je n'essayais pas de modéliser le monde. Par exemple, vous pouvez avoir un requin qui mange des pneus et des plaques d'immatriculation. Vous pouvez simplement créer une classe abstraite parent avec une méthode qui mange tout type d'objet et Food pourrait étendre cette classe abstraite.
hagensoft
@hagensoft: D'accord. Je m'emballe parfois parce que je vois constamment des développeurs modéliser sur la base d'une métaphore qu'ils ont immédiatement saisie, plutôt que de regarder comment les données doivent être consommées et utilisées. Ils se marient à une conception OO basée sur une idée initiale, puis essaient de forcer le problème à s'adapter à leur solution au lieu de faire en sorte que leur solution corresponde au problème.
dietbuddha
1

Approche de composition sur héritage ECS:

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)

Natureest une systemboucle qui parcourt ces entités, recherchant quels composants elles possèdent via une fonction de requête généralisée. Natureobligera les entités qui ont faim de viande à attaquer d'autres entités qui ont de la viande comme nourriture en utilisant leurs armes, sauf si elles ont une affinité avec cette entité. Si l'attaque réussit, l'entité se nourrira de sa victime, auquel cas la victime se transformera en cadavre privé de viande. Natureincitera les entités qui ont faim de plantes à se nourrir d'entités qui ont des plantes comme aliment, à condition qu'elles existent.

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))
    }
}

Peut-être que nous voulons étendre Grasspour avoir un besoin de soleil et d'eau, et nous voulons introduire le soleil et l'eau dans notre monde. Pourtant, Grassil ne peut pas les rechercher directement, comme il ne l'a pas fait mobility. Animalspeut également avoir besoin d'eau, mais peut le rechercher activement depuis mobility. Il est assez facile de continuer à étendre et à modifier ce modèle sans casse en cascade de la conception entière, car nous ajoutons simplement de nouveaux composants et étendons le comportement de nos systèmes (ou le nombre de systèmes).


la source
0

Sans utiliser l'héritage multiple et la conception orientée objet, comment résoudre ce problème?

Comme la plupart des choses, cela dépend .

Cela dépend de ce que vous voyez «ce problème».

  • S'agit-il d'un problème général de mise en œuvre , par exemple, comment contourner l'absence d'héritage multiple dans la plate-forme choisie?
  • Est-ce un problème de conception uniquement pour ce cas spécifique , par exemple comment modéliser le fait que les animaux sont aussi de la nourriture?
  • Est-ce un problème philosophique avec le modèle de domaine , par exemple, les classifications «alimentaire» et «animale» sont-elles valides, nécessaires et suffisantes pour l'application pratique envisagée?

Si vous posez des questions sur le problème général d'implémentation, la réponse dépendra des capacités de votre environnement. Les interfaces IFood et IAnimal pourraient fonctionner, avec une sous-classe EdibleAnimal implémentant les deux interfaces. Si votre environnement ne prend pas en charge les interfaces, faites simplement hériter Animal de la nourriture.

Si vous posez des questions sur ce problème de conception spécifique, faites simplement hériter les animaux de la nourriture. C'est la chose la plus simple qui pourrait fonctionner.

Si vous posez des questions sur ces concepts de conception, la réponse dépend fortement de ce que vous avez l'intention de faire avec le modèle. Si c'est pour un jeu vidéo chien-manger-chien ou même une application pour suivre les horaires d'alimentation dans un zoo, cela pourrait suffire. S'il s'agit d'un modèle conceptuel pour les modèles de comportement animal, c'est probablement un peu superficiel.

Steven A. Lowe
la source
0

L'héritage doit être utilisé pour quelque chose qui est toujours autre chose et ne peut pas changer. L'herbe n'est pas toujours de la nourriture. Par exemple, je ne mange pas d'herbe.

L'herbe joue le rôle d'un aliment pour certains animaux.

Neil McGuigan
la source
C'est juste une abstraction. Si c'est une exigence, vous pouvez créer plus de divisions qui étendent la classe abstraite des plantes et obliger les humains à manger une classe abstraite comme 'HumanEatablePlants' qui regrouperait les plantes que les humains mangent en classes concrètes.
hagensoft
0

Vous venez de découvrir la limitation de base de l'OO.

OO fonctionne bien avec les structures hiérarchiques. Mais une fois que vous vous éloignez des hiérarchies strictes, l'abstraction ne fonctionne pas si bien.

Je connais tout sur les compositions de métamorphose, etc. qui sont utilisées pour contourner ces limitations, mais elles sont maladroites et, plus important encore, conduisent à du code obscur et difficile à suivre.

Les bases de données relationnelles ont été inventées principalement pour échapper aux limites des structures hiérarchiques strictes.

Pour prendre votre exemple, l'herbe pourrait également être un matériau de construction, une matière première pour le papier, un matériau pour les vêtements, une mauvaise herbe ou une récolte.

Un cerf peut être un animal de compagnie, du bétail, un animal de zoo ou une espèce protégée.

Un lion peut également être un animal de zoo ou une espèce protégée.

La vie n'est pas simple.

James Anderson
la source
0

Sans utiliser l'héritage multiple et la conception orientée objet, comment résoudre ce problème?

Quel problème? Que fait ce système? Jusqu'à ce que vous répondiez à cela, je n'ai aucune idée des cours qui pourraient être requis. Essayez-vous de modéliser une écologie, avec des carnivores et des herbivores et des plantes, projetant des populations d'espèces dans le futur? Essayez-vous de faire jouer 20 questions à l'ordinateur?

C'est une perte de temps de commencer la conception avant de définir des cas d'utilisation. J'ai vu cela pris à des extrêmes ridicules lorsqu'une équipe d'une dizaine a commencé à produire un modèle OO d'une compagnie aérienne en utilisant un logiciel à travers des images. Ils ont travaillé pendant deux ans dans la modélisation sans aucun problème commercial réel. Enfin, le client s'est lassé d'attendre et a demandé à l'équipe de résoudre un problème réel. Toute cette modélisation était complètement inutile.

Kevin Cline
la source