Is it possible to make a factory in C ++ that follows the open / closed principle?
In a project I am working on in C ++, I need to create objects for messages when they enter a wire. I am currently using a factory method pattern to hide the creation of objects:
// very psuedo-codey
Message* MessageFactory::CreateMessage(InputStream& stream)
{
char header = stream.ReadByte();
switch (header) {
case MessageOne::Header:
return new MessageOne(stream);
case MessageTwo::Header:
return new MessageTwo(stream);
// etc.
}
}
The problem with this is that I'm lazy and don't like to write class names in two places!
In C #, I would do this with some thought about using the factory first (bonus question: what's OK using reflection, right?), But since C ++ lacks reflection, this doesn't work. I was thinking about using some kind of registry so that messages are logged with a factory on startup, but this is hampered by a non-deterministic (or at least implementation-specific) static initialization order task .
So the question is, is it possible to implement this type of factory in C ++ while observing the open / closed principle and how?
EDITOR: Apparently I overdid it. I asked this question as "how would you do this in C ++" since it is really easy to do with reflection in other languages.
a source to share
I think the open / closed and DRY approach are good principles. But they are not sacred. The goal should be to make the code reliable and maintainable. If you have to do unnatural things to stick to O / C or DRY, you can simply make your code unnecessary more complex, without material gain.
Here is something I wrote a few years ago about how I make these decisions.
a source to share
You can convert the classes that create messages (MessageOne, MessageTwo ...) to message factories and register them with the top-level MessageFactory upon initialization.
The factory message can contain a Map MessageX :: Header → instance of the form MessageXFactory.
In CreateMessage, you will find a MessageXFactory instance based on the message header, get a reference to the MessageXFactory, and then call a method that will return an instance of the actual MessageX.
With new messages, you no longer need to change the "switch", you just need to add an instance of the new MessageXFactory to the TopMessageFactory.
Example:
#include <iostream>
#include <map>
#include <string>
using namespace std;
struct Message
{
static const int id = 99;
virtual ~Message() {}
virtual int msgId() { return id; }
};
struct NullMessage : public Message
{
static const int id = 0;
virtual int msgId() { return id; }
};
struct MessageOne : public Message
{
static const int id = 1;
virtual int msgId() { return id; }
};
struct MessageTwo : public Message
{
static const int id = 2;
virtual int msgId() { return id; }
};
struct MessageThree : public Message
{
static const int id = 3;
virtual int msgId() { return id; }
};
struct IMessageFactory
{
virtual ~IMessageFactory() {}
virtual Message * createMessage() = 0;
};
struct MessageOneFactory : public IMessageFactory
{
MessageOne * createMessage()
{
return new MessageOne();
}
};
struct MessageTwoFactory : public IMessageFactory
{
MessageTwo * createMessage()
{
return new MessageTwo();
}
};
struct TopMessageFactory
{
Message * createMessage(const string& data)
{
map<string, IMessageFactory*>::iterator it = msgFactories.find(data);
if (it == msgFactories.end()) return new NullMessage();
return (*it).second->createMessage();
}
bool registerFactory(const string& msgId, IMessageFactory * factory)
{
if (!factory) return false;
msgFactories[msgId] = factory;
return true;
}
map<string, IMessageFactory*> msgFactories;
};
int main()
{
TopMessageFactory factory;
MessageOneFactory * mof = new MessageOneFactory();
MessageTwoFactory * mtf = new MessageTwoFactory();
factory.registerFactory("one", mof);
factory.registerFactory("two", mtf);
Message * msg = factory.createMessage("two");
cout << msg->msgId() << endl;
msg = factory.createMessage("one");
cout << msg->msgId() << endl;
}
a source to share
I answered in another question about C ++ factories. See there if flexible factory is interesting. I am trying to describe the old way from ET ++ to use macros that have worked great for me.
The method is macro-based and easily extensible.
ET ++ was a project to port the old MacApp to C ++ and X11. In an attempt at this, Eric Gamma, etc. Started thinking about Design Patterns
a source to share
First, your system is not that open since you include an 8-bit char, so your messages will not exceed 256; -)
Just as a joke, this is a situation where I would use a little boilerplate factory class (stateless if you put your message type char in a non-class template arg, or just that char as a state) that takes your stream & and does a new one on its template T argument by passing the stream & and return it. You will need a small registrar class to declare as static with a global scope and register a concrete T-instance of the factory (via an abstract base class pointer) with a manager (we have a generic one that accepts a "factory domain"). In your case, I would not use a map, but directly a 256-segment array to put the factory_base *.
You have a factory structure in place, it is simple and reusable. --DD
a source to share