La herencia salió mal

12

Tengo un código donde un buen modelo de herencia ha ido cuesta abajo y estoy tratando de entender por qué y cómo solucionarlo. Básicamente, imagine que tiene una jerarquía de zoológico con:

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

etc.

Tienes tus métodos eat (), run (), etc. y todo está bien. Entonces, un día aparece alguien y dice: nuestra clase CageBuilder funciona muy bien y usa animal.weight () y animal.height (), excepto el nuevo Bisonte africano que es demasiado fuerte y puede romper la pared, así que voy a agregar una propiedad más para la clase Animal: esAfricanBizon () y úsela al elegir el material y solo lo anule para la clase AfricanBizon. La siguiente persona viene y hace algo similar y lo siguiente que sabes es que tienes todas estas propiedades específicas para algún subconjunto de la jerarquía en la clase base.

¿Cuál es una buena manera de mejorar / refactorizar dicho código? Una alternativa aquí sería usar dynamic_casts para verificar los tipos, pero eso abarrota a las personas que llaman y agrega un montón de if-then-else por todas partes. Puede tener interfaces más específicas aquí, pero si todo lo que tiene es la referencia de clase base que tampoco ayuda mucho. ¿Cualquier otra sugerencia? Ejemplos?

¡Gracias!

naumcho
fuente
@ James: entonces tendrás que escribir analizadores a mano. : S
Matteo Italia
55
Claramente este es un caso de requerimiento ridículo del cliente. No hay bisontes en África. No puede diseñar modelos de objetos que no tengan conexión con la realidad. A menos que esa realidad sea creada por manos llenas de dólares. Lo que resuelve el problema.
Hans Passant
1
¿Comer todo el bisonte? [Publiqué esto anteriormente, pero por alguna razón fue eliminado, presumiblemente por tonterías sin humor.]
James McNellis
¿CageBuilder necesita su propia clase? ¿Qué sucede si hay un método MakeCage predeterminado, que puede ser anulado por cada clase individual?
Trabajo
1
Usted menciona el desorden if-then-else como un inconveniente para las personas que llaman, pero tan pronto como las personas que llaman comienzan a usar esAfricanBizon () abarrotan el código con if-then-else automáticamente. Por lo tanto, es desorden if-then-else con isAfricanBizon () o desorden if-then-else con lanzamientos dinámicos.
davidk01

Respuestas:

13

Parece que el problema es que en lugar de implementar RequiereConcretoMural (), implementaron una llamada de bandera IsAfricanBison (), y luego movieron la lógica sobre si el muro debería cambiar o no fuera del alcance de la clase. Sus clases deben exponer comportamiento y requisitos, no identidad; Sus consumidores de estas clases deben trabajar según lo que se les dice, no según lo que son.

Josh
fuente
1
-1: Solo dice qué no hacer. El OP ya sabe que esta fue una mala idea, de ahí la pregunta.
Steven Evers
12

isAfricanBizon () no es genérico. Suponga que extiende su granja de animales con un hipopótamo que también es demasiado fuerte, pero volver a ser cierto de isAfricanBizon () para tener el efecto adecuado sería una tontería.

siempre desea agregar métodos a la interfaz que respondan a la pregunta específica, en este caso sería algo así como fuerza ()

Karoly Horvath
fuente
+1: Todos los demás parecen estar rompiendo el modelo conceptual de la clase (que simplemente encapsula las propiedades de diferentes tipos de animales), para acomodar este caso de uso específico. Se strengthpuede consultar un método material.canHold(animal), permitiendo una forma limpia de soportar diferentes tipos de material que ConcreteWall.
Aidan Cully
Me gusta el enfoque de propiedad de la fuerza () mejor que la sugerencia de los demás de RequiereConcretoMural () porque es más flexible para permitir requisitos futuros. Para empezar, haga que la clase CageBuilder decida qué materiales son lo suficientemente fuertes, y luego puede extender fácilmente la clase con nuevos materiales.
jhocking
3

Creo que su problema es este: tiene varios clientes de la biblioteca que están interesados ​​solo en un subconjunto de la jerarquía pero a los que se les pasa un puntero / referencia a la clase base. De hecho, ese es el problema que dynamic_cast <> está ahí para resolver.

Es una cuestión de diseño de los clientes minimizar el uso de dynamic_cast <>; deben usarlo para determinar si el objeto requiere un tratamiento especial y, en caso afirmativo, hacer todas las operaciones en la referencia de fundición descendente.

Si tiene colecciones de funcionalidades de tipo "mix-in" que se aplican a varias sub-jerarquías separadas, es posible que desee usar el patrón de interfaz que usan Java y C #; tener una clase base virtual que es una clase puramente virtual, y usar dynamic_cast <> para determinar si una instancia proporciona una implementación para ella.

Antlersoft
fuente
1

Una cosa que puede hacer es reemplazar la comprobación explícita de tipo, como la isAfricanBison()comprobación de las propiedades que realmente le interesan, es decir isTooStrong().

hammar
fuente
1
isTooStrong () para qué? Estás agregando un código específico de la jaula a la clase animal.
Steven Evers
1

Los animales no deberían preocuparse por los muros de hormigón. Quizás puedas expresarlo con valores simples.

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

Aunque sospecho que eso no es viable. Sin embargo, ese es el problema con los ejemplos de juguetes.

Nunca quisiera ver RequiereConcreteWalls () o líneas y líneas de conversiones de puntero dinámico en cualquier caso.

Esta suele ser una solución barata . Es fácil de mantener y conceptualizar. Y realmente, el problema indica que está vinculado al tipo de animal de todos modos.

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

Esto tampoco le impide usar código compartido, solo contamina un poco a Animal.

Pero la forma en que se construye la jaula puede ser una política de algún otro sistema, y ​​tal vez tenga más de un tipo de constructor de jaulas por animal. Hay muchas combinaciones extrañas y complicadas que puedes encontrar.

He utilizado el diseño basado en componentes para buenos fines, el principal problema es que puede ser problemático cuando se comparte la propiedad de Animal. Cómo evitar tirar destructores siendo el punto de dolor.

Double Dispatch es otra opción, aunque siempre he sido reticente a participar.

Más allá de eso, es difícil adivinar el problema.

Tom Kerr
fuente
0

Bueno, seguramente todos los animales tienen la propiedad inherente de attemptEscape(). Mientras que algunos métodos pueden presentar un falseresultado en todos los escenarios, mientras que otros pueden tener una oportunidad basada en la heurística de sus otras características intrínsecas como sizey weight. Entonces, ciertamente, en algún momento se attemptEscape()vuelve trivial, ya que seguramente volverá true.

Sin embargo, me temo que no entiendo completamente tu pregunta ... todos los animales tienen acciones y características relacionadas. Los específicos para el animal deben introducirse donde encaja. Intentar relacionar directamente a Bison con los loros no es una buena configuración de inherencia y realmente no debería ser un problema en un diseño adecuado.


fuente
-1

Otra opción sería utilizar una fábrica que cree jaulas apropiadas para cada animal. Creo que esto puede ser mejor en caso de que las condiciones sean muy diferentes para cada uno de ellos. Pero si es solo esta condición, el RequiresConcreteWall()método mencionado anteriormente lo hará.

RocketR
fuente
-1

¿Qué tal recomendarCageType () como oppsed a RequireConcreteWall ()

Imbéciles
fuente
-2

¿Por qué no hacer algo como esto?

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

Con la clase HeavyAnimals puedes crear la clase African Bison extendiendo la clase HeavyAnimals.

Entonces, ahora la clase principal (los Animales) que se puede usar para crear otra clase base como la clase HeavyAnimal con se puede usar para crear la clase Bison africano y otros Animales pesados. Entonces, con el Bisonte africano ahora tienes acceso a los métodos y propiedades de la clase Animal (esta es la base para todos los animales) y acceso a la clase de Animales Pesados ​​(esta es la base para los Animales Pesados)

Mohhamed Rafi
fuente
2
Eso puede funcionar como una mezcla o rasgo, pero ciertamente no como una subclase. Esto es solo pedir herencia múltiple la próxima vez que se necesite otra propiedad.
Ordous