好的,上下文是一些序列化/反序列化代码,它将字节流解析为更容易使用的“对象”表示(反之亦然)。
这是一个带有基本消息类的简化示例,然后根据“类型”标题,存在更多数据/函数,我们必须选择正确的子类来实例化:
class BaseMessage {
public:
enum Type {
MyMessageA = 0x5a,
MyMessageB = 0xa5,
};
BaseMessage(Type type) : mType(type) { }
virtual ~BaseMessage() { }
Type type() const { return mType; }
protected:
Type mType;
virtual void parse(void *data, size_t len);
};
class MyMessageA {
public:
MyMessageA() : BaseMessage(MyMessageA) { }
/* message A specific stuf ... */
protected:
virtual void parse(void *data, size_t len);
};
class MyMessageB {
public:
MyMessageB() : BaseMessage(MyMessageB) { }
/* message B specific stuf ... */
protected:
virtual void parse(void *data, size_t len);
};
在一个实际的例子中,会有数百种不同的消息类型,可能还有几种级别或层次结构,因为某些消息彼此共享字段/功能。
现在,要解析一个字节字符串,我正在做类似的事情:
BaseMessage *msg = NULL;
Type type = (Type)data[0];
switch (type) {
case MyMessageA:
msg = new MyMessageA();
break;
case MyMessageB:
msg = new MyMessageB();
break;
default:
/* protocol error */
}
if (msg)
msg->parse(data, len);
但是我没有发现这个巨大的开关非常优雅,并且我有关于哪个消息具有哪个'类型值'两次的信息(一次在构造函数中,一个在此开关中) 它也很长......
我正在寻找一种更好的方式,那会更好......如何改进呢?
答案 0 :(得分:10)
接近它的一种方法是使用地图并为每种消息类型注册某种工厂功能。这意味着您可以摆脱开关案例,并可以动态添加和删除消息。
代码看起来像:
// Create the map (most likely a member in a different class)
std::map<BaseMessage::Type, MessageCreator*> messageMap;
...
// Register some message types
// Note that you can add and remove messages at runtime here
messageMap[BaseMessage::MyMessageA] = new MessageCreatorT<BaseMessageA>();
messageMap[BaseMessage::MyMessageB] = new MessageCreatorT<BaseMessageB>();
...
// Handle a message
std::map<Type, MessageCreator*>::const_iterator it = messageMap.find(msgType);
if(it == messageMap.end()) {
// Unknown message type
beepHang();
}
// Now create the message
BaseMessage* msg = it->second.createMessage(data);
MessageCreator类看起来像这样:
class MessageCreator {
public:
virtual BaseMessage* createMessage(void* data, size_t len) const = 0;
};
template<class T> class MessageCreatorT : public MessageCreator {
public:
BaseMessage* createMessage(void* data, size_t len) const {
T* newMessage = new T();
newMessage.parse(data, len);
return newMessage;
}
};
答案 1 :(得分:5)
这实际上是一个非常基本的问题(你可以想象,你绝对不是唯一一个在C ++中反序列化的人)。
您正在寻找的是虚拟建筑。
C ++没有定义虚拟构造,但很容易使用Prototype
设计模式或使用Factory
方法对其进行近似。
我个人更喜欢Factory
方法,因为Prototype
一个意味着有某种默认实例被复制并且已定义...问题是并非所有类都有有意义的默认,以及有意义的Default Constructor
。
Factory
方法很简单。
让我们看一些代码:
// Framework
class Message
{
public:
virtual ~Message();
};
class Parser
{
public:
virtual ~Parser();
virtual std::auto_ptr<Message> parse(std::istream& serialized) const;
};
// Factory of Messages
class MessageFactory
{
public:
void register(std::string const& tag, Parser const& parser);
std::auto_ptr<Message> build(std::string const& tag, std::istream& serialized) const;
private:
std::map<std::string,Parser const*> m_parsers;
};
使用这个框架(简单地说),一些派生类:
class MessageA: public Message
{
public:
MessageA(int a, int b);
};
class ParserA: public Parser
{
public:
typedef std::auto_ptr<MessageA> result_type;
virtual result_type parse(std::istream& serialized) const
{
int a = 0, b = 0;
char space = 0;
std::istream >> a >> space >> b;
// Need some error control there
return result_type(new MessageA(a,b));
}
};
最后,使用:
int main(int argc, char* argv[])
{
// Register the parsers
MessageFactory factory;
factory.register("A", ParserA());
// take a file
// which contains 'A 1 2\n'
std::ifstream file = std::ifstream("file.txt");
std::string tag;
file >> tag;
std::auto_ptr<Message> message = factory.parse(tag, file);
// message now points to an instance of MessageA built by MessageA(1,2)
}
它有效,我知道我使用它(或变体)。
有些事情需要考虑:
MessageFactory
作为单例,然后允许在库加载时调用它,因此您可以通过实例化静态变量来注册解析器。如果您不希望main
必须注册每个解析器类型,这非常方便:locality&gt;少依赖。像:
class Message
{
public:
virtual ~Message();
virtual const std::string& tag() const = 0;
virtual void serialize(std::ostream& out) const;
};
像:
class MessageA: public Message
{
public:
static const std::string& Tag();
virtual const std::string& tag() const;
virtual void serialize(std::ostream& out) const;
MessageA(std::istream& in);
};
template <class M>
class ParserTemplate: public Parser // not really a parser now...
{
public:
virtual std::auto_ptr<M> parse(std::istream& in) const
{
return std::auto_ptr<M>(new M(in));
}
};
模板的优点在于它永远不会让我感到惊讶
class MessageFactory
{
public:
template <class M>
void register()
{
m_parsers[M::Tag()] = new ParserTemplate<M>();
}
};
//skipping to registration
factory.register<MessageA>();
现在不是很漂亮:)?