Registrar dinamicamente métodos construtores em um AbstractFactory em tempo de compilação usando modelos C ++
Ao implementar uma classe MessageFactory para instanciar objetos Message, usei algo como:
class MessageFactory
{
public:
static Message *create(int type)
{
switch(type) {
case PING_MSG:
return new PingMessage();
case PONG_MSG:
return new PongMessage();
....
}
}
Isso funciona bem, mas toda vez que adiciono uma nova mensagem, tenho que adicionar um novo XXX_MSG e modificar a instrução switch.
Após algumas pesquisas, encontrei uma maneira de atualizar dinamicamente o MessageFactory em tempo de compilação, para que eu possa adicionar quantas mensagens desejar sem precisar modificar o próprio MessageFactory. Isso permite um código mais limpo e fácil de manter, pois não preciso modificar três locais diferentes para adicionar / remover classes de mensagens:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
class Message
{
protected:
inline Message() {};
public:
inline virtual ~Message() { }
inline int getMessageType() const { return m_type; }
virtual void say() = 0;
protected:
uint16_t m_type;
};
template<int TYPE, typename IMPL>
class MessageTmpl: public Message
{
enum { _MESSAGE_ID = TYPE };
public:
static Message* Create() { return new IMPL(); }
static const uint16_t MESSAGE_ID; // for registration
protected:
MessageTmpl() { m_type = MESSAGE_ID; } //use parameter to instanciate template
};
typedef Message* (*t_pfFactory)();
class MessageFactory⋅
{ ,
public:
static uint16_t Register(uint16_t msgid, t_pfFactory factoryMethod)
{
printf("Registering constructor for msg id %d\n", msgid);
m_List[msgid] = factoryMethod;
return msgid;
}
static Message *Create(uint16_t msgid)
{
return m_List[msgid]();
}
static t_pfFactory m_List[65536];
};
template <int TYPE, typename IMPL>
const uint16_t MessageTmpl<TYPE, IMPL >::MESSAGE_ID = MessageFactory::Register(
MessageTmpl<TYPE, IMPL >::_MESSAGE_ID, &MessageTmpl<TYPE, IMPL >::Create);
class PingMessage: public MessageTmpl < 10, PingMessage >
{⋅
public:
PingMessage() {}
virtual void say() { printf("Ping\n"); }
};
class PongMessage: public MessageTmpl < 11, PongMessage >
{⋅
public:
PongMessage() {}
virtual void say() { printf("Pong\n"); }
};
t_pfFactory MessageFactory::m_List[65536];
int main(int argc, char **argv)
{
Message *msg1;
Message *msg2;
msg1 = MessageFactory::Create(10);
msg1->say();
msg2 = MessageFactory::Create(11);
msg2->say();
delete msg1;
delete msg2;
return 0;
}
O modelo aqui faz a mágica registrando-se na classe MessageFactory, todas as novas classes de mensagem (por exemplo, PingMessage e PongMessage) que subclasses de MessageTmpl.
Isso funciona muito bem e simplifica a manutenção do código, mas ainda tenho algumas perguntas sobre essa técnica:
Essa é uma técnica / padrão conhecido? qual é o nome? Quero pesquisar mais informações sobre isso.
Eu quero fazer a matriz para armazenar novos construtoresMessageFactory :: m_List [65536] um std :: map, mas isso faz com que o programa segfault antes mesmo de chegar a main (). Criar uma matriz de 65536 elementos é um exagero, mas não encontrei uma maneira de fazer deste um contêiner dinâmico.
Para todas as classes de mensagem que são subclasses de MessageTmpl, tenho que implementar o construtor. Caso contrário, ele não será registrado no MessageFactory.
Por exemplo, comentando o construtor do PongMessage:
class PongMessage: public MessageTmpl < 11, PongMessage >
{
public:
//PongMessage() {} /* HERE */
virtual void say() { printf("Pong\n"); }
};
resultaria no fato de a classe PongMessage não ser registrada pelo MessageFactory e o programa seria segfault noMessageFactory :: Create (11) linha. A questão é
por que a turma não se registra? Ter que adicionar a implementação vazia das mais de 100 mensagens que eu preciso parece ineficiente e desnecessário.