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 Message
classe 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 Obj
interface 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, obj2
o usuário Physics
selecionará a mensagem e fará o processamento necessário. Quando terminar, ele irá:
- Envie uma mensagem "SetPosition" para si mesmo, informando que o
Position
componente será escolhido;
- Ou acesse diretamente o
Position
componente para modificações (bastante errado para um design baseado em componente puro, pois você não pode assumir que todo objeto tem um Position
componente, mas o Position
componente 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 Message
classe 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 EmitForceMessage
que deriva Message
e adiciona o vetor de força desejado - mas cuidado com o custo de tempo de execução do RTTI, se você fizer isso.