Diseño orientado a objetos

23

Supongamos que tiene lo siguiente:

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

Deerhereda de Animaly Grasshereda de Food.

Hasta aquí todo bien. AnimalLos objetos pueden comer Foodobjetos.

Ahora vamos a mezclarlo un poco. Vamos a agregar un Lionque hereda de Animal.

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

Ahora tenemos un problema porque Lionpodemos comer ambos Deery Grass, pero Deerno Foodes así Animal.

Sin usar la herencia múltiple y el diseño orientado a objetos, ¿cómo se resuelve este problema?

FYI: Usé http://www.asciiflow.com para crear los diagramas ASCII.

Michael Irey
fuente
14
El modelado del mundo real suele ser un problema tarde o temprano, porque siempre está sucediendo algo extraño (¿como un pez volador, un pez o un pájaro? Pero un pingüino es un pájaro, no puede volar y come pescado). Lo que dice @Ampt suena plausible, un animal debe tener una colección de cosas que come.
Rob van der Veer
2
Creo que los animales deberían heredar de los alimentos. Si algo intenta comerse un León, simplemente lanza una InvalidOperationException.
RalphChapin
44
@RalphChapin: Todo tipo de cosas comen leones (buitres, insectos, etc.). Creo que los animales y los alimentos son distinciones artificiales que se romperán porque no son lo suficientemente amplios (todos los animales son otros alimentos para animales, eventualmente). Si clasificaras en "LivingThing", solo tendrías que lidiar con los casos extremos con plantas que comen cosas no vivas (minerales, etc.), y no rompería nada tener LivingThing.Eat (LivingThing).
Satanicpuppy
2
Compartir su investigación ayuda a todos. Cuéntanos qué has probado y por qué no satisfizo tus necesidades. Esto demuestra que te has tomado el tiempo para tratar de ayudarte a ti mismo, nos salva de reiterar respuestas obvias y, sobre todo, te ayuda a obtener una respuesta más específica y relevante. También vea Cómo preguntar
mosquito
99
Esta pregunta ha sido respondida por el juego Age of Empire III. ageofempires.wikia.com/wiki/List_of_Animals Implementan Deer and Gazelle IHuntable, Sheep and Cow son IHerdable(controlables por humanos), y Lion solo implementa IAnimal, lo que no implica ninguna de esas interfaces. AOE3 admite la consulta del conjunto de interfaces compatibles con un objeto particular (similar a instanceof) que permite que un programa consulte sus capacidades.
rwong

Respuestas:

38

ES una relación = herencia

El león es un animal

TIENE relaciones A = Composición

El auto tiene una rueda

PUEDE HACER relaciones = Interfaces

Puedo comer

Robert Harvey
fuente
55
1 Esto es tan simple ya la vez un resumen tan buena de los 3 tipos diferentes de relación
dreza
44
Alternativa: ICanBeEatenoIEdible
Mike Weller
2
Relaciones CAN HAZ = lolcats
Steven A. Lowe
1
¿Cómo responde esto a la pregunta?
user253751
13

OO es solo una metáfora que sigue el modelo del mundo real. Pero las metáforas solo van tan lejos.

Normalmente no hay una forma correcta de modelar algo en OO. Hay una manera correcta de hacerlo para un problema particular en un dominio particular y no debe esperar que funcione bien si cambia su problema, incluso si los objetos de dominio son los mismos.

Creo que este es un error común en la mayoría de los Comp. Ing. los estudiantes tienen en sus primeros años. OO no es una solución universal, solo una herramienta decente para algún tipo de problema que puede modelar su dominio razonablemente bien.

No respondí la pregunta, precisamente porque nos falta información de dominio. Pero teniendo en cuenta lo anterior, es posible que pueda diseñar algo que se adapte a sus necesidades.

DPM
fuente
3
+1 OO es una herramienta, no una religión.
mouviciel
Estoy de acuerdo, puede que no haya una solución perfecta si este problema continúa cambiando y evolucionando. En su estado actual, ¿este problema carece de información de dominio para encontrar una solución?
Michael Irey
¿Estás pensando seriamente que un mundo real está siendo modelado en OP? Un modelo de relación se representa a través de un ejemplo.
Basilevs
@Basilevs Esa es la implicación, en realidad, ya que menciona cómo se comportan los animales en la vida real. Uno debe preocuparse por qué necesita ese comportamiento para tener en cuenta en el programa, la OMI. Dicho esto, hubiera sido amable de mi parte sugerir algún posible diseño.
DPM
10

Desea dividir aún más a los animales en sus subclases (o al menos en la medida en que tenga sentido para lo que está haciendo). Dado que está trabajando con lo que parecen animales básicos y dos tipos de alimentos (plantas y carne), tiene sentido usar carnívoros y herbívoros para definir mejor a un animal y mantenerlos separados. Esto es lo que redacté para ti.

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

Como puede ver, ambos exponen un método de comer, pero lo que comen cambia. El León ahora puede matar a un ciervo, el ciervo puede morir y devolver DeerMeat, y la pregunta original de OP sobre cómo permitir que un león coma un ciervo pero no hierba se responde sin diseñar un ecosistema completo.

Por supuesto, esto se vuelve interesante muy rápidamente porque un ciervo también podría considerarse un tipo de carne, pero para simplificar las cosas, crearía un método llamado kill () debajo de los ciervos, que devuelve una carne de ciervo, y lo pongo como un clase concreta que extiende la carne.

Ampt
fuente
¿Deer entonces expondría una interfaz IMeat?
Dan Pichelman
La carne no es una interfaz, es una clase abstracta.
Agregué
Eat(Plant p)y Eat(Meat m)ambos violan LSP.
Tulains Córdova
¿Cómo es eso @ user61852? A propósito no expuse Eat in the animal interface para que cada tipo de animal pueda tener su propio método de comer.
Ampt
1
TCWL (Demasiado complejo, tendrá fugas). El problema es distribuido y emergente y su solución es estática, centralizada y predefinida. TCWL.
Tulains Córdova
7

Mi diseño sería así:

  1. Los alimentos se declaran como interfaces; hay una interfaz IFood y dos interfaces derivadas: IMeat e IVegetable
  2. Los animales implementan IMeat y las verduras implementan IVegetable
  3. Los animales tienen dos descendientes, carnívoros y hebívoros.
  4. Los carnívoros tienen el método Eat que recibe una instancia de IMeat
  5. Los herbívoros tienen el método Eat que recibe una instancia de IVegetable
  6. León desciende de carnívoro
  7. El ciervo desciende del herbívoro
  8. La hierba desciende de la verdura

Debido a que los animales implementan IMeat y Deer es un animal (herbívoro), Lion, que es un animal (carnívoro) que puede comer IMeat también puede comer Deer.

El ciervo es un herbívoro, por lo que puede comer hierba porque implementa IVegetable.

Los carnívoros no pueden comer IVegeable y los herbívoros no pueden comer IMeat.

AlexSC
fuente
1
Veo muchos tipos de enumeración aquí usando la herencia solo para restringir cuando los tipos que se heredan no implementan nada ... Cada vez que te encuentras haciendo tipos que no implementan ninguna funcionalidad en absoluto, es un obsequio que algo es un pie; expandió un modelo en el sistema de tipos que no da valor a la usabilidad en el código
Jimmy Hoffa
Recuerde que existen omnívoros, como humanos, simios y osos.
Tulains Córdova
Entonces, ¿cómo se agrega que ambos, leones y venados, son mamíferos? :-)
johannes
2
@JimmyHoffa Esas se llaman "interfaces de marcador" y son un uso totalmente válido de la interfaz. Es necesario revisar el código para decidir si el uso está justificado, pero hay muchos casos de uso (como este, donde un León que intenta comer hierba arrojaría una excepción NoInterface). La interfaz del marcador (o la falta de) sirve para predecir una excepción que se lanzará si se llama a un método con argumentos no compatibles.
rwong
1
@rwong Entiendo el concepto, nunca lo escuché formalizado antes; solo mi experiencia ha sido cada vez que una base de código en la que he estado trabajando los hace más complejos y difíciles de mantener. Quizás mi experiencia es, sin embargo, simplemente donde la gente los usó mal.
Jimmy Hoffa
5

Los alimentos que puede comer un animal en realidad no forman una jerarquía, en este caso, la naturaleza no se ajustó inexcusablemente al modelado simple orientado a objetos (tenga en cuenta que incluso si lo hiciera, el animal tendría que heredar de la comida, ya que es comida).

El conocimiento de qué alimentos puede comer un animal no puede vivir completamente con ninguna de las clases, por lo que simplemente tener una referencia a algún miembro de la jerarquía alimentaria no puede ser suficiente para decirle qué cosas puede comer.

Es una relación de muchos a muchos. Esto significa que cada vez que agrega un animal, necesita descubrir qué puede comer, y cada vez que agrega un alimento, necesita averiguar qué puede comerlo. Si hay más estructura para explotar depende de qué animales y alimentos esté modelando.

La herencia múltiple tampoco resuelve esto muy bien. Necesita algún tipo de colección de cosas que un animal puede comer, o de animales que pueden comer un alimento.

psr
fuente
Como dicen sobre regex "Tenía un problema, así que usé regex, ahora tengo dos problemas", MI está en la línea de "Tuve un problema, así que usé MI, ahora tengo 99 problemas" Si fuera usted, yo ' Si seguimos ese vano en el que estabas hurgando porque la comida sabe lo que puede comer, esto en realidad simplifica mucho el modelo. Inversión de dependencia FTW.
Jimmy Hoffa
1

Abordaré el problema desde un lado diferente: la POO se trata de comportamiento. En su caso, ¿ Grasstiene algún comportamiento del que ser hijo Food? Entonces, en su caso, no habrá Grassclase, o al menos, no se heredará de Food. Además, si necesita imponer quién puede comer qué en el momento de la compilación, es cuestionable si necesita Animalabstracción. Además, no es raro ver carnívoros comiendo hierba , aunque no para su sustento.

Así que diseñaría esto como (sin molestarme con el arte ASCI):

IEdiblecon propiedad Type, que es una enumeración de carne, planta, canal, etc. (esto no cambiará con frecuencia y no tiene ningún comportamiento específico, por lo tanto, no es necesario modelar esto como una jerarquía de clases).

Animalcon métodos CanEat(IEdible food)y Eat(IEdible food), que son lógicos. Luego, los animales específicos pueden verificar cada vez que pueden comer alimentos dados en circunstancias dadas y luego comer esos alimentos para obtener sustento / hacer otra cosa. Además, modelaría las clases Carnívoro, Herbívoro, Omnívoro como Patrón de estrategia , que como parte de la jerarquía animal.

Eufórico
fuente
1

TL; DR: Diseño o modelo con un contexto.

Creo que su pregunta es difícil porque carece de contexto del problema real que está tratando de resolver. Tiene algunos modelos y algunas relaciones, pero carece del marco en el que necesita trabajar. Sin contexto, el modelado y las metáforas no funcionan bien, dejan la puerta abierta a múltiples interpretaciones.

Creo que es más productivo centrarse en cómo se consumirán los datos. Una vez que tenga el patrón de uso de datos, es más fácil retroceder a lo que deberían ser los modelos y las relaciones.

Por ejemplo, requisitos más detallados requerirán diferentes relaciones de objeto:

  • apoyo Animals eatno FoodsimilarGastroliths
  • soporte Chocolatecomo Poisonpara Dogs, pero no paraHumans

Si comenzamos con el ejercicio de cómo modelar la relación simple presentada, la interfaz de alimentos puede ser la mejor; y si esa es la suma total de las relaciones en el sistema, entonces está bien. Sin embargo, solo unos pocos requisitos o relaciones adicionales pueden afectar enormemente los modelos y las relaciones que funcionaron en el caso más simple.

dietbuddha
fuente
Estoy de acuerdo, pero es solo un pequeño ejemplo y no intentamos modelar el mundo. Por ejemplo, puede tener un tiburón que come neumáticos y placas de matrícula. Simplemente puede hacer una clase abstracta principal con un método que coma cualquier tipo de objeto y Food podría extender esta clase de abstact.
hagensoft
@hagensoft: De acuerdo. A veces me dejo llevar porque veo constantemente a los desarrolladores modelando sobre la base de una metáfora de la que se apoderaron inmediatamente, en lugar de ver cómo los datos deben ser consumidos y utilizados. Se casan con un diseño OO basado en una idea inicial y luego intentan forzar el problema para que se ajuste a su solución en lugar de hacer que su solución se ajuste al problema.
dietbuddha
1

Enfoque de composición sobre herencia de 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.

Pseudocódigo:

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)

Naturees un systemque recorre estas entidades, buscando qué componentes tienen a través de una función de consulta generalizada. Naturehará que las entidades con hambre de carne ataquen a otras entidades que tienen carne como alimento usando sus armas, a menos que tengan una afinidad con esa entidad. Si el ataque tiene éxito, la entidad se alimentará de su víctima, momento en el cual la víctima se convertirá en un cadáver privado de carne. Naturehará que las entidades con hambre de plantas se alimenten de entidades que tienen plantas como alimento, siempre que existan.

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

Quizás queremos extendernos Grasspara tener una necesidad de luz solar y agua, y queremos introducir la luz solar y el agua en nuestro mundo. Sin embargo Grass, no puede buscarlos directamente, ya que no tiene mobility. AnimalsTambién puede necesitar agua, pero puede buscarla activamente ya que la tienen mobility. Es bastante fácil seguir ampliando y cambiando este modelo sin romper en cascada todo el diseño, ya que solo agregamos nuevos componentes y ampliamos el comportamiento de nuestros sistemas (o la cantidad de sistemas).


fuente
0

Sin usar la herencia múltiple y el diseño orientado a objetos, ¿cómo se resuelve este problema?

Como la mayoría de las cosas, depende .

Depende de lo que vea 'este problema'.

  • ¿Es un problema de implementación general , por ejemplo, cómo "sortear" la ausencia de herencia múltiple en la plataforma elegida?
  • ¿Es un problema de diseño solo para este caso específico , por ejemplo, cómo modelar el hecho de que los animales también son alimento?
  • ¿Es un problema filosófico con el modelo de dominio , por ejemplo, son 'alimentos' y 'animales' clasificaciones válidas, necesarias y suficientes para la aplicación práctica prevista?

Si está preguntando sobre el problema general de implementación, la respuesta dependerá de las capacidades de su entorno. Las interfaces IFood e IAnimal podrían funcionar, con una subclase EdibleAnimal que implementa ambas interfaces. Si su entorno no admite interfaces, simplemente haga que Animal herede de Food.

Si está preguntando sobre este problema de diseño específico, simplemente haga que Animal herede de Food. Es lo más simple que podría funcionar.

Si está preguntando acerca de estos conceptos de diseño, la respuesta depende en gran medida de lo que pretenda hacer con el modelo. Si es para un videojuego perro-come-perro o incluso una aplicación para rastrear los horarios de alimentación en un zoológico, podría ser suficiente para trabajar. Si se trata de un modelo conceptual para patrones de comportamiento animal, probablemente sea un poco superficial.

Steven A. Lowe
fuente
0

La herencia debe usarse para algo que siempre es otra cosa y que no puede cambiar. La hierba no siempre es comida. Por ejemplo, yo no como hierba.

La hierba desempeña el papel de un alimento para ciertos animales.

Neil McGuigan
fuente
Es solo una abstracción. Si ese es un requisito, entonces podría crear más divisiones que extiendan la clase abstracta Planta y hacer que los humanos coman una clase abstracta como 'HumanEatablePlants' que agruparía las plantas que los humanos comen en clases concretas.
hagensoft
0

Acaba de encontrar la limitación básica de OO.

OO funciona bien con estructuras jerárquicas. Pero una vez que te alejas de las jerarquías estrictas, la abstracción no funciona tan bien.

Sé todo sobre las composiciones de metamorfosis, etc., que se utilizan para sortear estas limitaciones, pero son torpes y, lo que es más importante, conducen a un código oscuro y difícil de seguir.

Las bases de datos relacionales se inventaron principalmente para escapar de las limitaciones de las estructuras jerárquicas estrictas.

Para tomar su ejemplo, el césped también podría ser un material de construcción, una materia prima para el papel, un material de ropa, una maleza o un cultivo.

Un ciervo puede ser una mascota, ganado, un animal de zoológico o una especie protegida.

Un león también podría ser un animal de zoológico o una especie protegida.

La vida no es simple.

James Anderson
fuente
0

Sin usar la herencia múltiple y el diseño orientado a objetos, ¿cómo se resuelve este problema?

¿Qué problema? ¿Qué hace este sistema? Hasta que responda eso, no tengo idea de qué clases pueden ser necesarias. ¿Estás tratando de modelar una ecología, con carnívoros, herbívoros y plantas, proyectando poblaciones de especies hacia el futuro? ¿Estás tratando de que la computadora juegue 20 preguntas?

Es una pérdida de tiempo comenzar el diseño antes de que se hayan definido los casos de uso. He visto esto llevado a extremos ridículos cuando un equipo de unos diez comenzó a producir un modelo OO de una aerolínea utilizando software a través de imágenes. Trabajaron durante dos años modelando sin ningún problema comercial real en mente. Finalmente, el cliente se cansó de esperar y le pidió al equipo que resolviera un problema real. Todo ese modelado fue completamente inútil.

Kevin Cline
fuente