Isso geralmente é feito usando mensagens. Você pode encontrar muitos detalhes em outras perguntas neste site, como aqui ou ali .
Para responder seu exemplo específico, um caminho a seguir é definir uma Messageclasse pequena que seus objetos possam processar, por exemplo:
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);
}
}
Dessa forma, você não está "poluindo" sua Objinterface de classe com métodos relacionados a componentes. Alguns componentes podem optar por processar a mensagem, outros podem ignorá-la.
Você pode começar chamando esse método diretamente de outro objeto:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
Nesse caso, obj2o usuário Physicsselecionará a mensagem e fará o processamento necessário. Quando terminar, ele irá:
- Envie uma mensagem "SetPosition" para si mesmo, informando que o
Positioncomponente será escolhido;
- Ou acesse diretamente o
Positioncomponente para modificações (bastante errado para um design baseado em componente puro, pois você não pode assumir que todo objeto tem um Positioncomponente, mas o Positioncomponente pode ser um requisito Physics).
Geralmente, é uma boa idéia adiar o processamento real da mensagem para a próxima atualização do componente. Processá-lo imediatamente pode significar enviar mensagens para outros componentes de outros objetos; portanto, enviar apenas uma mensagem pode significar rapidamente uma pilha de espaguete inextricável.
Você provavelmente terá que optar por um sistema mais avançado posteriormente: filas de mensagens assíncronas, envio de mensagens para um grupo de objetos, registro / cancelamento de registro por componente de mensagens etc.
A Messageclasse pode ser um contêiner genérico para uma sequência simples, como mostrado acima, mas o processamento de sequências em tempo de execução não é realmente eficiente. Você pode optar por um contêiner de valores genéricos: cadeias, números inteiros, flutuantes ... Com um nome ou, melhor ainda, um ID, para distinguir diferentes tipos de mensagens. Ou você também pode derivar uma classe base para atender às necessidades específicas. No seu caso, você pode imaginar um EmitForceMessageque deriva Messagee adiciona o vetor de força desejado - mas cuidado com o custo de tempo de execução do RTTI, se você fizer isso.