MessageFactoryクラスを実装してMessageオブジェクトをインスタンス化するとき、私は次のようなものを使用しました。
class MessageFactory
{
public:
static Message *create(int type)
{
switch(type) {
case PING_MSG:
return new PingMessage();
case PONG_MSG:
return new PongMessage();
....
}
}
これは問題なく機能しますが、新しいメッセージを追加するたびに、新しいXXX_MSGを追加し、switchステートメントを変更する必要があります。
調査の結果、コンパイル時にMessageFactoryを動的に更新して、MessageFactory自体を変更せずに必要な数のメッセージを追加できるようにする方法を見つけました。これにより、メッセージクラスを追加/削除するために3つの異なる場所を変更する必要がないため、コードをよりクリーンで簡単に保守できます。
#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;
}
ここでのテンプレートは、MessageFactoryクラス、MessageTmplからサブクラス化されたすべての新しいMessageクラス(PingMessageやPongMessageなど)に登録することで魔法をかけます。
これはうまく機能し、コードのメンテナンスを簡素化しますが、この手法についてはまだいくつか質問があります。
これは既知の手法/パターンですか?名前は何ですか?それについてもっと情報を検索したいです。
新しいコンストラクターを格納するための配列MessageFactory::m_List [65536] std :: mapを作成したいのですが、そうすると、main()に到達する前でもプログラムがセグメンテーション違反になります。65536要素の配列を作成するのはやり過ぎですが、これを動的コンテナーにする方法は見つかりませんでした。
MessageTmplのサブクラスであるすべてのメッセージクラスに対して、コンストラクターを実装する必要があります。そうでない場合は、MessageFactoryに登録されません。
たとえば、PongMessageのコンストラクターにコメントする:
class PongMessage: public MessageTmpl < 11, PongMessage > { public: //PongMessage() {} /* HERE */ virtual void say() { printf("Pong\n"); } };
その結果、PongMessageクラスはMessageFactoryによって登録されず、プログラムはMessageFactory :: Create(11)行でセグメンテーション違反を起こします。問題は
、なぜクラスが登録されないのかということです。必要な100以上のメッセージの空の実装を追加する必要があると、非効率的で不必要に感じます。