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 Messageclase 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 Objinterfaz 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 Physicselegirá el mensaje y realizará el procesamiento que sea necesario. Cuando termine, ya sea:
- Envíe un mensaje "SetPosition" a sí mismo, que el
Positioncomponente elegirá;
- O acceda directamente al
Positioncomponente para modificaciones (bastante incorrecto para un diseño basado en componentes puros, ya que no puede suponer que cada objeto tiene un Positioncomponente, pero el Positioncomponente 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 Messageclase 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 EmitForceMessagederivado del Messagevector de fuerza deseado y agregarlo, pero tenga cuidado con el costo de tiempo de ejecución de RTTI si lo hace.