Alternativas flexibles a muchas clases polimórficas pequeñas (para usar como propiedades, mensajes o eventos) C ++


9

Hay dos clases en mi juego que son realmente útiles, pero que poco a poco se están volviendo dolorosas. Mensaje y propiedad (la propiedad es esencialmente un componente).

Ambos se derivan de una clase base y contienen una identificación estática para que los sistemas solo puedan prestar atención a los que desean. Está funcionando muy bien ... excepto ...

Estoy constantemente creando nuevos tipos de mensajes y tipos de propiedades a medida que extiendo mi juego. Cada vez que necesito escribir 2 archivos (hpp y cpp) y una tonelada de repeticiones para obtener esencialmente un ID de clase y uno o dos tipos de datos estándar o un puntero.

Está empezando a hacer que jugar y probar nuevas ideas sea una tarea real. Deseo cuando quiero crear un nuevo mensaje o tipo de propiedad, quiero poder escribir algo como

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

en lugar de crear un encabezado y un archivo cpp, escribir un constructor, incluida la clase principal, etc.

Son alrededor de 20 a 40 líneas (incluyendo incluir guardias y todo) solo para hacer lo que lógicamente es 1 o 2 líneas en mi mente.

¿Hay algún patrón de programación para evitar esto?

¿Qué pasa con las secuencias de comandos (de las cuales no sé nada) ... ¿hay alguna manera de definir un grupo de clases que sean casi iguales?


Así es exactamente como se ve una clase:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

Todo eso solo para un Vector 2D y una ID que dice esperar un vector 2D. (De acuerdo, algunas propiedades tienen elementos más complicados, pero es la misma idea)


3
Mira esto, podría ayudarte. gameprogrammingpatterns.com/type-object.html

1
Todo esto parece que las plantillas podrían ayudar, pero no con la inicialización estática. Sin duda, se puede hacer mucho más fácil en este caso, pero es difícil saberlo sin más información sobre lo que desea hacer con esos ID y por qué está utilizando la herencia. Usar herencia pública pero no funciones virtuales es definitivamente un olor a código ... ¡Esto no es polimorfismo!
ltjax

¿Has encontrado la biblioteca de terceros que lo implementa?
Boris

Respuestas:


1

C ++ es poderoso, pero es detallado . Si lo que quiere es un montón de pequeñas clases polimórficas que son todas diferentes, entonces sí, tomará mucho código fuente para declarar y definir. No hay nada que hacer al respecto, de verdad.

Ahora, como dijo ltjax, lo que estás haciendo aquí no es exactamente polimorfismo , al menos para el código que has proporcionado. No puedo ver una interfaz común que oculte la implementación específica de las subclases. Excepto tal vez para la ID de clase, pero en realidad esto es redundante con la ID de clase real: su nombre. Esto parece ser solo un grupo de clases que contienen algunos datos, nada realmente complicado.

Pero esto no resuelve su problema: desea crear muchos mensajes y propiedades con la menor cantidad de código. Menos código significa menos errores, por lo que esta es una decisión acertada. Desafortunadamente para ti no hay una solución única. Es difícil saber qué se adaptaría mejor a sus necesidades sin saber exactamente qué piensa hacer con esos mensajes y propiedades . Permítanme exponer sus opciones:

  1. Use plantillas de C ++ . Las plantillas son una excelente manera de dejar que el compilador "escriba el código" por usted. Se está volviendo cada vez más prominente en el mundo de C ++, a medida que el lenguaje evoluciona para admitirlos mejor (por ejemplo, ahora puede usar un número variable de parámetros de plantilla ). Hay toda una disciplina dedicada a la generación automatizada de código a través de plantillas: metaprogramación de plantillas . El problema es: es difícil. No espere que los no programadores puedan agregar nuevas propiedades por sí mismos si planea usar una técnica de este tipo.

  2. Use macros de C simples . Es de la vieja escuela, fácil de abusar en exceso y propenso a errores. También son muy simples de crear. Las macros son copias de pastas glorificadas realizadas por el preprocesador, por lo que en realidad son bastante adecuadas para crear toneladas de cosas que son casi iguales con pequeñas variaciones. Pero no espere que otros programadores lo amen por usarlos, ya que las macros a menudo se usan para ocultar fallas en el diseño general del programa. A veces, sin embargo, son útiles.

  3. Otra opción es utilizar una herramienta externa para generar el código por usted. Ya se ha mencionado en una respuesta anterior, así que no voy a ampliar eso.

  4. Hay otros idiomas que son menos detallados y le permitirán crear clases de manera más fácil. Entonces, si ya tiene enlaces de script (o planea tenerlos), definir esas clases en script podría ser una opción. Sin embargo, acceder a ellos desde C ++ será bastante complicado y perderá la mayor parte del beneficio de la creación simplificada de clases. Entonces, esto probablemente sea viable solo si planeas hacer la mayor parte de la lógica de tu juego en otro idioma.

  5. Finalmente, pero no menos importante, debe considerar usar un diseño basado en datos . Podría definir esas "clases" en archivos de texto simples utilizando un diseño similar al que usted propuso. Tendrá que crear un formato personalizado y un analizador para él, o usar una de las varias opciones ya disponibles (.ini, XML, JSON y otras cosas). Luego, en el lado de C ++, deberá crear un sistema que admita una colección de esos diferentes tipos de objetos. Esto es casi equivalente al enfoque de secuencias de comandos (y probablemente requiere aún más trabajo), excepto que podrá adaptarlo con mayor precisión a sus necesidades. Y si lo hace lo suficientemente simple, los no programadores podrían crear cosas nuevas por sí mismos.


0

¿Qué tal una herramienta de generación de código para ayudarte?

por ejemplo, podría definir su tipo de mensaje y miembros en un pequeño archivo de texto y hacer que la herramienta code gen lo analice y escriba todos los archivos de C ++ repetitivos como un paso previo a la compilación.

Existen soluciones existentes como ANTLR o LEX / YACC, y no sería difícil implementar las suyas dependiendo de la complejidad de sus propiedades y mensajes.


Solo una alternativa al enfoque LEX / YACC, cuando tengo que generar archivos muy similares con solo modificaciones menores, utilizo un script python simple y pequeño y un archivo de plantilla de código C ++ que contiene etiquetas. El script de Python busca estas etiquetas en la plantilla reemplazándolas por los tokens representativos del elemento que se está creando.
victor

0

Le aconsejo que investigue sobre los Buffers de protocolo de Google ( http://code.google.com/p/protobuf/ ). Son una forma muy inteligente de manejar mensajes genéricos. Solo tiene que especificar las propiedades en un patrón estructurado y un generador de código hace todo el trabajo de generar las clases por usted (Java, C ++ o C #). Todas las clases generadas tienen analizadores de texto y binarios, que los hacen buenos tanto para la inicialización como para la serialización de mensajes basados ​​en texto.


Mi central de mensajería es el núcleo de mi programa: ¿sabe si las memorias intermedias de protocolo incurre en una gran sobrecarga?
Bryan

No creo que incurran en una gran sobrecarga, ya que la API es solo una clase Builder para cada mensaje y la clase para el mensaje en sí. Google los usa para el núcleo de su infraestructura. Por ejemplo, todas las entidades de Google App Engine se convierten en buffers de protocolo antes de que persistan. Si tiene dudas, le aconsejo que implemente una prueba de comparación entre su implementación actual y los buffers de protocolo. Si crees que los gastos generales son aceptables, úsalos.
dsilva.vinicius
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.