Packet Factory design for networking application

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
8
down vote

favorite












I'm working on a network application that implements a custom protocol. I want to easily extend the application support over the protocol as it changes. Also, as the application extends, I might need to implement other protocols. So I need a design that allows flexible packet creation, based on protocol and variety of packets that it supports.



I found that the best way to achive this is by using the Factory Pattern. (Or, for more than one protocol, an Abstract Factory).



Here is my implementation:



IPacket.h:



#pragma once
#include "PacketData.h"

class IPacket

public:
// Deleted fuctions
IPacket(const IPacket&) = delete;

IPacket& operator=(const IPacket&) = delete;
public:
IPacket();
virtual ~IPacket();

// Encode data into a byte buffer
virtual void Serialize(PacketData &) = 0;

// Decode data from a byte buffer
virtual void Deserialize(const PacketData &) = 0;
;


This provides a packet interface that will be used for further implementations of protocols.



MBXGeneric.h:



#pragma once
#include "IPacket.h"
#include "MBXHeader.h"
#include "MBXPacketFactory.h"

// Base class for custom protocol
class MBXGenericPacket : public IPacket

public:
MBXGenericPacket(MBXPacketFactory::MBXPacket _type) : H_MBX(static_cast<uint16_t>(m_Type))


// Serialize MBXHeader
virtual void Serialize(PacketData& data) override

data.Data << H_MBX.h_time << H_MBX.P_Type;


// Extract MBXHeader
virtual void Deserialize(const PacketData& data) override

data.Data >> H_MBX >> P_Type;


MBXPacketFactory::MBXPacket GetType() const return static_cast<MBXPacketFactory::MBXPacket>( H_MBX.P_Type );

static std::unique_ptr<IPacket> CreateGenericPacket(const MBXPacketFactory::MBXPacket Type)

return std::make_unique<MBXGenericPacket>(Type);

protected:
MBXHeader H_MBX;
;


This is the base class of an example protocol.

The packets have the following structure:




|MBX Header (Metadata + PacketType) | BODY |




The header will be initialized with each packet derrived from MBXGenericPacket. It can then be decoded or encoded in a byte buffer.



MBXPacketFactory.h:



#pragma once
#include <map>
#include <memory>
#include <mutex>
#include "MBXGeneric.h"
#include "Packets.h"

using CreateMBXPacketCb = std::unique_ptr<MBXGenericPacket>(*)();

class MBXPacketFactory

public:
enum class MBXPacket : uint16_t

MBXGeneric,
Msg_MessageBox,
ErrorResponseMsg,

MAX_PACKET
;

private:
using PacketsMap = std::map<MBXPacketFactory::MBXPacket, CreateMBXPacketCb>;

PacketsMap map;
private:

MBXPacketFactory()

map[MBXPacket::Msg_MessageBox] = ChatMessage::CreatePacket;
map[MBXPacket::ErrorResponseMsg] = ErrorResponse::CreatePacket;

public:
MBXPacketFactory(const MBXPacketFactory&) = delete;
MBXPacketFactory & operator=(const MBXPacketFactory&) = delete;
public:

bool UnRegisterPacket(MBXPacketFactory::MBXPacket Type)

return map.erase(Type);


bool RegisterPacket(const MBXPacketFactory::MBXPacket& type, CreateMBXPacketCb callback)

return map.insert(std::make_pair(type, callback)).second;


std::unique_ptr<MBXGenericPacket> CreatePacket(MBXPacketFactory::MBXPacket Type)

auto it = map.find(Type);

if (it == map.end())
throw std::runtime_error("Unknown packet type!");

return it->second();


static MBXPacketFactory& Get()

static MBXPacketFactory instance;
return instance;

;


Here's an example of packets:



// Packets.h
#include "MBXGeneric.h"
#include "MBXPacketFactory.h"

class ChatMessage : public MBXGenericPacket

public:
ChatMessage() : MBXGenericPacket(MBXPacketFactory::MBXPacket::Msg_MessageBox)


void Serialize(PacketData& data) override

data << H_MBX;

data << sz_Title << s_Title;
data << sz_Message << s_Message;


void Deserialize(const PacketData& data) override

// Extract the header
data >> H_MBX;

data >> sz_Title;
data.read(s_Title, sz_Title);

data >> sz_Message;
data.read(s_Message, sz_Message);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ChatMessage>();

public:

// Getters and setters

std::string GetTitle()

return s_Title;


std::string GetMessage()

return s_Message;


void SetTitle(const std::string& title)

s_Title = title;


void SetMessage(const std::string& message)

s_Message = message;



private:
int32_t sz_Title, sz_Message;

std::string s_Title, s_Message;
;



class ErrorResponse : public MBXGenericPacket

public:
ErrorResponse() : MBXGenericPacket(MBXPacketFactory::MBXPacket::ErrorResponseMsg)


void Serialize(PacketData& data) override

data << H_MBX;
data << sz_Error << s_ErrorMessage;


void Deserialize(const PacketData& data) override

data >> H_MBX;

data >> sz_Error;
data.read(s_ErrorMessage, sz_Error);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ErrorResponse>();

public:

std::string GetErrorMessage()

return s_ErrorMessage;


void SetErrorMessage(const std::string& msg_error)

s_ErrorMessage = msg_error;


private:
int32_t sz_Error;
std::string s_ErrorMessage;
;


Example usage of factory:



When receiving a packet:



...

PacketData NewPacket = connection.RecvPacket();

uint16_t PacketID = GetUint16(NewPacket.Data);

std::unique_ptr<MBXGenericPacket> Packet = MBXPacketFactory::Get().CreatePacket(static_cast<MBXPacketFactory::MBXPacket>(PacketID));

// Up-cast the packet pointer to the right packet type

// Let's asume we've received an error response

std::unique_ptr<ErrorResponse> ErrorPacket(reinterpret_cast<ErrorResponse*>( Packet.release() ));

ErrorPacket->Deserialize(NewPacket);

std::cout << ErrorPacket->GetErrorMessage() << 'n';

...


When sending a packet:



...

std::string Title = GetUserInput();
std::string Message = GetUserInput();

std::unique_ptr<MBXGenericPacket> Interface = MBXPacketFactory::Get().CreatePacket(MBXPacketFactory::MBXPacket::Msg_MessageBox);

std::unique_ptr<ChatMessage> MessagePk(reinterpret_cast<ChatMessage*>( Interface.release() ));

MessagePk->SetTitle(Title);
MessagePk->SetMessage(Message);

PacketData MsgPacketData;
MessagePk->Serialize(MsgPacketData);

PacketQueue.push(MsgPacketData);

...


Improvements:



What I would like to improve in the current design:



  • The down-casting from the base class to the derived class in
    order to access member functions.

  • With each new packet derived from MBXGenericPacket, I'm forced to
    encode and decode the MBX_Header in the
    serialization/deserialization, thus causing code repetition.

  • To get the required packet class from the factory, I need to first
    extract the identifier from the header and then pass it to the
    CreatePacket() method. I think it will be nice if you could just
    pass the PacketData to the factory, rather then decoding
    information by yourself.

  • The factory's purpose is to abstract the creation process of classes.
    In my opinion, the packet identifiers should be encapsulated by the
    factory, but that causes a lot of noise in the code, as you have
    to type: MBXPacketFactory::MBXPacket:: ...

Let me know your thoughts about the code. I'm here to learn, so any opinion is welcomed !







share|improve this question





















  • I know this is not quite the purpose of code review but did you look into some of the available serialization libraries, protobuffers for example.
    – Harald Scheirich
    Apr 4 at 20:47










  • Nice code, but realizing CreatePacket() and CreateGenericPacket() as a helper functions rather than member functions should be better looking and more correct from factory design pattern point of view? Being static member functions, the outcome will be same though.
    – seccpur
    Apr 6 at 14:19










  • @seccpur What do you mean by "the same outcome" ? Isn't this the purpose of `Factory Pattern, to return a pointer to the base class, leaving subclasses to decide which classes to instantiate ?! (correct me if I'm wrong)
    – Cosain Melic
    Apr 6 at 14:42











  • @CosainMelic: I will love to call CreatePacket(...) or MBXPacketFactory::CreatePacket() instead of MBXPacketFactory::Get().CreatePacket(...) in the implementation. I would like a namespace here to avoid conflict. BTW i have +1.
    – seccpur
    Apr 6 at 14:58











  • @seccpur Yes, it feels more natural, but to do that I'm afraid I will complicate things more. The MBXPacketFactory::CreatePacket(...) will have to be a static function, the constructor of the factory will have to be made public, and I will have to make sure only one instance of the class exists.
    – Cosain Melic
    Apr 6 at 15:24
















up vote
8
down vote

favorite












I'm working on a network application that implements a custom protocol. I want to easily extend the application support over the protocol as it changes. Also, as the application extends, I might need to implement other protocols. So I need a design that allows flexible packet creation, based on protocol and variety of packets that it supports.



I found that the best way to achive this is by using the Factory Pattern. (Or, for more than one protocol, an Abstract Factory).



Here is my implementation:



IPacket.h:



#pragma once
#include "PacketData.h"

class IPacket

public:
// Deleted fuctions
IPacket(const IPacket&) = delete;

IPacket& operator=(const IPacket&) = delete;
public:
IPacket();
virtual ~IPacket();

// Encode data into a byte buffer
virtual void Serialize(PacketData &) = 0;

// Decode data from a byte buffer
virtual void Deserialize(const PacketData &) = 0;
;


This provides a packet interface that will be used for further implementations of protocols.



MBXGeneric.h:



#pragma once
#include "IPacket.h"
#include "MBXHeader.h"
#include "MBXPacketFactory.h"

// Base class for custom protocol
class MBXGenericPacket : public IPacket

public:
MBXGenericPacket(MBXPacketFactory::MBXPacket _type) : H_MBX(static_cast<uint16_t>(m_Type))


// Serialize MBXHeader
virtual void Serialize(PacketData& data) override

data.Data << H_MBX.h_time << H_MBX.P_Type;


// Extract MBXHeader
virtual void Deserialize(const PacketData& data) override

data.Data >> H_MBX >> P_Type;


MBXPacketFactory::MBXPacket GetType() const return static_cast<MBXPacketFactory::MBXPacket>( H_MBX.P_Type );

static std::unique_ptr<IPacket> CreateGenericPacket(const MBXPacketFactory::MBXPacket Type)

return std::make_unique<MBXGenericPacket>(Type);

protected:
MBXHeader H_MBX;
;


This is the base class of an example protocol.

The packets have the following structure:




|MBX Header (Metadata + PacketType) | BODY |




The header will be initialized with each packet derrived from MBXGenericPacket. It can then be decoded or encoded in a byte buffer.



MBXPacketFactory.h:



#pragma once
#include <map>
#include <memory>
#include <mutex>
#include "MBXGeneric.h"
#include "Packets.h"

using CreateMBXPacketCb = std::unique_ptr<MBXGenericPacket>(*)();

class MBXPacketFactory

public:
enum class MBXPacket : uint16_t

MBXGeneric,
Msg_MessageBox,
ErrorResponseMsg,

MAX_PACKET
;

private:
using PacketsMap = std::map<MBXPacketFactory::MBXPacket, CreateMBXPacketCb>;

PacketsMap map;
private:

MBXPacketFactory()

map[MBXPacket::Msg_MessageBox] = ChatMessage::CreatePacket;
map[MBXPacket::ErrorResponseMsg] = ErrorResponse::CreatePacket;

public:
MBXPacketFactory(const MBXPacketFactory&) = delete;
MBXPacketFactory & operator=(const MBXPacketFactory&) = delete;
public:

bool UnRegisterPacket(MBXPacketFactory::MBXPacket Type)

return map.erase(Type);


bool RegisterPacket(const MBXPacketFactory::MBXPacket& type, CreateMBXPacketCb callback)

return map.insert(std::make_pair(type, callback)).second;


std::unique_ptr<MBXGenericPacket> CreatePacket(MBXPacketFactory::MBXPacket Type)

auto it = map.find(Type);

if (it == map.end())
throw std::runtime_error("Unknown packet type!");

return it->second();


static MBXPacketFactory& Get()

static MBXPacketFactory instance;
return instance;

;


Here's an example of packets:



// Packets.h
#include "MBXGeneric.h"
#include "MBXPacketFactory.h"

class ChatMessage : public MBXGenericPacket

public:
ChatMessage() : MBXGenericPacket(MBXPacketFactory::MBXPacket::Msg_MessageBox)


void Serialize(PacketData& data) override

data << H_MBX;

data << sz_Title << s_Title;
data << sz_Message << s_Message;


void Deserialize(const PacketData& data) override

// Extract the header
data >> H_MBX;

data >> sz_Title;
data.read(s_Title, sz_Title);

data >> sz_Message;
data.read(s_Message, sz_Message);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ChatMessage>();

public:

// Getters and setters

std::string GetTitle()

return s_Title;


std::string GetMessage()

return s_Message;


void SetTitle(const std::string& title)

s_Title = title;


void SetMessage(const std::string& message)

s_Message = message;



private:
int32_t sz_Title, sz_Message;

std::string s_Title, s_Message;
;



class ErrorResponse : public MBXGenericPacket

public:
ErrorResponse() : MBXGenericPacket(MBXPacketFactory::MBXPacket::ErrorResponseMsg)


void Serialize(PacketData& data) override

data << H_MBX;
data << sz_Error << s_ErrorMessage;


void Deserialize(const PacketData& data) override

data >> H_MBX;

data >> sz_Error;
data.read(s_ErrorMessage, sz_Error);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ErrorResponse>();

public:

std::string GetErrorMessage()

return s_ErrorMessage;


void SetErrorMessage(const std::string& msg_error)

s_ErrorMessage = msg_error;


private:
int32_t sz_Error;
std::string s_ErrorMessage;
;


Example usage of factory:



When receiving a packet:



...

PacketData NewPacket = connection.RecvPacket();

uint16_t PacketID = GetUint16(NewPacket.Data);

std::unique_ptr<MBXGenericPacket> Packet = MBXPacketFactory::Get().CreatePacket(static_cast<MBXPacketFactory::MBXPacket>(PacketID));

// Up-cast the packet pointer to the right packet type

// Let's asume we've received an error response

std::unique_ptr<ErrorResponse> ErrorPacket(reinterpret_cast<ErrorResponse*>( Packet.release() ));

ErrorPacket->Deserialize(NewPacket);

std::cout << ErrorPacket->GetErrorMessage() << 'n';

...


When sending a packet:



...

std::string Title = GetUserInput();
std::string Message = GetUserInput();

std::unique_ptr<MBXGenericPacket> Interface = MBXPacketFactory::Get().CreatePacket(MBXPacketFactory::MBXPacket::Msg_MessageBox);

std::unique_ptr<ChatMessage> MessagePk(reinterpret_cast<ChatMessage*>( Interface.release() ));

MessagePk->SetTitle(Title);
MessagePk->SetMessage(Message);

PacketData MsgPacketData;
MessagePk->Serialize(MsgPacketData);

PacketQueue.push(MsgPacketData);

...


Improvements:



What I would like to improve in the current design:



  • The down-casting from the base class to the derived class in
    order to access member functions.

  • With each new packet derived from MBXGenericPacket, I'm forced to
    encode and decode the MBX_Header in the
    serialization/deserialization, thus causing code repetition.

  • To get the required packet class from the factory, I need to first
    extract the identifier from the header and then pass it to the
    CreatePacket() method. I think it will be nice if you could just
    pass the PacketData to the factory, rather then decoding
    information by yourself.

  • The factory's purpose is to abstract the creation process of classes.
    In my opinion, the packet identifiers should be encapsulated by the
    factory, but that causes a lot of noise in the code, as you have
    to type: MBXPacketFactory::MBXPacket:: ...

Let me know your thoughts about the code. I'm here to learn, so any opinion is welcomed !







share|improve this question





















  • I know this is not quite the purpose of code review but did you look into some of the available serialization libraries, protobuffers for example.
    – Harald Scheirich
    Apr 4 at 20:47










  • Nice code, but realizing CreatePacket() and CreateGenericPacket() as a helper functions rather than member functions should be better looking and more correct from factory design pattern point of view? Being static member functions, the outcome will be same though.
    – seccpur
    Apr 6 at 14:19










  • @seccpur What do you mean by "the same outcome" ? Isn't this the purpose of `Factory Pattern, to return a pointer to the base class, leaving subclasses to decide which classes to instantiate ?! (correct me if I'm wrong)
    – Cosain Melic
    Apr 6 at 14:42











  • @CosainMelic: I will love to call CreatePacket(...) or MBXPacketFactory::CreatePacket() instead of MBXPacketFactory::Get().CreatePacket(...) in the implementation. I would like a namespace here to avoid conflict. BTW i have +1.
    – seccpur
    Apr 6 at 14:58











  • @seccpur Yes, it feels more natural, but to do that I'm afraid I will complicate things more. The MBXPacketFactory::CreatePacket(...) will have to be a static function, the constructor of the factory will have to be made public, and I will have to make sure only one instance of the class exists.
    – Cosain Melic
    Apr 6 at 15:24












up vote
8
down vote

favorite









up vote
8
down vote

favorite











I'm working on a network application that implements a custom protocol. I want to easily extend the application support over the protocol as it changes. Also, as the application extends, I might need to implement other protocols. So I need a design that allows flexible packet creation, based on protocol and variety of packets that it supports.



I found that the best way to achive this is by using the Factory Pattern. (Or, for more than one protocol, an Abstract Factory).



Here is my implementation:



IPacket.h:



#pragma once
#include "PacketData.h"

class IPacket

public:
// Deleted fuctions
IPacket(const IPacket&) = delete;

IPacket& operator=(const IPacket&) = delete;
public:
IPacket();
virtual ~IPacket();

// Encode data into a byte buffer
virtual void Serialize(PacketData &) = 0;

// Decode data from a byte buffer
virtual void Deserialize(const PacketData &) = 0;
;


This provides a packet interface that will be used for further implementations of protocols.



MBXGeneric.h:



#pragma once
#include "IPacket.h"
#include "MBXHeader.h"
#include "MBXPacketFactory.h"

// Base class for custom protocol
class MBXGenericPacket : public IPacket

public:
MBXGenericPacket(MBXPacketFactory::MBXPacket _type) : H_MBX(static_cast<uint16_t>(m_Type))


// Serialize MBXHeader
virtual void Serialize(PacketData& data) override

data.Data << H_MBX.h_time << H_MBX.P_Type;


// Extract MBXHeader
virtual void Deserialize(const PacketData& data) override

data.Data >> H_MBX >> P_Type;


MBXPacketFactory::MBXPacket GetType() const return static_cast<MBXPacketFactory::MBXPacket>( H_MBX.P_Type );

static std::unique_ptr<IPacket> CreateGenericPacket(const MBXPacketFactory::MBXPacket Type)

return std::make_unique<MBXGenericPacket>(Type);

protected:
MBXHeader H_MBX;
;


This is the base class of an example protocol.

The packets have the following structure:




|MBX Header (Metadata + PacketType) | BODY |




The header will be initialized with each packet derrived from MBXGenericPacket. It can then be decoded or encoded in a byte buffer.



MBXPacketFactory.h:



#pragma once
#include <map>
#include <memory>
#include <mutex>
#include "MBXGeneric.h"
#include "Packets.h"

using CreateMBXPacketCb = std::unique_ptr<MBXGenericPacket>(*)();

class MBXPacketFactory

public:
enum class MBXPacket : uint16_t

MBXGeneric,
Msg_MessageBox,
ErrorResponseMsg,

MAX_PACKET
;

private:
using PacketsMap = std::map<MBXPacketFactory::MBXPacket, CreateMBXPacketCb>;

PacketsMap map;
private:

MBXPacketFactory()

map[MBXPacket::Msg_MessageBox] = ChatMessage::CreatePacket;
map[MBXPacket::ErrorResponseMsg] = ErrorResponse::CreatePacket;

public:
MBXPacketFactory(const MBXPacketFactory&) = delete;
MBXPacketFactory & operator=(const MBXPacketFactory&) = delete;
public:

bool UnRegisterPacket(MBXPacketFactory::MBXPacket Type)

return map.erase(Type);


bool RegisterPacket(const MBXPacketFactory::MBXPacket& type, CreateMBXPacketCb callback)

return map.insert(std::make_pair(type, callback)).second;


std::unique_ptr<MBXGenericPacket> CreatePacket(MBXPacketFactory::MBXPacket Type)

auto it = map.find(Type);

if (it == map.end())
throw std::runtime_error("Unknown packet type!");

return it->second();


static MBXPacketFactory& Get()

static MBXPacketFactory instance;
return instance;

;


Here's an example of packets:



// Packets.h
#include "MBXGeneric.h"
#include "MBXPacketFactory.h"

class ChatMessage : public MBXGenericPacket

public:
ChatMessage() : MBXGenericPacket(MBXPacketFactory::MBXPacket::Msg_MessageBox)


void Serialize(PacketData& data) override

data << H_MBX;

data << sz_Title << s_Title;
data << sz_Message << s_Message;


void Deserialize(const PacketData& data) override

// Extract the header
data >> H_MBX;

data >> sz_Title;
data.read(s_Title, sz_Title);

data >> sz_Message;
data.read(s_Message, sz_Message);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ChatMessage>();

public:

// Getters and setters

std::string GetTitle()

return s_Title;


std::string GetMessage()

return s_Message;


void SetTitle(const std::string& title)

s_Title = title;


void SetMessage(const std::string& message)

s_Message = message;



private:
int32_t sz_Title, sz_Message;

std::string s_Title, s_Message;
;



class ErrorResponse : public MBXGenericPacket

public:
ErrorResponse() : MBXGenericPacket(MBXPacketFactory::MBXPacket::ErrorResponseMsg)


void Serialize(PacketData& data) override

data << H_MBX;
data << sz_Error << s_ErrorMessage;


void Deserialize(const PacketData& data) override

data >> H_MBX;

data >> sz_Error;
data.read(s_ErrorMessage, sz_Error);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ErrorResponse>();

public:

std::string GetErrorMessage()

return s_ErrorMessage;


void SetErrorMessage(const std::string& msg_error)

s_ErrorMessage = msg_error;


private:
int32_t sz_Error;
std::string s_ErrorMessage;
;


Example usage of factory:



When receiving a packet:



...

PacketData NewPacket = connection.RecvPacket();

uint16_t PacketID = GetUint16(NewPacket.Data);

std::unique_ptr<MBXGenericPacket> Packet = MBXPacketFactory::Get().CreatePacket(static_cast<MBXPacketFactory::MBXPacket>(PacketID));

// Up-cast the packet pointer to the right packet type

// Let's asume we've received an error response

std::unique_ptr<ErrorResponse> ErrorPacket(reinterpret_cast<ErrorResponse*>( Packet.release() ));

ErrorPacket->Deserialize(NewPacket);

std::cout << ErrorPacket->GetErrorMessage() << 'n';

...


When sending a packet:



...

std::string Title = GetUserInput();
std::string Message = GetUserInput();

std::unique_ptr<MBXGenericPacket> Interface = MBXPacketFactory::Get().CreatePacket(MBXPacketFactory::MBXPacket::Msg_MessageBox);

std::unique_ptr<ChatMessage> MessagePk(reinterpret_cast<ChatMessage*>( Interface.release() ));

MessagePk->SetTitle(Title);
MessagePk->SetMessage(Message);

PacketData MsgPacketData;
MessagePk->Serialize(MsgPacketData);

PacketQueue.push(MsgPacketData);

...


Improvements:



What I would like to improve in the current design:



  • The down-casting from the base class to the derived class in
    order to access member functions.

  • With each new packet derived from MBXGenericPacket, I'm forced to
    encode and decode the MBX_Header in the
    serialization/deserialization, thus causing code repetition.

  • To get the required packet class from the factory, I need to first
    extract the identifier from the header and then pass it to the
    CreatePacket() method. I think it will be nice if you could just
    pass the PacketData to the factory, rather then decoding
    information by yourself.

  • The factory's purpose is to abstract the creation process of classes.
    In my opinion, the packet identifiers should be encapsulated by the
    factory, but that causes a lot of noise in the code, as you have
    to type: MBXPacketFactory::MBXPacket:: ...

Let me know your thoughts about the code. I'm here to learn, so any opinion is welcomed !







share|improve this question













I'm working on a network application that implements a custom protocol. I want to easily extend the application support over the protocol as it changes. Also, as the application extends, I might need to implement other protocols. So I need a design that allows flexible packet creation, based on protocol and variety of packets that it supports.



I found that the best way to achive this is by using the Factory Pattern. (Or, for more than one protocol, an Abstract Factory).



Here is my implementation:



IPacket.h:



#pragma once
#include "PacketData.h"

class IPacket

public:
// Deleted fuctions
IPacket(const IPacket&) = delete;

IPacket& operator=(const IPacket&) = delete;
public:
IPacket();
virtual ~IPacket();

// Encode data into a byte buffer
virtual void Serialize(PacketData &) = 0;

// Decode data from a byte buffer
virtual void Deserialize(const PacketData &) = 0;
;


This provides a packet interface that will be used for further implementations of protocols.



MBXGeneric.h:



#pragma once
#include "IPacket.h"
#include "MBXHeader.h"
#include "MBXPacketFactory.h"

// Base class for custom protocol
class MBXGenericPacket : public IPacket

public:
MBXGenericPacket(MBXPacketFactory::MBXPacket _type) : H_MBX(static_cast<uint16_t>(m_Type))


// Serialize MBXHeader
virtual void Serialize(PacketData& data) override

data.Data << H_MBX.h_time << H_MBX.P_Type;


// Extract MBXHeader
virtual void Deserialize(const PacketData& data) override

data.Data >> H_MBX >> P_Type;


MBXPacketFactory::MBXPacket GetType() const return static_cast<MBXPacketFactory::MBXPacket>( H_MBX.P_Type );

static std::unique_ptr<IPacket> CreateGenericPacket(const MBXPacketFactory::MBXPacket Type)

return std::make_unique<MBXGenericPacket>(Type);

protected:
MBXHeader H_MBX;
;


This is the base class of an example protocol.

The packets have the following structure:




|MBX Header (Metadata + PacketType) | BODY |




The header will be initialized with each packet derrived from MBXGenericPacket. It can then be decoded or encoded in a byte buffer.



MBXPacketFactory.h:



#pragma once
#include <map>
#include <memory>
#include <mutex>
#include "MBXGeneric.h"
#include "Packets.h"

using CreateMBXPacketCb = std::unique_ptr<MBXGenericPacket>(*)();

class MBXPacketFactory

public:
enum class MBXPacket : uint16_t

MBXGeneric,
Msg_MessageBox,
ErrorResponseMsg,

MAX_PACKET
;

private:
using PacketsMap = std::map<MBXPacketFactory::MBXPacket, CreateMBXPacketCb>;

PacketsMap map;
private:

MBXPacketFactory()

map[MBXPacket::Msg_MessageBox] = ChatMessage::CreatePacket;
map[MBXPacket::ErrorResponseMsg] = ErrorResponse::CreatePacket;

public:
MBXPacketFactory(const MBXPacketFactory&) = delete;
MBXPacketFactory & operator=(const MBXPacketFactory&) = delete;
public:

bool UnRegisterPacket(MBXPacketFactory::MBXPacket Type)

return map.erase(Type);


bool RegisterPacket(const MBXPacketFactory::MBXPacket& type, CreateMBXPacketCb callback)

return map.insert(std::make_pair(type, callback)).second;


std::unique_ptr<MBXGenericPacket> CreatePacket(MBXPacketFactory::MBXPacket Type)

auto it = map.find(Type);

if (it == map.end())
throw std::runtime_error("Unknown packet type!");

return it->second();


static MBXPacketFactory& Get()

static MBXPacketFactory instance;
return instance;

;


Here's an example of packets:



// Packets.h
#include "MBXGeneric.h"
#include "MBXPacketFactory.h"

class ChatMessage : public MBXGenericPacket

public:
ChatMessage() : MBXGenericPacket(MBXPacketFactory::MBXPacket::Msg_MessageBox)


void Serialize(PacketData& data) override

data << H_MBX;

data << sz_Title << s_Title;
data << sz_Message << s_Message;


void Deserialize(const PacketData& data) override

// Extract the header
data >> H_MBX;

data >> sz_Title;
data.read(s_Title, sz_Title);

data >> sz_Message;
data.read(s_Message, sz_Message);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ChatMessage>();

public:

// Getters and setters

std::string GetTitle()

return s_Title;


std::string GetMessage()

return s_Message;


void SetTitle(const std::string& title)

s_Title = title;


void SetMessage(const std::string& message)

s_Message = message;



private:
int32_t sz_Title, sz_Message;

std::string s_Title, s_Message;
;



class ErrorResponse : public MBXGenericPacket

public:
ErrorResponse() : MBXGenericPacket(MBXPacketFactory::MBXPacket::ErrorResponseMsg)


void Serialize(PacketData& data) override

data << H_MBX;
data << sz_Error << s_ErrorMessage;


void Deserialize(const PacketData& data) override

data >> H_MBX;

data >> sz_Error;
data.read(s_ErrorMessage, sz_Error);


static std::unique_ptr<MBXGenericPacket> CreatePacket()

return std::make_unique<ErrorResponse>();

public:

std::string GetErrorMessage()

return s_ErrorMessage;


void SetErrorMessage(const std::string& msg_error)

s_ErrorMessage = msg_error;


private:
int32_t sz_Error;
std::string s_ErrorMessage;
;


Example usage of factory:



When receiving a packet:



...

PacketData NewPacket = connection.RecvPacket();

uint16_t PacketID = GetUint16(NewPacket.Data);

std::unique_ptr<MBXGenericPacket> Packet = MBXPacketFactory::Get().CreatePacket(static_cast<MBXPacketFactory::MBXPacket>(PacketID));

// Up-cast the packet pointer to the right packet type

// Let's asume we've received an error response

std::unique_ptr<ErrorResponse> ErrorPacket(reinterpret_cast<ErrorResponse*>( Packet.release() ));

ErrorPacket->Deserialize(NewPacket);

std::cout << ErrorPacket->GetErrorMessage() << 'n';

...


When sending a packet:



...

std::string Title = GetUserInput();
std::string Message = GetUserInput();

std::unique_ptr<MBXGenericPacket> Interface = MBXPacketFactory::Get().CreatePacket(MBXPacketFactory::MBXPacket::Msg_MessageBox);

std::unique_ptr<ChatMessage> MessagePk(reinterpret_cast<ChatMessage*>( Interface.release() ));

MessagePk->SetTitle(Title);
MessagePk->SetMessage(Message);

PacketData MsgPacketData;
MessagePk->Serialize(MsgPacketData);

PacketQueue.push(MsgPacketData);

...


Improvements:



What I would like to improve in the current design:



  • The down-casting from the base class to the derived class in
    order to access member functions.

  • With each new packet derived from MBXGenericPacket, I'm forced to
    encode and decode the MBX_Header in the
    serialization/deserialization, thus causing code repetition.

  • To get the required packet class from the factory, I need to first
    extract the identifier from the header and then pass it to the
    CreatePacket() method. I think it will be nice if you could just
    pass the PacketData to the factory, rather then decoding
    information by yourself.

  • The factory's purpose is to abstract the creation process of classes.
    In my opinion, the packet identifiers should be encapsulated by the
    factory, but that causes a lot of noise in the code, as you have
    to type: MBXPacketFactory::MBXPacket:: ...

Let me know your thoughts about the code. I'm here to learn, so any opinion is welcomed !









share|improve this question












share|improve this question




share|improve this question








edited Apr 4 at 15:02
























asked Apr 4 at 14:34









Cosain Melic

636




636











  • I know this is not quite the purpose of code review but did you look into some of the available serialization libraries, protobuffers for example.
    – Harald Scheirich
    Apr 4 at 20:47










  • Nice code, but realizing CreatePacket() and CreateGenericPacket() as a helper functions rather than member functions should be better looking and more correct from factory design pattern point of view? Being static member functions, the outcome will be same though.
    – seccpur
    Apr 6 at 14:19










  • @seccpur What do you mean by "the same outcome" ? Isn't this the purpose of `Factory Pattern, to return a pointer to the base class, leaving subclasses to decide which classes to instantiate ?! (correct me if I'm wrong)
    – Cosain Melic
    Apr 6 at 14:42











  • @CosainMelic: I will love to call CreatePacket(...) or MBXPacketFactory::CreatePacket() instead of MBXPacketFactory::Get().CreatePacket(...) in the implementation. I would like a namespace here to avoid conflict. BTW i have +1.
    – seccpur
    Apr 6 at 14:58











  • @seccpur Yes, it feels more natural, but to do that I'm afraid I will complicate things more. The MBXPacketFactory::CreatePacket(...) will have to be a static function, the constructor of the factory will have to be made public, and I will have to make sure only one instance of the class exists.
    – Cosain Melic
    Apr 6 at 15:24
















  • I know this is not quite the purpose of code review but did you look into some of the available serialization libraries, protobuffers for example.
    – Harald Scheirich
    Apr 4 at 20:47










  • Nice code, but realizing CreatePacket() and CreateGenericPacket() as a helper functions rather than member functions should be better looking and more correct from factory design pattern point of view? Being static member functions, the outcome will be same though.
    – seccpur
    Apr 6 at 14:19










  • @seccpur What do you mean by "the same outcome" ? Isn't this the purpose of `Factory Pattern, to return a pointer to the base class, leaving subclasses to decide which classes to instantiate ?! (correct me if I'm wrong)
    – Cosain Melic
    Apr 6 at 14:42











  • @CosainMelic: I will love to call CreatePacket(...) or MBXPacketFactory::CreatePacket() instead of MBXPacketFactory::Get().CreatePacket(...) in the implementation. I would like a namespace here to avoid conflict. BTW i have +1.
    – seccpur
    Apr 6 at 14:58











  • @seccpur Yes, it feels more natural, but to do that I'm afraid I will complicate things more. The MBXPacketFactory::CreatePacket(...) will have to be a static function, the constructor of the factory will have to be made public, and I will have to make sure only one instance of the class exists.
    – Cosain Melic
    Apr 6 at 15:24















I know this is not quite the purpose of code review but did you look into some of the available serialization libraries, protobuffers for example.
– Harald Scheirich
Apr 4 at 20:47




I know this is not quite the purpose of code review but did you look into some of the available serialization libraries, protobuffers for example.
– Harald Scheirich
Apr 4 at 20:47












Nice code, but realizing CreatePacket() and CreateGenericPacket() as a helper functions rather than member functions should be better looking and more correct from factory design pattern point of view? Being static member functions, the outcome will be same though.
– seccpur
Apr 6 at 14:19




Nice code, but realizing CreatePacket() and CreateGenericPacket() as a helper functions rather than member functions should be better looking and more correct from factory design pattern point of view? Being static member functions, the outcome will be same though.
– seccpur
Apr 6 at 14:19












@seccpur What do you mean by "the same outcome" ? Isn't this the purpose of `Factory Pattern, to return a pointer to the base class, leaving subclasses to decide which classes to instantiate ?! (correct me if I'm wrong)
– Cosain Melic
Apr 6 at 14:42





@seccpur What do you mean by "the same outcome" ? Isn't this the purpose of `Factory Pattern, to return a pointer to the base class, leaving subclasses to decide which classes to instantiate ?! (correct me if I'm wrong)
– Cosain Melic
Apr 6 at 14:42













@CosainMelic: I will love to call CreatePacket(...) or MBXPacketFactory::CreatePacket() instead of MBXPacketFactory::Get().CreatePacket(...) in the implementation. I would like a namespace here to avoid conflict. BTW i have +1.
– seccpur
Apr 6 at 14:58





@CosainMelic: I will love to call CreatePacket(...) or MBXPacketFactory::CreatePacket() instead of MBXPacketFactory::Get().CreatePacket(...) in the implementation. I would like a namespace here to avoid conflict. BTW i have +1.
– seccpur
Apr 6 at 14:58













@seccpur Yes, it feels more natural, but to do that I'm afraid I will complicate things more. The MBXPacketFactory::CreatePacket(...) will have to be a static function, the constructor of the factory will have to be made public, and I will have to make sure only one instance of the class exists.
– Cosain Melic
Apr 6 at 15:24




@seccpur Yes, it feels more natural, but to do that I'm afraid I will complicate things more. The MBXPacketFactory::CreatePacket(...) will have to be a static function, the constructor of the factory will have to be made public, and I will have to make sure only one instance of the class exists.
– Cosain Melic
Apr 6 at 15:24










4 Answers
4






active

oldest

votes

















up vote
2
down vote



accepted










This is rather long, so I’m posting a new answer rather than making it a P.S. on the first note about visitors.




You have separate IPacket (interface-only) and MBXGenericPacket (any kind of packet). I use only one class, as I want my typeID to be present in the polymorphic pointer. I don't like how you have two classes — which one am I supposed to use, and is it proper to use one or the other in different circumstances? I think it's an artifact of thinking of Java style interfaces.



Your post initially confused me because you use message and packet as synonyms. Packet is what comes over the wire, Message is the useful object you extract from it. So I'll go with Message here.



enum MBXHeader 
eChat = 0,
eError,
// etc.
;


class Message
protected:
MBXHeader H_MBX; // packet type ID
Message (MBXHeader id) : H_MBX(id)
public:
MBXHeader get_header() const return H_MBX;
virtual ~Message() = default;
virtual void serialize(PacketData&) = 0;

// this is your factory (needed for receiving)
static unique_ptr<Message> Create (PacketData& data);
;


Your values in the chat message are free to set and get; they behave as a simple struct. So hiding them and having get/set for each one is not useful. If you have boilerplate get/set for every data member that do nothing other than return and assign, you are missing the point. In this case, you could have a content sanity check done before the serialization, if you wanted to check the values for legality.



class ChatMessage : public Message 
public:
int32_t sz_Title;
int32_t sz_Message;
std::string s_Title;
std::string s_Message;

ChatMessage() : Message(eChat)
ChatMessage (PacketData& data);
void serialize(PacketData&) override;
;

class ErrorMessage : public Message
public:
int32_t sz_Error;
int32_t sz_Message;

ErrorMessage() : Message(eError)
ErrorMessage (PacketData& data);
void serialize(PacketData&) override;
;


The send side is simple and look at that first so it doesn't get over complicated with stuff you need for polymorphic creation and run-time types.



Just create a message object, populate it, and send it.



 ChatMessage msg; // no pointers!!
msg.sMessage= "Hello World!";
// ... etc.
PacketData MsgPacketData;
msg->serialize(MsgPacketData);
PacketQueue.push(MsgPacketData);


To receive a message, you don't know the type. That's where you need a factory.



 PacketData NewPacket = connection.RecvPacket();
auto incomingMessage = Message::Create (NewPacket);


Now what do you do with it? The easy way:



 incomingMessage->process(); // virtual function call


since that is what the program is for, essentially, I don't see what's wrong with making that a virtual function. Note that you can define them in different CPP files so the header doesn't need to know about the details of processing.



But this is just a toy example, and I know from my own work that we do indeed get different things we want to do and isolate the different systems from each other. So, as your original question indicated, you want to use the Visitor pattern.



Let’s start with how you want to use it, then go back and make it work.



As explained before, you define your "overloaded" function for each type you want to handle. The details of what you want to do go in the function bodies, not shown. Each body gets the parameter of the correct type.



struct process_message 
void operator()(const ChatMessage&) const;
void operator()(const ErrorMessage&) const;
;


Then have some common dispatching infrastructure that gets the object to the right function based on the actual type.



 visit (process_message(), *incomingMessage);


Just how do you write the visit function? We already looked at double-dispatch and are not doing that.



Note that we are assigning our own type ID codes, which is a fundimental part of having a wire encoding of different packets. That’s what we need for the protocol and ultimatly how the receiver does in fact know the type. So just use that! Unlike the C++ typeid, these are a compact range of integers so a switch/case statement is possible and very efficient.



 template<typename V>
void visit (V visitor, Message& m)

switch (m.get_header())
default: throw std::invalid_argument("oops!");
case eChat:
auto& msg = static_cast<ChatMessage&>(m);
visitor(msg);
break;

case eError:
auto& msg = static_cast<ErrorMessage&>(m);
visitor(msg);
break;

// repeat the cases for each kind of message.
// Note: if checking for non-final types, order matters!




Piece of cake, though you have to replicate the small case clause by hand.



Now, what about reading? You’ll notice that the classes reflect my earlier comments in that the deserialization is done by the constructor. It can be implemented in the same manner as the visitor.



 unique_ptr<Message> Message::Create (const PacketData& data)

uint16_t PacketID = GetUint16(data.Data);
switch (PacketID)
default: throw std::invalid_argument("oops!");
case eChat:
return make_unique<ChatMessage>(data);
case eError:
return make_unique<ErrorMessage>(data);

// add a case for each message type

}


And that's all. The Create function is special, and then the visit function can handle everything else you want to do with the objects based on the concrete type of what was read.






share|improve this answer

















  • 1




    Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
    – Cosain Melic
    Apr 8 at 7:42

















up vote
1
down vote













For the first point — casting from the base class for every use.



really need that?



In the last block of example code, you are creating a packet of a type you do in fact know in advance — you are asking for a ChatMessage so you can populate it. So, don’t use the factory here! Just create the object.



Have the class provide a static member for that, so they are always created the right way (on the heap).



auto MessagePk = ChatMessage::Create();
MessagePk->SetTitle(Title);
⋮
// prep and send it.


Visitor Pattern



In the penultimate code block,
you are getting a generic (polymorphic) packet and need to do different things based on its type, but you are a consumer of the class and not in a position to add virtual functions to it.



What you want here is the visitor pattern.



Write a struct that is a function-callable object, but contains overloads for the different types of packet that you have. Then a single “don't look under the hood” piece of code will call the proper one based on the dynamic type. You can easily extend this with new “externally polymorphic” functions, and not write a single cast.



auto Packet= MBXPacketFactory::Get_Next_Packet(); // whatever
Packet.Deserialize(NewPacket); // this one should be a simple virtual function.
visit (my_processor, *Packet);

⋮

struct my_processor
void operator() (ChatMessage& packet);
void operator() (ErrorMessage& packet)

std::cout << packet->GetErrorMessage() << 'n';

// etc.
;



Now how do you write visit? You can include a virtual function in the class hierarchy that does this. This is a classic approach, but it has the drawback that the processor has to inherit from an interface class and all the individual functions have to be virtual.



class Base 
virtual void visit (const VisitInterface&) =0;


class Derived_1 : public Base  






Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191257%2fpacket-factory-design-for-networking-application%23new-answer', 'question_page');

);

Post as a guest






























4 Answers
4






active

oldest

votes








4 Answers
4






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted










This is rather long, so I’m posting a new answer rather than making it a P.S. on the first note about visitors.




You have separate IPacket (interface-only) and MBXGenericPacket (any kind of packet). I use only one class, as I want my typeID to be present in the polymorphic pointer. I don't like how you have two classes — which one am I supposed to use, and is it proper to use one or the other in different circumstances? I think it's an artifact of thinking of Java style interfaces.



Your post initially confused me because you use message and packet as synonyms. Packet is what comes over the wire, Message is the useful object you extract from it. So I'll go with Message here.



enum MBXHeader 
eChat = 0,
eError,
// etc.
;


class Message
protected:
MBXHeader H_MBX; // packet type ID
Message (MBXHeader id) : H_MBX(id)
public:
MBXHeader get_header() const return H_MBX;
virtual ~Message() = default;
virtual void serialize(PacketData&) = 0;

// this is your factory (needed for receiving)
static unique_ptr<Message> Create (PacketData& data);
;


Your values in the chat message are free to set and get; they behave as a simple struct. So hiding them and having get/set for each one is not useful. If you have boilerplate get/set for every data member that do nothing other than return and assign, you are missing the point. In this case, you could have a content sanity check done before the serialization, if you wanted to check the values for legality.



class ChatMessage : public Message 
public:
int32_t sz_Title;
int32_t sz_Message;
std::string s_Title;
std::string s_Message;

ChatMessage() : Message(eChat)
ChatMessage (PacketData& data);
void serialize(PacketData&) override;
;

class ErrorMessage : public Message
public:
int32_t sz_Error;
int32_t sz_Message;

ErrorMessage() : Message(eError)
ErrorMessage (PacketData& data);
void serialize(PacketData&) override;
;


The send side is simple and look at that first so it doesn't get over complicated with stuff you need for polymorphic creation and run-time types.



Just create a message object, populate it, and send it.



 ChatMessage msg; // no pointers!!
msg.sMessage= "Hello World!";
// ... etc.
PacketData MsgPacketData;
msg->serialize(MsgPacketData);
PacketQueue.push(MsgPacketData);


To receive a message, you don't know the type. That's where you need a factory.



 PacketData NewPacket = connection.RecvPacket();
auto incomingMessage = Message::Create (NewPacket);


Now what do you do with it? The easy way:



 incomingMessage->process(); // virtual function call


since that is what the program is for, essentially, I don't see what's wrong with making that a virtual function. Note that you can define them in different CPP files so the header doesn't need to know about the details of processing.



But this is just a toy example, and I know from my own work that we do indeed get different things we want to do and isolate the different systems from each other. So, as your original question indicated, you want to use the Visitor pattern.



Let’s start with how you want to use it, then go back and make it work.



As explained before, you define your "overloaded" function for each type you want to handle. The details of what you want to do go in the function bodies, not shown. Each body gets the parameter of the correct type.



struct process_message 
void operator()(const ChatMessage&) const;
void operator()(const ErrorMessage&) const;
;


Then have some common dispatching infrastructure that gets the object to the right function based on the actual type.



 visit (process_message(), *incomingMessage);


Just how do you write the visit function? We already looked at double-dispatch and are not doing that.



Note that we are assigning our own type ID codes, which is a fundimental part of having a wire encoding of different packets. That’s what we need for the protocol and ultimatly how the receiver does in fact know the type. So just use that! Unlike the C++ typeid, these are a compact range of integers so a switch/case statement is possible and very efficient.



 template<typename V>
void visit (V visitor, Message& m)

switch (m.get_header())
default: throw std::invalid_argument("oops!");
case eChat:
auto& msg = static_cast<ChatMessage&>(m);
visitor(msg);
break;

case eError:
auto& msg = static_cast<ErrorMessage&>(m);
visitor(msg);
break;

// repeat the cases for each kind of message.
// Note: if checking for non-final types, order matters!




Piece of cake, though you have to replicate the small case clause by hand.



Now, what about reading? You’ll notice that the classes reflect my earlier comments in that the deserialization is done by the constructor. It can be implemented in the same manner as the visitor.



 unique_ptr<Message> Message::Create (const PacketData& data)

uint16_t PacketID = GetUint16(data.Data);
switch (PacketID)
default: throw std::invalid_argument("oops!");
case eChat:
return make_unique<ChatMessage>(data);
case eError:
return make_unique<ErrorMessage>(data);

// add a case for each message type




And that's all. The Create function is special, and then the visit function can handle everything else you want to do with the objects based on the concrete type of what was read.






share|improve this answer

















  • 1




    Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
    – Cosain Melic
    Apr 8 at 7:42














up vote
2
down vote



accepted










This is rather long, so I’m posting a new answer rather than making it a P.S. on the first note about visitors.




You have separate IPacket (interface-only) and MBXGenericPacket (any kind of packet). I use only one class, as I want my typeID to be present in the polymorphic pointer. I don't like how you have two classes — which one am I supposed to use, and is it proper to use one or the other in different circumstances? I think it's an artifact of thinking of Java style interfaces.



Your post initially confused me because you use message and packet as synonyms. Packet is what comes over the wire, Message is the useful object you extract from it. So I'll go with Message here.



enum MBXHeader 
eChat = 0,
eError,
// etc.
;


class Message
protected:
MBXHeader H_MBX; // packet type ID
Message (MBXHeader id) : H_MBX(id)
public:
MBXHeader get_header() const return H_MBX;
virtual ~Message() = default;
virtual void serialize(PacketData&) = 0;

// this is your factory (needed for receiving)
static unique_ptr<Message> Create (PacketData& data);
;


Your values in the chat message are free to set and get; they behave as a simple struct. So hiding them and having get/set for each one is not useful. If you have boilerplate get/set for every data member that do nothing other than return and assign, you are missing the point. In this case, you could have a content sanity check done before the serialization, if you wanted to check the values for legality.



class ChatMessage : public Message 
public:
int32_t sz_Title;
int32_t sz_Message;
std::string s_Title;
std::string s_Message;

ChatMessage() : Message(eChat)
ChatMessage (PacketData& data);
void serialize(PacketData&) override;
;

class ErrorMessage : public Message
public:
int32_t sz_Error;
int32_t sz_Message;

ErrorMessage() : Message(eError)
ErrorMessage (PacketData& data);
void serialize(PacketData&) override;
;


The send side is simple and look at that first so it doesn't get over complicated with stuff you need for polymorphic creation and run-time types.



Just create a message object, populate it, and send it.



 ChatMessage msg; // no pointers!!
msg.sMessage= "Hello World!";
// ... etc.
PacketData MsgPacketData;
msg->serialize(MsgPacketData);
PacketQueue.push(MsgPacketData);


To receive a message, you don't know the type. That's where you need a factory.



 PacketData NewPacket = connection.RecvPacket();
auto incomingMessage = Message::Create (NewPacket);


Now what do you do with it? The easy way:



 incomingMessage->process(); // virtual function call


since that is what the program is for, essentially, I don't see what's wrong with making that a virtual function. Note that you can define them in different CPP files so the header doesn't need to know about the details of processing.



But this is just a toy example, and I know from my own work that we do indeed get different things we want to do and isolate the different systems from each other. So, as your original question indicated, you want to use the Visitor pattern.



Let’s start with how you want to use it, then go back and make it work.



As explained before, you define your "overloaded" function for each type you want to handle. The details of what you want to do go in the function bodies, not shown. Each body gets the parameter of the correct type.



struct process_message 
void operator()(const ChatMessage&) const;
void operator()(const ErrorMessage&) const;
;


Then have some common dispatching infrastructure that gets the object to the right function based on the actual type.



 visit (process_message(), *incomingMessage);


Just how do you write the visit function? We already looked at double-dispatch and are not doing that.



Note that we are assigning our own type ID codes, which is a fundimental part of having a wire encoding of different packets. That’s what we need for the protocol and ultimatly how the receiver does in fact know the type. So just use that! Unlike the C++ typeid, these are a compact range of integers so a switch/case statement is possible and very efficient.



 template<typename V>
void visit (V visitor, Message& m)

switch (m.get_header())
default: throw std::invalid_argument("oops!");
case eChat:
auto& msg = static_cast<ChatMessage&>(m);
visitor(msg);
break;

case eError:
auto& msg = static_cast<ErrorMessage&>(m);
visitor(msg);
break;

// repeat the cases for each kind of message.
// Note: if checking for non-final types, order matters!




Piece of cake, though you have to replicate the small case clause by hand.



Now, what about reading? You’ll notice that the classes reflect my earlier comments in that the deserialization is done by the constructor. It can be implemented in the same manner as the visitor.



 unique_ptr<Message> Message::Create (const PacketData& data)

uint16_t PacketID = GetUint16(data.Data);
switch (PacketID)
default: throw std::invalid_argument("oops!");
case eChat:
return make_unique<ChatMessage>(data);
case eError:
return make_unique<ErrorMessage>(data);

// add a case for each message type

}


And that's all. The Create function is special, and then the visit function can handle everything else you want to do with the objects based on the concrete type of what was read.






share|improve this answer

















  • 1




    Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
    – Cosain Melic
    Apr 8 at 7:42












up vote
2
down vote



accepted







up vote
2
down vote



accepted






This is rather long, so I’m posting a new answer rather than making it a P.S. on the first note about visitors.




You have separate IPacket (interface-only) and MBXGenericPacket (any kind of packet). I use only one class, as I want my typeID to be present in the polymorphic pointer. I don't like how you have two classes — which one am I supposed to use, and is it proper to use one or the other in different circumstances? I think it's an artifact of thinking of Java style interfaces.



Your post initially confused me because you use message and packet as synonyms. Packet is what comes over the wire, Message is the useful object you extract from it. So I'll go with Message here.



enum MBXHeader 
eChat = 0,
eError,
// etc.
;


class Message
protected:
MBXHeader H_MBX; // packet type ID
Message (MBXHeader id) : H_MBX(id)
public:
MBXHeader get_header() const return H_MBX;
virtual ~Message() = default;
virtual void serialize(PacketData&) = 0;

// this is your factory (needed for receiving)
static unique_ptr<Message> Create (PacketData& data);
;


Your values in the chat message are free to set and get; they behave as a simple struct. So hiding them and having get/set for each one is not useful. If you have boilerplate get/set for every data member that do nothing other than return and assign, you are missing the point. In this case, you could have a content sanity check done before the serialization, if you wanted to check the values for legality.



class ChatMessage : public Message 
public:
int32_t sz_Title;
int32_t sz_Message;
std::string s_Title;
std::string s_Message;

ChatMessage() : Message(eChat)
ChatMessage (PacketData& data);
void serialize(PacketData&) override;
;

class ErrorMessage : public Message
public:
int32_t sz_Error;
int32_t sz_Message;

ErrorMessage() : Message(eError)
ErrorMessage (PacketData& data);
void serialize(PacketData&) override;
;


The send side is simple and look at that first so it doesn't get over complicated with stuff you need for polymorphic creation and run-time types.



Just create a message object, populate it, and send it.



 ChatMessage msg; // no pointers!!
msg.sMessage= "Hello World!";
// ... etc.
PacketData MsgPacketData;
msg->serialize(MsgPacketData);
PacketQueue.push(MsgPacketData);


To receive a message, you don't know the type. That's where you need a factory.



 PacketData NewPacket = connection.RecvPacket();
auto incomingMessage = Message::Create (NewPacket);


Now what do you do with it? The easy way:



 incomingMessage->process(); // virtual function call


since that is what the program is for, essentially, I don't see what's wrong with making that a virtual function. Note that you can define them in different CPP files so the header doesn't need to know about the details of processing.



But this is just a toy example, and I know from my own work that we do indeed get different things we want to do and isolate the different systems from each other. So, as your original question indicated, you want to use the Visitor pattern.



Let’s start with how you want to use it, then go back and make it work.



As explained before, you define your "overloaded" function for each type you want to handle. The details of what you want to do go in the function bodies, not shown. Each body gets the parameter of the correct type.



struct process_message 
void operator()(const ChatMessage&) const;
void operator()(const ErrorMessage&) const;
;


Then have some common dispatching infrastructure that gets the object to the right function based on the actual type.



 visit (process_message(), *incomingMessage);


Just how do you write the visit function? We already looked at double-dispatch and are not doing that.



Note that we are assigning our own type ID codes, which is a fundimental part of having a wire encoding of different packets. That’s what we need for the protocol and ultimatly how the receiver does in fact know the type. So just use that! Unlike the C++ typeid, these are a compact range of integers so a switch/case statement is possible and very efficient.



 template<typename V>
void visit (V visitor, Message& m)

switch (m.get_header())
default: throw std::invalid_argument("oops!");
case eChat:
auto& msg = static_cast<ChatMessage&>(m);
visitor(msg);
break;

case eError:
auto& msg = static_cast<ErrorMessage&>(m);
visitor(msg);
break;

// repeat the cases for each kind of message.
// Note: if checking for non-final types, order matters!




Piece of cake, though you have to replicate the small case clause by hand.



Now, what about reading? You’ll notice that the classes reflect my earlier comments in that the deserialization is done by the constructor. It can be implemented in the same manner as the visitor.



 unique_ptr<Message> Message::Create (const PacketData& data)

uint16_t PacketID = GetUint16(data.Data);
switch (PacketID)
default: throw std::invalid_argument("oops!");
case eChat:
return make_unique<ChatMessage>(data);
case eError:
return make_unique<ErrorMessage>(data);

// add a case for each message type

}


And that's all. The Create function is special, and then the visit function can handle everything else you want to do with the objects based on the concrete type of what was read.






share|improve this answer













This is rather long, so I’m posting a new answer rather than making it a P.S. on the first note about visitors.




You have separate IPacket (interface-only) and MBXGenericPacket (any kind of packet). I use only one class, as I want my typeID to be present in the polymorphic pointer. I don't like how you have two classes — which one am I supposed to use, and is it proper to use one or the other in different circumstances? I think it's an artifact of thinking of Java style interfaces.



Your post initially confused me because you use message and packet as synonyms. Packet is what comes over the wire, Message is the useful object you extract from it. So I'll go with Message here.



enum MBXHeader 
eChat = 0,
eError,
// etc.
;


class Message
protected:
MBXHeader H_MBX; // packet type ID
Message (MBXHeader id) : H_MBX(id)
public:
MBXHeader get_header() const return H_MBX;
virtual ~Message() = default;
virtual void serialize(PacketData&) = 0;

// this is your factory (needed for receiving)
static unique_ptr<Message> Create (PacketData& data);
;


Your values in the chat message are free to set and get; they behave as a simple struct. So hiding them and having get/set for each one is not useful. If you have boilerplate get/set for every data member that do nothing other than return and assign, you are missing the point. In this case, you could have a content sanity check done before the serialization, if you wanted to check the values for legality.



class ChatMessage : public Message 
public:
int32_t sz_Title;
int32_t sz_Message;
std::string s_Title;
std::string s_Message;

ChatMessage() : Message(eChat)
ChatMessage (PacketData& data);
void serialize(PacketData&) override;
;

class ErrorMessage : public Message
public:
int32_t sz_Error;
int32_t sz_Message;

ErrorMessage() : Message(eError)
ErrorMessage (PacketData& data);
void serialize(PacketData&) override;
;


The send side is simple and look at that first so it doesn't get over complicated with stuff you need for polymorphic creation and run-time types.



Just create a message object, populate it, and send it.



 ChatMessage msg; // no pointers!!
msg.sMessage= "Hello World!";
// ... etc.
PacketData MsgPacketData;
msg->serialize(MsgPacketData);
PacketQueue.push(MsgPacketData);


To receive a message, you don't know the type. That's where you need a factory.



 PacketData NewPacket = connection.RecvPacket();
auto incomingMessage = Message::Create (NewPacket);


Now what do you do with it? The easy way:



 incomingMessage->process(); // virtual function call


since that is what the program is for, essentially, I don't see what's wrong with making that a virtual function. Note that you can define them in different CPP files so the header doesn't need to know about the details of processing.



But this is just a toy example, and I know from my own work that we do indeed get different things we want to do and isolate the different systems from each other. So, as your original question indicated, you want to use the Visitor pattern.



Let’s start with how you want to use it, then go back and make it work.



As explained before, you define your "overloaded" function for each type you want to handle. The details of what you want to do go in the function bodies, not shown. Each body gets the parameter of the correct type.



struct process_message 
void operator()(const ChatMessage&) const;
void operator()(const ErrorMessage&) const;
;


Then have some common dispatching infrastructure that gets the object to the right function based on the actual type.



 visit (process_message(), *incomingMessage);


Just how do you write the visit function? We already looked at double-dispatch and are not doing that.



Note that we are assigning our own type ID codes, which is a fundimental part of having a wire encoding of different packets. That’s what we need for the protocol and ultimatly how the receiver does in fact know the type. So just use that! Unlike the C++ typeid, these are a compact range of integers so a switch/case statement is possible and very efficient.



 template<typename V>
void visit (V visitor, Message& m)

switch (m.get_header())
default: throw std::invalid_argument("oops!");
case eChat:
auto& msg = static_cast<ChatMessage&>(m);
visitor(msg);
break;

case eError:
auto& msg = static_cast<ErrorMessage&>(m);
visitor(msg);
break;

// repeat the cases for each kind of message.
// Note: if checking for non-final types, order matters!




Piece of cake, though you have to replicate the small case clause by hand.



Now, what about reading? You’ll notice that the classes reflect my earlier comments in that the deserialization is done by the constructor. It can be implemented in the same manner as the visitor.



 unique_ptr<Message> Message::Create (const PacketData& data)

uint16_t PacketID = GetUint16(data.Data);
switch (PacketID)
default: throw std::invalid_argument("oops!");
case eChat:
return make_unique<ChatMessage>(data);
case eError:
return make_unique<ErrorMessage>(data);

// add a case for each message type

}


And that's all. The Create function is special, and then the visit function can handle everything else you want to do with the objects based on the concrete type of what was read.







share|improve this answer













share|improve this answer



share|improve this answer











answered Apr 8 at 0:26









JDługosz

5,047731




5,047731







  • 1




    Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
    – Cosain Melic
    Apr 8 at 7:42












  • 1




    Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
    – Cosain Melic
    Apr 8 at 7:42







1




1




Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
– Cosain Melic
Apr 8 at 7:42




Thank you so much for the complete answer and time you put in to explain clearly to me and others your design ! That's exactly what I needed. After your second answer, I've read a lot of articles about the Visitor pattern, but I still had problems adapting it to my design. Now I can get coding :)
– Cosain Melic
Apr 8 at 7:42












up vote
1
down vote













For the first point — casting from the base class for every use.



really need that?



In the last block of example code, you are creating a packet of a type you do in fact know in advance — you are asking for a ChatMessage so you can populate it. So, don’t use the factory here! Just create the object.



Have the class provide a static member for that, so they are always created the right way (on the heap).



auto MessagePk = ChatMessage::Create();
MessagePk->SetTitle(Title);
⋮
// prep and send it.


Visitor Pattern



In the penultimate code block,
you are getting a generic (polymorphic) packet and need to do different things based on its type, but you are a consumer of the class and not in a position to add virtual functions to it.



What you want here is the visitor pattern.



Write a struct that is a function-callable object, but contains overloads for the different types of packet that you have. Then a single “don't look under the hood” piece of code will call the proper one based on the dynamic type. You can easily extend this with new “externally polymorphic” functions, and not write a single cast.



auto Packet= MBXPacketFactory::Get_Next_Packet(); // whatever
Packet.Deserialize(NewPacket); // this one should be a simple virtual function.
visit (my_processor, *Packet);

⋮

struct my_processor
void operator() (ChatMessage& packet);
void operator() (ErrorMessage& packet)

std::cout << packet->GetErrorMessage() << 'n';

// etc.
;



Now how do you write visit? You can include a virtual function in the class hierarchy that does this. This is a classic approach, but it has the drawback that the processor has to inherit from an interface class and all the individual functions have to be virtual.



class Base 
virtual void visit (const VisitInterface&) =0;


class Derived_1 : public Base {
void visit (const VisitInterface& visitor)

visitor (*this);



This is called double dispatch because you are using virtual calls twice to resolve two different polymorphic aspects of the call. The first call to visit is a virtual call so it recovers the correct type of the message object naturally. Then the body of that function uses overloading to call the proper function of the set. But, to allow for different commands, that requires a virtual dispatch too.



You also see that my sketch did not provide additional arguments to the call. If you had commands with different signatures, this gets repetitive.




Look at std::variant and the original Boost.Variant which has a good overview and tutorial about it.



This lets you write your visitor similarly to what I described: a single function-call object with overloaded forms in it. But it does not use polymorphism to do its work! The different classes are named explicitly in the definition of the variant.



You could write your program so that a generic message is a variant of the different kinds of messages, rather than a base class.



Then my example above would work as I wrote it by calling std::visit.






share|improve this answer























  • After reading about the visitor design pattern, I start to understand what you're saying. I think it's a really good ideea to separate the data processing on the objects from the actual class interface, as it makes code much cleaner. But I can't really decide to go for the classical approach or the modern one (function-callable object).
    – Cosain Melic
    Apr 6 at 8:20










  • It seams like std::visit will give me more flexibility on the different types of processors and allow me at the same time to make the base class really Generic, as you've pointed out in the previous answer.
    – Cosain Melic
    Apr 6 at 8:24














up vote
1
down vote













For the first point — casting from the base class for every use.



really need that?



In the last block of example code, you are creating a packet of a type you do in fact know in advance — you are asking for a ChatMessage so you can populate it. So, don’t use the factory here! Just create the object.



Have the class provide a static member for that, so they are always created the right way (on the heap).



auto MessagePk = ChatMessage::Create();
MessagePk->SetTitle(Title);
⋮
// prep and send it.


Visitor Pattern



In the penultimate code block,
you are getting a generic (polymorphic) packet and need to do different things based on its type, but you are a consumer of the class and not in a position to add virtual functions to it.



What you want here is the visitor pattern.



Write a struct that is a function-callable object, but contains overloads for the different types of packet that you have. Then a single “don't look under the hood” piece of code will call the proper one based on the dynamic type. You can easily extend this with new “externally polymorphic” functions, and not write a single cast.



auto Packet= MBXPacketFactory::Get_Next_Packet(); // whatever
Packet.Deserialize(NewPacket); // this one should be a simple virtual function.
visit (my_processor, *Packet);

⋮

struct my_processor
void operator() (ChatMessage& packet);
void operator() (ErrorMessage& packet)

std::cout << packet->GetErrorMessage() << 'n';

// etc.
;



Now how do you write visit? You can include a virtual function in the class hierarchy that does this. This is a classic approach, but it has the drawback that the processor has to inherit from an interface class and all the individual functions have to be virtual.



class Base 
virtual void visit (const VisitInterface&) =0;


class Derived_1 : public Base {
void visit (const VisitInterface& visitor)

visitor (*this);



This is called double dispatch because you are using virtual calls twice to resolve two different polymorphic aspects of the call. The first call to visit is a virtual call so it recovers the correct type of the message object naturally. Then the body of that function uses overloading to call the proper function of the set. But, to allow for different commands, that requires a virtual dispatch too.



You also see that my sketch did not provide additional arguments to the call. If you had commands with different signatures, this gets repetitive.




Look at std::variant and the original Boost.Variant which has a good overview and tutorial about it.



This lets you write your visitor similarly to what I described: a single function-call object with overloaded forms in it. But it does not use polymorphism to do its work! The different classes are named explicitly in the definition of the variant.



You could write your program so that a generic message is a variant of the different kinds of messages, rather than a base class.



Then my example above would work as I wrote it by calling std::visit.






share|improve this answer























  • After reading about the visitor design pattern, I start to understand what you're saying. I think it's a really good ideea to separate the data processing on the objects from the actual class interface, as it makes code much cleaner. But I can't really decide to go for the classical approach or the modern one (function-callable object).
    – Cosain Melic
    Apr 6 at 8:20










  • It seams like std::visit will give me more flexibility on the different types of processors and allow me at the same time to make the base class really Generic, as you've pointed out in the previous answer.
    – Cosain Melic
    Apr 6 at 8:24












up vote
1
down vote










up vote
1
down vote









For the first point — casting from the base class for every use.



really need that?



In the last block of example code, you are creating a packet of a type you do in fact know in advance — you are asking for a ChatMessage so you can populate it. So, don’t use the factory here! Just create the object.



Have the class provide a static member for that, so they are always created the right way (on the heap).



auto MessagePk = ChatMessage::Create();
MessagePk->SetTitle(Title);
⋮
// prep and send it.


Visitor Pattern



In the penultimate code block,
you are getting a generic (polymorphic) packet and need to do different things based on its type, but you are a consumer of the class and not in a position to add virtual functions to it.



What you want here is the visitor pattern.



Write a struct that is a function-callable object, but contains overloads for the different types of packet that you have. Then a single “don't look under the hood” piece of code will call the proper one based on the dynamic type. You can easily extend this with new “externally polymorphic” functions, and not write a single cast.



auto Packet= MBXPacketFactory::Get_Next_Packet(); // whatever
Packet.Deserialize(NewPacket); // this one should be a simple virtual function.
visit (my_processor, *Packet);

⋮

struct my_processor
void operator() (ChatMessage& packet);
void operator() (ErrorMessage& packet)

std::cout << packet->GetErrorMessage() << 'n';

// etc.
;



Now how do you write visit? You can include a virtual function in the class hierarchy that does this. This is a classic approach, but it has the drawback that the processor has to inherit from an interface class and all the individual functions have to be virtual.



class Base 
virtual void visit (const VisitInterface&) =0;


class Derived_1 : public Base {
void visit (const VisitInterface& visitor)

visitor (*this);



This is called double dispatch because you are using virtual calls twice to resolve two different polymorphic aspects of the call. The first call to visit is a virtual call so it recovers the correct type of the message object naturally. Then the body of that function uses overloading to call the proper function of the set. But, to allow for different commands, that requires a virtual dispatch too.



You also see that my sketch did not provide additional arguments to the call. If you had commands with different signatures, this gets repetitive.




Look at std::variant and the original Boost.Variant which has a good overview and tutorial about it.



This lets you write your visitor similarly to what I described: a single function-call object with overloaded forms in it. But it does not use polymorphism to do its work! The different classes are named explicitly in the definition of the variant.



You could write your program so that a generic message is a variant of the different kinds of messages, rather than a base class.



Then my example above would work as I wrote it by calling std::visit.






share|improve this answer















For the first point — casting from the base class for every use.



really need that?



In the last block of example code, you are creating a packet of a type you do in fact know in advance — you are asking for a ChatMessage so you can populate it. So, don’t use the factory here! Just create the object.



Have the class provide a static member for that, so they are always created the right way (on the heap).



auto MessagePk = ChatMessage::Create();
MessagePk->SetTitle(Title);
⋮
// prep and send it.


Visitor Pattern



In the penultimate code block,
you are getting a generic (polymorphic) packet and need to do different things based on its type, but you are a consumer of the class and not in a position to add virtual functions to it.



What you want here is the visitor pattern.



Write a struct that is a function-callable object, but contains overloads for the different types of packet that you have. Then a single “don't look under the hood” piece of code will call the proper one based on the dynamic type. You can easily extend this with new “externally polymorphic” functions, and not write a single cast.



auto Packet= MBXPacketFactory::Get_Next_Packet(); // whatever
Packet.Deserialize(NewPacket); // this one should be a simple virtual function.
visit (my_processor, *Packet);

⋮

struct my_processor
void operator() (ChatMessage& packet);
void operator() (ErrorMessage& packet)

std::cout << packet->GetErrorMessage() << 'n';

// etc.
;



Now how do you write visit? You can include a virtual function in the class hierarchy that does this. This is a classic approach, but it has the drawback that the processor has to inherit from an interface class and all the individual functions have to be virtual.



class Base 
virtual void visit (const VisitInterface&) =0;


class Derived_1 : public Base {
void visit (const VisitInterface& visitor)

visitor (*this);



This is called double dispatch because you are using virtual calls twice to resolve two different polymorphic aspects of the call. The first call to visit is a virtual call so it recovers the correct type of the message object naturally. Then the body of that function uses overloading to call the proper function of the set. But, to allow for different commands, that requires a virtual dispatch too.



You also see that my sketch did not provide additional arguments to the call. If you had commands with different signatures, this gets repetitive.




Look at std::variant and the original Boost.Variant which has a good overview and tutorial about it.



This lets you write your visitor similarly to what I described: a single function-call object with overloaded forms in it. But it does not use polymorphism to do its work! The different classes are named explicitly in the definition of the variant.



You could write your program so that a generic message is a variant of the different kinds of messages, rather than a base class.



Then my example above would work as I wrote it by calling std::visit.







share|improve this answer















share|improve this answer



share|improve this answer








edited Apr 4 at 20:46


























answered Apr 4 at 19:59









JDługosz

5,047731




5,047731











  • After reading about the visitor design pattern, I start to understand what you're saying. I think it's a really good ideea to separate the data processing on the objects from the actual class interface, as it makes code much cleaner. But I can't really decide to go for the classical approach or the modern one (function-callable object).
    – Cosain Melic
    Apr 6 at 8:20










  • It seams like std::visit will give me more flexibility on the different types of processors and allow me at the same time to make the base class really Generic, as you've pointed out in the previous answer.
    – Cosain Melic
    Apr 6 at 8:24
















  • After reading about the visitor design pattern, I start to understand what you're saying. I think it's a really good ideea to separate the data processing on the objects from the actual class interface, as it makes code much cleaner. But I can't really decide to go for the classical approach or the modern one (function-callable object).
    – Cosain Melic
    Apr 6 at 8:20










  • It seams like std::visit will give me more flexibility on the different types of processors and allow me at the same time to make the base class really Generic, as you've pointed out in the previous answer.
    – Cosain Melic
    Apr 6 at 8:24















After reading about the visitor design pattern, I start to understand what you're saying. I think it's a really good ideea to separate the data processing on the objects from the actual class interface, as it makes code much cleaner. But I can't really decide to go for the classical approach or the modern one (function-callable object).
– Cosain Melic
Apr 6 at 8:20




After reading about the visitor design pattern, I start to understand what you're saying. I think it's a really good ideea to separate the data processing on the objects from the actual class interface, as it makes code much cleaner. But I can't really decide to go for the classical approach or the modern one (function-callable object).
– Cosain Melic
Apr 6 at 8:20












It seams like std::visit will give me more flexibility on the different types of processors and allow me at the same time to make the base class really Generic, as you've pointed out in the previous answer.
– Cosain Melic
Apr 6 at 8:24




It seams like std::visit will give me more flexibility on the different types of processors and allow me at the same time to make the base class really Generic, as you've pointed out in the previous answer.
– Cosain Melic
Apr 6 at 8:24










up vote
1
down vote













For the second point — code repetition of MBX_Header



It is only one line, and it should be. Everything that is common should go into the base class, so you only need one call to trigger it all.



It is not at all uncommon to have functions call their base class’s version first (or last), but I argue that this would be more cumbersome than what you have:



void Serialize(PacketData& data) override

MBXGenericPacket::Serialize(data);
data << sz_Error << s_ErrorMessage;



Using the same syntax to serialize or deserialize the common header, as you have it in your code, is intuitive and easy to understand.



The key is to make it one thing, as you have already done by having a single base member. If there were more data members there, you would group them in a struct or have a base class function to call with straightforward syntax.






share|improve this answer

























    up vote
    1
    down vote













    For the second point — code repetition of MBX_Header



    It is only one line, and it should be. Everything that is common should go into the base class, so you only need one call to trigger it all.



    It is not at all uncommon to have functions call their base class’s version first (or last), but I argue that this would be more cumbersome than what you have:



    void Serialize(PacketData& data) override

    MBXGenericPacket::Serialize(data);
    data << sz_Error << s_ErrorMessage;



    Using the same syntax to serialize or deserialize the common header, as you have it in your code, is intuitive and easy to understand.



    The key is to make it one thing, as you have already done by having a single base member. If there were more data members there, you would group them in a struct or have a base class function to call with straightforward syntax.






    share|improve this answer























      up vote
      1
      down vote










      up vote
      1
      down vote









      For the second point — code repetition of MBX_Header



      It is only one line, and it should be. Everything that is common should go into the base class, so you only need one call to trigger it all.



      It is not at all uncommon to have functions call their base class’s version first (or last), but I argue that this would be more cumbersome than what you have:



      void Serialize(PacketData& data) override

      MBXGenericPacket::Serialize(data);
      data << sz_Error << s_ErrorMessage;



      Using the same syntax to serialize or deserialize the common header, as you have it in your code, is intuitive and easy to understand.



      The key is to make it one thing, as you have already done by having a single base member. If there were more data members there, you would group them in a struct or have a base class function to call with straightforward syntax.






      share|improve this answer













      For the second point — code repetition of MBX_Header



      It is only one line, and it should be. Everything that is common should go into the base class, so you only need one call to trigger it all.



      It is not at all uncommon to have functions call their base class’s version first (or last), but I argue that this would be more cumbersome than what you have:



      void Serialize(PacketData& data) override

      MBXGenericPacket::Serialize(data);
      data << sz_Error << s_ErrorMessage;



      Using the same syntax to serialize or deserialize the common header, as you have it in your code, is intuitive and easy to understand.



      The key is to make it one thing, as you have already done by having a single base member. If there were more data members there, you would group them in a struct or have a base class function to call with straightforward syntax.







      share|improve this answer













      share|improve this answer



      share|improve this answer











      answered Apr 4 at 20:58









      JDługosz

      5,047731




      5,047731




















          up vote
          1
          down vote













          For the third point — CreatePacket takes PacketData



          Certainly. I think you fell into a trap in making the factory take the ID of the kind of packet to create. As mentioned in First Point, you used that instead of creating normally when you already knew the type you wanted, on the sending side.



          So yes, have a static factory include the logic to figure out what kind of packet it is, hiding that detail from the users of the class.



          Then, since the factory has the data, why not have it do the deserialize too?
          You are already dispatching to a CreateMBXPacketCb function that’s written separately for each class. So pass the data through to that!



          auto msg = Factory::Create(packet_data);


          figures out the type from the data, and then calls the proper



          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData);


          the only difference from what you have being that CreatePacket takes a parameter now.



          Within the functions:



          //static 
          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData data)

          auto self= make_unique<WhateverClass>();
          self->Deserialize(data);
          return self;



          The forth point too!



          With the above design, nobody needs to be exposed to the type ID other than the factory function itself and each class which knows to initialize itself with the proper value when creating an empty message for sending.




          Advanced



          From my generic CreatePacket above, you can see that each one is exactly the same except for the name of its own class.



          This suggests that you make it Generic for real. You only need one template function.



          The only reason I say this is advanced is because of how you’re dispatching on the type to registered functions. You might scratch your head for a moment to figure out how to take the address of a specific instantiation. Then, you wonder why you need all those lines to populate the map when they are all the same except for the type name…






          share|improve this answer





















          • That sounds greate, but I can't really figure out how I would do that, as I'm not as experienced yet. The packets are pretty much the same, the only difference being the types of data they stores and respective accessor functions, which really makes me think if the Factory Pattern is what I need in this situation !?
            – Cosain Melic
            Apr 6 at 15:00










          • Yes, the factory pattern is how you create an object of the proper type when reading in a packet that could be of several types.
            – JDługosz
            Apr 6 at 18:57














          up vote
          1
          down vote













          For the third point — CreatePacket takes PacketData



          Certainly. I think you fell into a trap in making the factory take the ID of the kind of packet to create. As mentioned in First Point, you used that instead of creating normally when you already knew the type you wanted, on the sending side.



          So yes, have a static factory include the logic to figure out what kind of packet it is, hiding that detail from the users of the class.



          Then, since the factory has the data, why not have it do the deserialize too?
          You are already dispatching to a CreateMBXPacketCb function that’s written separately for each class. So pass the data through to that!



          auto msg = Factory::Create(packet_data);


          figures out the type from the data, and then calls the proper



          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData);


          the only difference from what you have being that CreatePacket takes a parameter now.



          Within the functions:



          //static 
          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData data)

          auto self= make_unique<WhateverClass>();
          self->Deserialize(data);
          return self;



          The forth point too!



          With the above design, nobody needs to be exposed to the type ID other than the factory function itself and each class which knows to initialize itself with the proper value when creating an empty message for sending.




          Advanced



          From my generic CreatePacket above, you can see that each one is exactly the same except for the name of its own class.



          This suggests that you make it Generic for real. You only need one template function.



          The only reason I say this is advanced is because of how you’re dispatching on the type to registered functions. You might scratch your head for a moment to figure out how to take the address of a specific instantiation. Then, you wonder why you need all those lines to populate the map when they are all the same except for the type name…






          share|improve this answer





















          • That sounds greate, but I can't really figure out how I would do that, as I'm not as experienced yet. The packets are pretty much the same, the only difference being the types of data they stores and respective accessor functions, which really makes me think if the Factory Pattern is what I need in this situation !?
            – Cosain Melic
            Apr 6 at 15:00










          • Yes, the factory pattern is how you create an object of the proper type when reading in a packet that could be of several types.
            – JDługosz
            Apr 6 at 18:57












          up vote
          1
          down vote










          up vote
          1
          down vote









          For the third point — CreatePacket takes PacketData



          Certainly. I think you fell into a trap in making the factory take the ID of the kind of packet to create. As mentioned in First Point, you used that instead of creating normally when you already knew the type you wanted, on the sending side.



          So yes, have a static factory include the logic to figure out what kind of packet it is, hiding that detail from the users of the class.



          Then, since the factory has the data, why not have it do the deserialize too?
          You are already dispatching to a CreateMBXPacketCb function that’s written separately for each class. So pass the data through to that!



          auto msg = Factory::Create(packet_data);


          figures out the type from the data, and then calls the proper



          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData);


          the only difference from what you have being that CreatePacket takes a parameter now.



          Within the functions:



          //static 
          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData data)

          auto self= make_unique<WhateverClass>();
          self->Deserialize(data);
          return self;



          The forth point too!



          With the above design, nobody needs to be exposed to the type ID other than the factory function itself and each class which knows to initialize itself with the proper value when creating an empty message for sending.




          Advanced



          From my generic CreatePacket above, you can see that each one is exactly the same except for the name of its own class.



          This suggests that you make it Generic for real. You only need one template function.



          The only reason I say this is advanced is because of how you’re dispatching on the type to registered functions. You might scratch your head for a moment to figure out how to take the address of a specific instantiation. Then, you wonder why you need all those lines to populate the map when they are all the same except for the type name…






          share|improve this answer













          For the third point — CreatePacket takes PacketData



          Certainly. I think you fell into a trap in making the factory take the ID of the kind of packet to create. As mentioned in First Point, you used that instead of creating normally when you already knew the type you wanted, on the sending side.



          So yes, have a static factory include the logic to figure out what kind of packet it is, hiding that detail from the users of the class.



          Then, since the factory has the data, why not have it do the deserialize too?
          You are already dispatching to a CreateMBXPacketCb function that’s written separately for each class. So pass the data through to that!



          auto msg = Factory::Create(packet_data);


          figures out the type from the data, and then calls the proper



          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData);


          the only difference from what you have being that CreatePacket takes a parameter now.



          Within the functions:



          //static 
          unique_ptr<MBXGenericPacket> WhateverClass::CreatePacket(PacketData data)

          auto self= make_unique<WhateverClass>();
          self->Deserialize(data);
          return self;



          The forth point too!



          With the above design, nobody needs to be exposed to the type ID other than the factory function itself and each class which knows to initialize itself with the proper value when creating an empty message for sending.




          Advanced



          From my generic CreatePacket above, you can see that each one is exactly the same except for the name of its own class.



          This suggests that you make it Generic for real. You only need one template function.



          The only reason I say this is advanced is because of how you’re dispatching on the type to registered functions. You might scratch your head for a moment to figure out how to take the address of a specific instantiation. Then, you wonder why you need all those lines to populate the map when they are all the same except for the type name…







          share|improve this answer













          share|improve this answer



          share|improve this answer











          answered Apr 4 at 21:25









          JDługosz

          5,047731




          5,047731











          • That sounds greate, but I can't really figure out how I would do that, as I'm not as experienced yet. The packets are pretty much the same, the only difference being the types of data they stores and respective accessor functions, which really makes me think if the Factory Pattern is what I need in this situation !?
            – Cosain Melic
            Apr 6 at 15:00










          • Yes, the factory pattern is how you create an object of the proper type when reading in a packet that could be of several types.
            – JDługosz
            Apr 6 at 18:57
















          • That sounds greate, but I can't really figure out how I would do that, as I'm not as experienced yet. The packets are pretty much the same, the only difference being the types of data they stores and respective accessor functions, which really makes me think if the Factory Pattern is what I need in this situation !?
            – Cosain Melic
            Apr 6 at 15:00










          • Yes, the factory pattern is how you create an object of the proper type when reading in a packet that could be of several types.
            – JDługosz
            Apr 6 at 18:57















          That sounds greate, but I can't really figure out how I would do that, as I'm not as experienced yet. The packets are pretty much the same, the only difference being the types of data they stores and respective accessor functions, which really makes me think if the Factory Pattern is what I need in this situation !?
          – Cosain Melic
          Apr 6 at 15:00




          That sounds greate, but I can't really figure out how I would do that, as I'm not as experienced yet. The packets are pretty much the same, the only difference being the types of data they stores and respective accessor functions, which really makes me think if the Factory Pattern is what I need in this situation !?
          – Cosain Melic
          Apr 6 at 15:00












          Yes, the factory pattern is how you create an object of the proper type when reading in a packet that could be of several types.
          – JDługosz
          Apr 6 at 18:57




          Yes, the factory pattern is how you create an object of the proper type when reading in a packet that could be of several types.
          – JDługosz
          Apr 6 at 18:57












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191257%2fpacket-factory-design-for-networking-application%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?