Reputation: 12578
In a project I'm working on in C++, I need to create objects for messages as they come in over the wire. I'm currently using the 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 I have with this is that I'm lazy and don't like writing the names of the classes in two places!
In C# I would do this with some reflection on first use of the factory (bonus question: that's an OK use of reflection, right?) but since C++ lacks reflection, this is off the table. I thought about using a registry of some sort so that the messages would register themselves with the factory at startup, but this is hampered by the non-deterministic (or at least implementation-specific) static initialization order problem.
So the question is, is it possible to implement this type of factory in C++ while respecting the open/closed principle, and how?
EDIT: Apparently I'm overthinking this. I intended this question to be a "how would you do this in C++" since it's really easy to do with reflection in other languages.
Upvotes: 1
Views: 542
Reputation: 79810
You could convert classes that create messages (MessageOne, MessageTwo ...) into message factories and register them with top level MessageFactory on initialization.
Message factory could hold map of MessageX::Header -> instance of MessageXFactory kind of map.
In CreateMessage you would find instance of MessageXFactory based on message header, retrieve the reference to MessageXFactory and then call it's method that would return instance of the actual MessageX.
With new messages you no longer have to modify the 'switch', you just need to add an instance of 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;
}
Upvotes: 1
Reputation: 46051
I have answered in another SO question about C++ factories. Please see there if a flexible factory is of interest. I try to describe an old way from ET++ to use macros which has worked great for me.
The method is macro based and are easily extensible.
ET++ was a project to port old MacApp to C++ and X11. In the effort of it Eric Gamma etc started to think about Design Patterns
Upvotes: 1
Reputation: 1882
First, your system is not so open ended, since you switch on an 8-bit char, so your message type count won't exceed 256 ;-)
Just joking apart, this is a situation I'd use a little templated factory class (stateless if you put your char message type in a non-class template arg, or with just that char as state) that accepts your stream& and does the new on its T template arg, passing the stream& and returning it. You'll need a little registrar class to declare as static with global scope, and register the concrete T-instantiated factory (via an abstract base class pointer) with a manager (we have a generic one that takes a "factory domain" key). In your case I wouldn't use a map but directly an 256 "slot" array to put the factory_base* in.
One you have the factory framework in place, it's easy and reusable. --DD
Upvotes: 0
Reputation: 111150
You do not need to make your code follow every possible principle simultaneously. The aim should be to stick to as many of those paradigms as possible and no more. Do not over-engineer your solution -- you are likely to end up with spaghetti code otherwise.
Upvotes: 1
Reputation: 7493
I think that the open/closed approach and DRY are good principles. But they are not sacred. The goal should be making the code reliable and maintainable. If you have to perform unnatural acts to adhere to O/C or DRY, then you may simply be making your code needlessly more complex with no material benefit.
Here is something I wrote a few years ago on how I make these judgment calls.
Upvotes: 2