videos sonidos que programming personas perros perro para otros oriented obedezcan noche mundo muchos mucho las ladrido ladre ladrar ladrando ladra hacer extraños enojado dibujo deje concepts como chistoso calmar callar bravos aullando c++ oop

c++ - sonidos - OOP: formas de auto-dibujo y perros ladrando.



sonidos para perros para que obedezcan (6)

La mayoría de los libros sobre programación orientada a objetos que he leído utilizaban una clase Shape con una función miembro Shape.draw() o una clase Dog con una función miembro Dog.talk() , o algo similar, para demostrar el concepto de polimorfismo. Ahora, esto ha sido una fuente de confusión para mí, que no tiene nada que ver con el polimorfismo.

class Dog : public Animal { public: ... virtual void talk() { cout << "bark! bark!" << endl; } ... };

Si bien esto podría funcionar como un simple ejemplo, no puedo imaginar una buena manera de hacer que esto funcione en una aplicación más complicada, donde Dog.talk () podría necesitar acceder a subrutinas de sonido de otra clase, por ejemplo, para jugar a bark.mp3 En lugar de usar cout para la salida. Digamos que tengo:

class Audio { public: ... void playMP3(const string& filename) ... };

¿Cuál sería una buena manera de acceder a Audio.playMP3() desde Dog.talk () en tiempo de diseño? Hacer Audio.playMP3() estático? ¿Pasar por los punteros de función? ¿Tiene Dog.talk() devolver el nombre de archivo que quiere reproducir y dejar que otra parte del programa se ocupe de él?


Depende principalmente de cuál sea tu aplicación. Pasar punteros de función a los animales no es una buena idea a menos que desee que los perros y gatos usen diferentes controladores de audio.

El enfoque con el método estático playMP3 está bien. Usar una referencia global para su sistema de audio es perfectamente correcto.


Esta es una pregunta realmente interesante ya que toca elementos de diseño y abstracción. Por ejemplo, ¿cómo unir un objeto Dog para mantener el control sobre cómo se crea? ¿Qué tipo de objeto de audio debería admitir y debería "ladrar" en MP3 o WAV, etc.?

Vale la pena leer un poco acerca de la Inversión de control y la inyección de dependencia, ya que muchos de los temas en los que está pensando se han analizado bastante. Existen bastantes implicaciones, como flexibilidad, facilidad de mantenimiento, pruebas, etc.


La interfaz de devolución de llamada ha sido sugerida en algunas de las otras respuestas, pero tiene inconvenientes:

  • Muchas (potencialmente significativas) clases diferentes que dependen de la misma interfaz. Estas diferentes necesidades de clases pueden corromper la claridad de la interfaz, lo que comenzó como PlaySound (sound_name) se convierte en PlaySound (string sound_name, bool reverb, float max_level, vector direction, bool looping, ...) con un montón de otros métodos (StopSound). , RestartSound, etc etc)
  • Los cambios en la interfaz de audio reconstruirán todo lo que se sabe sobre la interfaz de audio (creo que esto sí importa con C ++)
  • La interfaz proporcionada solo funciona para el sistema de audio (bueno, debería ser solo para el sistema de audio). ¿Qué pasa con el sistema de video y el sistema de redes?

Una alternativa que también se ha mencionado es hacer que el sistema de audio se convierta en estático (o la interfaz del sistema de audio en un singleton). Esto mantendrá la construcción del perro simple (la creación de un perro ya no requiere conocimiento del sistema de audio), pero no aborda ninguno de los problemas anteriores.

Mi solución preferida es delegados. El perro define su interfaz de salida genérica (IE Bark (t_barkData const & data); Growl (t_growlData const & data)) y otras clases se suscriben a esta interfaz. Los sistemas delegados pueden volverse bastante complejos, pero cuando se implementan correctamente no son más difíciles de depurar que una interfaz de devolución de llamada, reducen los tiempos de recompilación y mejoran la legibilidad.

Una nota importante es que la interfaz de salida del perro no necesita ser una clase separada que el perro recibe en la construcción. En su lugar, los punteros a las funciones miembro de los perros se pueden almacenar en caché cuando el perro decide que quiere ladrar (o la forma decide dibujar).

Una gran implementación genérica son las señales y ranuras de QT, pero implementar algo tan poderoso será difícil. Si quisiera un ejemplo simple de algo como esto en c ++, consideraría publicar uno, pero si no está interesado, no voy a tomarme el tiempo de mi sábado :)

Algunos inconvenientes para los delegados (desde la parte superior de mi cabeza): 1. Gastos generales de llamadas, para las cosas que suceden miles de veces por segundo (es decir, las operaciones de "sorteo" de IE en un motor de renderizado) deben tenerse en cuenta. La mayoría de las implementaciones son más lentas que las funciones virtuales. Esta sobrecarga es absolutamente insignificante para las operaciones que no ocurren con mucha frecuencia. 2. Generación de código, principalmente la falla del soporte limitado de puntero a función de C ++. Las plantillas son prácticamente un requisito para implementar un sistema de delegado portátil fácil de leer.


Mi solución sería que la clase Perro pase un dispositivo de audio en la función de ladrido.

El perro no debe almacenar un puntero al dispositivo de audio todo el tiempo, esa no es una de sus responsabilidades. Si recorres esa ruta, terminas con el constructor tomando dos docenas de objetos, esencialmente apuntando al resto de la aplicación (también necesita un puntero al renderizador, por lo que puede dibujarse. Necesita un puntero al suelo, y al administrador de insumos diciéndole a dónde ir, y ........... La locura es así.

Nada de eso pertenece al perro. Si necesita comunicarse con otro objeto, pase ese objeto al método específico que lo necesita.

La responsabilidad del perro es ladrar. Un ladrido hace un sonido. Entonces, el método de la corteza necesita una forma de generar un sonido: se debe pasar una referencia a un objeto de audio. El perro en su conjunto no debería preocuparse o saber sobre eso.

class Dog: public Animal { public: virtual void talk(Audio& a); };

Por la misma lógica, las formas no deben dibujarse por sí mismas. El renderizador dibuja objetos, para eso está. La responsabilidad del objeto rectángulo es simplemente ser rectangular. Parte de esta responsabilidad es poder pasar los datos de dibujo necesarios al renderizador cuando desee dibujar el rectángulo, pero el dibujo en sí no es parte de él.


Una forma podría ser que el constructor del Dog tome una referencia a una instancia de una clase de Audio , porque los perros (generalmente) hacen ruido:

class Dog: public Animal { public: Dog(Audio &a): audio(a) {} virtual void talk() { audio.playMP3("bark.mp3"); } private: Audio &audio; };

Podrías usarlo así:

Audio audioDriver; Dog fido(audioDriver); fido.talk();


Una respuesta básica es que un animal se inicializa con un objeto de audio o un objeto más complejo que contiene varios audios. La función de conversación de un animal llama a un método en este objeto de audio para producir el ruido de conversación para el animal.

El objeto Dog inicializa el Animal con una instancia particular de un objeto de audio característico de Dogs, o (en casos más complejos) toma parámetros que le permiten construir el objeto de Audio para pasarlo a Animal.