Eso generalmente se hace mediante mensajes. Puede encontrar muchos detalles en otras preguntas en este sitio, como aquí o allá .
Para responder a su ejemplo específico, un camino a seguir es definir una pequeña Message
clase que sus objetos puedan procesar, por ejemplo:
struct Message
{
Message(const Objt& sender, const std::string& msg)
: m_sender(&sender)
, m_msg(msg) {}
const Obj* m_sender;
std::string m_msg;
};
void Obj::Process(const Message& msg)
{
for (int i=0; i<m_components.size(); ++i)
{
// let components do some stuff with msg
m_components[i].Process(msg);
}
}
De esta manera no estás "contaminando" tu Obj
interfaz de clase con métodos relacionados con componentes. Algunos componentes pueden elegir procesar el mensaje, algunos simplemente pueden ignorarlo.
Puede comenzar llamando a este método directamente desde otro objeto:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
En este caso, obj2
's Physics
elegirá el mensaje y realizará el procesamiento que sea necesario. Cuando termine, ya sea:
- Envíe un mensaje "SetPosition" a sí mismo, que el
Position
componente elegirá;
- O acceda directamente al
Position
componente para modificaciones (bastante incorrecto para un diseño basado en componentes puros, ya que no puede suponer que cada objeto tiene un Position
componente, pero el Position
componente podría ser un requisito Physics
).
En general, es una buena idea retrasar el procesamiento real del mensaje a la actualización del siguiente componente. Procesarlo de inmediato podría significar enviar mensajes a otros componentes de otros objetos, por lo que enviar solo un mensaje podría significar rápidamente una pila de espagueti inextricable.
Probablemente tendrá que ir a un sistema más avanzado más adelante: colas de mensajes asíncronos, envío de mensajes a un grupo de objetos, registro / anulación de registro por componente, etc.
La Message
clase puede ser un contenedor genérico para una cadena simple como se muestra arriba, pero el procesamiento de cadenas en tiempo de ejecución no es realmente eficiente. Puede buscar un contenedor de valores genéricos: cadenas, enteros, flotantes ... Con un nombre o mejor aún, un ID, para distinguir diferentes tipos de mensajes. O también puede derivar una clase base para satisfacer necesidades específicas. En su caso, podría imaginar un EmitForceMessage
derivado del Message
vector de fuerza deseado y agregarlo, pero tenga cuidado con el costo de tiempo de ejecución de RTTI si lo hace.