2010-11-25 22 views
5

Recientemente se me dio una tarea en la que tenía que implementar algo similar a lo siguiente:C++ - ¿Uso excesivo de métodos virtuales?

Hay algunos animales con ciertos atributos, tales como:

Dog1: Nombre: Tery, color: blanco, bebida fav : jugo de uva
Dog2: nombre: Chiva, color: negro, bebida favorito: limonada
Bird1: nombre: tweety, canfly: sí, cansing: no
Bird2: nombre: Parry, canfly: no, cansing: sí

¿Cómo harías esto en C++ de manera eficiente usando OOP prr ¿Actices?

hice algo como esto:

class Animal { 
    Animal(...); 
    ... 
    public String getName() const; 
    public void setName(string s); 
    ... 
    private: 
    String name; 
} 

class Bird : public Animal { 
    Bird(...); 

    public bool canFly() const; 
    public void setCanFly(bool b); 
    ... 

    private: 
    bool canFly; 
    bool canSing; 
} 

class Dog : public Animal { 
    ... 
} 

El problema de esta aplicación es que no puedo beneficiarse de polymorhism:

Animal* p = new Anima(...); 
... 
p->canFly(); 

y tengo que usar de calidad:

((Bird*)p)->canFly(); 

Al final fui criticado por no usar funciones virtuales en clase base y usar moldes en lugar de OOP.

Pero en mi opinión no tiene sentido usar funciones virtuales aquí porque getName() debe estar en la clase base para evitar múltiples implementaciones del mismo método. Y canFly no es una propiedad válida para perros, por ejemplo.

Entonces habría que definir algo absurdo como esto uno para el otro (futuro) de los animales que también se hereda de la clase base, lo que crearía una sobrecarga innecesaria:

bool Dog::canFly() const { 
return false; 
} 

Quién es aquí, hice no obtener los principios básicos del polimorfismo?

+0

Si la pregunta se presentó así (sin otras partes), entonces, sí, creo que no hay ningún caso para las funciones virtuales, excepto el destructor. – lijie

+0

Su programa está escrito básicamente en Java, no en C++. ¿Por qué lo preguntas como una pregunta de C++? Algunas de las personas aquí lo han editado para que sea más C++ que Java, pero creo que es un error porque oscurece algo importante acerca de la pregunta. – Omnifarious

+2

El problema con la presentación del polimorfismo es que uno intenta modelar la realidad (animales, automóviles) donde el polimorfismo se vuelve difícil. Cuando se usa en software (estado, estrategia, envío de mensajes), es tan obvio que uno se pregunta cuál podría ser la dificultad. – stefaanv

Respuesta

7

Por supuesto 'canFly' es una propiedad válida para un perro, simplemente va a devolver falso.

No tiene sentido tener canFly en absoluto si solo lo implementa cuando es necesario. En tu ejemplo, para cuando hayas hecho tu caso de estilo c con un animal volador, ya te has comprometido con el tipo de animal, y en ese momento no necesitas llamar a canFly, porque ya sabes la respuesta.

Si realmente no desea implementar canFly en una gran cantidad de animales que no vuelan, implemente virtual bool canFly() const {return false; } en su clase base, y simplemente anularlo en los animales voladores.

Supongo que esta es solo una pregunta inventada de 'tarea', por lo que la respuesta seguramente se verá artificial, pero un estilo que involucre muchos tipos de objetos de fundición realmente será una mala noticia en el trabajo real.

+0

Bien , no es necesario que sea cierto para las aves ya que hay muchas aves no voladoras. Supongo que puedes acumular muchos sobrecargas de esa manera. – Lagerbaer

+0

Um. La mosca blanca para pájaros puede ser falsa, como se ve en el ejemplo 'Bird2'. – lijie

+1

En el futuro si decido agregar canguros, gatos, etc. Para cada nueva clase tengo que implementar canFly(), canSing(), canSwim() etc. – Caner

0

Creo que tienes razón. Agregar todas las propiedades concebibles que una familia de animales puede tener a una clase base Animal es simple y produce demasiada sobrecarga.

Aunque está claro lo que se pretendía en la tarea, es decir, que realmente tiene una función virtual canFly en la clase base, creo que es un diseño deficiente.

+2

¿Por qué? canFly responde una pregunta que es perfectamente válida para cualquier animal, ave o no. ¿Qué tal los murciélagos?¿Qué tal volar zorros? – dajames

+0

no solo para aquellos. Es una pregunta perfectamente válida para las lombrices de tierra y los gatos domésticos también. No pueden volar, pero la pregunta es significativa (y de hecho, diría que es una propiedad definitoria bastante importante para una lombriz que no puede volar. * Necesita * poder responder esta pregunta) – jalf

7

Bueno, no necesitas una sola clase base. Considere esto:

Animal 
    | 
    |--Flying Animal 
    |  |---Bird 
    | 
    |--Non Flying Animal 
      |---Dog 

donde:

class Animal 
{ 
public: 
    virtual bool CanFly() = 0; 
    String Name(); 
}; 

class Flying : public Animal 
{ 
public: 
    virtual bool CanFly() { return true; } 
}; 

class NonFlying : public Animal 
{ 
public: 
    virtual bool CanFly() { return false; } 
}; 

class Bird : public Flying 
{ 
}; 

class Dog : public NonFlying 
{ 
}; 

Hay muchas otras maneras de hacer esto así, incluso utilizando la composición en lugar de herencia.

EDIT: Composición. Tener una jerarquía en la que cada nivel de la jerarquía representa un grupo más pequeño de miembros (hay menos perros que animales) presenta el problema de cómo dividir el conjunto de todos los tipos posibles en una jerarquía. Como señaló Lagerbaer en los comentarios, algunas aves no pueden volar.

Así que en lugar de crear un árbol compleja, tener un simple árbol (o ningún árbol) y tienen cada animal contener una lista de características de ese animal:

class Animal 
{ 
public: 
    String Name(); 
    List <Characteristic> Characteristics(); 
}; 

class Characteristic 
{ 
public: 
    String Name(); 
}; 

class CanFly : public Characteristic 
{ 
public: 
    bool CanFly(); 
}; 

class Legs : public Characteristic 
{ 
public: 
    int NumberOfLegs(); 
}; 

Y luego, para crear un perro:

Animal *CreateDog() 
{ 
    Animal *dog = new Animal; 
    dog->Characteristics()->Add (new CanFly (false)); 
    dog->Characteristics()->Add (new NumberOfLegs (4)); 
    return dog; 
} 

y crear un pájaro:

Animal *CreateBird() 
{ 
    Animal *bird = new Animal; 
    bird->Characteristics()->Add (new CanFly (true)); 
    bird->Characteristics()->Add (new NumberOfLegs (2)); 
    return bird; 
} 

Hay dos ventajas a esto:

  1. Puede ampliarlo para agregar las características que desee.
  2. Puede controlar la creación de animales a partir de datos en lugar de codificarlo todo.

Si el idioma de su elección admite la reflexión, la búsqueda de la lista de características es muy sencilla. En los lenguajes que no son de reflexión, deberá implementar alguna forma de identificar qué características tiene cada uno.

+0

@Skizz creo que su realización es mejor !;) – Edward83

+0

¿Qué hay de los pájaros Kiwi? Ellos no pueden volar. – Lagerbaer

+0

@Lagerbaer: ¿Qué tal 'clase NonFlyingBird: Bird {virtual bool CanFly() {return false; }}; ' – Skizz

1

Podría ser demasiado en ese caso simple, pero más adelante podría mantener todos sus animales en una lista vinculada (o lista estándar o matriz o lo que sea) y luego iterar sobre todas las entradas y simplemente llamar a los métodos base para hacer todo tipo de cosas sin tener que preocuparse por los moldes.

Solo piense en un juego simple con GameObject siendo la clase base y los Métodos update() y draw() siendo virtuales. A continuación, heredas otras clases, p. PlayerObject, EnemyObject, PowerUpObject, etc.

En el bucle principal, a continuación, podría hacer algo como esto:

GameObject *p = firstObject; 
while(p) 
{ 
    p->update(); 
    p = p->nextObject; 
} 

Esto iterar sobre todos los objetos del juego y llamar a los update() métodos adecuados (por ejemplo, mover el jugador, permitiendo un giro de encendido o lo que sea) y no tienes que hacer un casting especial, por ejemplo revisando para ver si es un jugador o lo que sea.

+0

Mejor que eso podría ser utilizar una fábrica que ejemplifique una subclase específica de _ "updater" _ para cada tipo de GameObject. – Trinidad

0

Declarar algo virtual no le impide implementarlo en la clase base.

Es un mecanismo para decir que debe usar la implementación más específica disponible. Es distinto de superar la implementación en la clase derivada.

¿Por qué debería ser un problema devolver un mensaje falso de canFly() para un perro? Algunas aves no pueden volar y no hay pájaros que puedan volar.

3

Los métodos virtuales solo tienen sentido cuando existe la necesidad de que las subclases proporcionen su propia implementación, o que las fuercen (puramente virtuales).

En el caso de su uso canFly y canSing, donde los miembros de datos de la base de apoyo de la clase aplicación invariable en todas las subclases, por lo que los métodos Get/set virtual no tiene ningún sentido en absoluto para mí.

un mejor candidato para virtual sería los correspondientes fly y sing métodos, donde la implementación de la clase base podría tirar y sólo cuando las propiedades se establecen true se proporcionaría una implementación específica del animal en una subclase.

5

Para abordar la cuestión técnica, esto es incorrecto:

((Bird*)p)->canFly(); 

Esta conversión de estilo C realiza una static_cast; si p apunta a Dog, el lanzamiento tendrá éxito pero el resultado será incorrecto. Cosas malas suceden

Cuando no se conoce el tipo más derivada de la punta-a objetar y no tiene alguna forma de determinar su tipo a través del puntero de clase base, es necesario utilizar dynamic_cast:

if (Bird* bp = dynamic_cast<Bird*>(p)) { 
    // p points to a Bird 
} 
else { 
    // p points to something else 
} 

dynamic_cast devuelve un puntero nulo si el lanzamiento falla, lo que le permite verificar el tipo del objeto.


Para resolver el problema de diseño, depende. En el software del mundo real, no siempre se pueden tener funciones virtuales en la clase base que definan el comportamiento de todas las posibles clases derivadas. Simplemente no es posible. A veces es necesario dynamic_cast a una clase derivada para poder llamar a funciones no declaradas en la clase base.

Los yesos probablemente no eran necesarios en este caso muy simple, pero si tuviera en cuenta una jerarquía de clases más completa que definiera el reino animal, encontraría que necesitaría una enorme cantidad de funciones en la clase base Animal o tendrías que usar yesos en algún momento.

+0

Tienes razón, gracias. – Caner

3
struct Animal { 
    std::string name; 
    std::string favoriteDrink; 
    bool canFly; 
    bool canSing; 
}; 

Siéntase libre de envolver get/setters alrededor de los miembros si esto lo hace feliz.

Pero una cosa que la gente tiende a olvidar es que el polimorfismo es sobre comportamiento. Se trata de hacer diferentes clases que se ven iguales, pero se comportan de manera diferente.

En este ejemplo, no hay comportamiento diferente entre ninguno de los animales, por lo que hacer más de una clase es exagerado.

No existe un comportamiento real requerido para ninguno de los animales. Las únicas operaciones que necesitamos son la capacidad de preguntar "¿cómo se llama", "puede volar", "puede cantar" (y, por supuesto, "se mezclará?")

Todas estas operaciones tienen tanto sentido para un pingüino como lo hacen en un terrier, una ballena azul o una musaraña. El comportamiento es el mismo, sólo cambian los datos. Y lo que debe ser uno clase, con diferentes instancias para diferentes animales.

Y por eso tratar de dividirlos en clases separadas va en contra de todos los objetivos de OOP: terminas duplicando código intencionalmente, haciendo menos reutilización de código, y estás haciendo tu código menos polimórfico, no más. En mi solución, cualquier animal es un reemplazo directo para cualquier otro animal. Una vez que empiezas a jugar con diferentes clases y virtudes métodos reales, debe escribir un nuevo código para cada animal nuevo para que sea una implementación adecuada de la clase base Animal.

Si alguna vez necesita agregar el método real Fly(), es posible que necesite diferentes clases. La mecánica del vuelo es diferente para un gorrión, un águila y un murciélago (aunque esto depende del objetivo. Dependiendo del nivel de abstracción en que esté trabajando la aplicación, la rutina "volar" podría consistir en nada más que establecer otra bandera bool en algún lugar, o tal vez darle al animal una altitud positiva distinta de cero, en cuyo caso la misma implementación es reutilizable para cualquier animal volador).

Pero por el momento, todo lo que necesitamos es la capacidad de preguntar si un animal puede o no volar. Y la implementación de que es trivialmente reutilizable.

Pero, por supuesto, de la tarea que se le indicó que la respuesta correcta (donde "correcto" se define como "lo que esperaba cuando formulé la pregunta" es "usar muchos métodos virtuales para todo, y dar todo su propia clase".

cual sólo sirve para demostrar que cuanto más POO fanatismo que se obtiene de una persona, menor es la probabilidad de que en realidad entienden programación orientada a objetos.

Ver también my blog post on the topic

+3

Los perros son probablemente más difíciles de mezclar que las aves, pero si se les da una licuadora suficientemente grande ... –

+0

El único problema aquí es que todos los animales contienen todas las propiedades, incluso si la propiedad no es relevante para el animal en cuestión. Entonces, pensando en un ejemplo, si tuvieras: ojos móviles (perro = sí, araña = no), también puedes tener ojos movibles independientemente (camaleón = sí, perro = no) pero no es relevante para las arañas, pero aún tienes la propiedad en el objeto. – Skizz

+1

Perdone mi ignorancia de la zoología, pero ¿por qué la pregunta no es relevante para las arañas? De todos modos, tienes un punto válido, por supuesto. Sugerí una solución simple * al problema planteado por el OP *. Si sabe qué tipo de extensiones van a ser necesarias más adelante, entonces podrá usar este conocimiento para diseñar una mejor solución. Pero si no se sabe nada acerca de cómo evolucionará el sistema, entonces elegiré la solución más simple que se adapte a la tarea simple que tengo * ahora *. – jalf

0

En mi humilde opinión, tener getter y métodos setter es indicativo de un diseño pobre orientado a objetos. Y este espacio problemático no es particularmente propicio para mostrar lo que es un buen diseño orientado a objetos.

Cuestiones relacionadas