在我正在使用C ++编写的项目中,我需要为消息创建对象,因为它们通过网络传入。我目前正在使用factory method pattern来隐藏对象的创建:
// 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.
}
}
我遇到的问题是我很懒,不喜欢在两个地方写这些课程的名字!
在C#中,我会对首次使用工厂进行一些反思(奖励问题:这可以正确使用反射,对吗?)但是由于C ++没有反射,所以这不在桌面上。我考虑过使用某种类型的注册表,以便消息在启动时将自己注册到工厂,但non-deterministic (or at least implementation-specific) static initialization order problem阻碍了这一点。
所以问题是,是否有可能在尊重开放/封闭原则的同时用C ++实现这种类型的工厂,以及如何实现?
编辑:显然我正在思考这个问题。我打算将这个问题作为“你将如何在C ++中实现这一点”,因为在其他语言中使用反射非常容易。答案 0 :(得分:2)
我认为开放/封闭的方法和DRY是很好的原则。但他们并不神圣。目标应该是使代码可靠和可维护。如果您必须执行不自然的行为以遵守O / C或DRY,那么您可能只是简单地使您的代码变得更加复杂而没有任何实质性好处。
几年前,something I wrote是关于如何进行这些判断的。{/ p>
答案 1 :(得分:1)
您可以将创建消息的类(MessageOne,MessageTwo ...)转换为消息工厂,并在初始化时使用顶级MessageFactory注册它们。
消息工厂可以保存MessageX :: Header的地图 - > MessageXFactory类映射的实例。
在CreateMessage中,您将找到基于消息头的MessageXFactory实例,检索对MessageXFactory的引用,然后调用它将返回实际MessageX实例的方法。
使用新消息,您不再需要修改'switch',您只需要向TopMessageFactory添加一个新的MessageXFactory实例。
示例:
#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;
}
答案 2 :(得分:1)
您无需让代码同时遵循所有可能的原则。目标应该是尽可能多地遵守这些范式,而不是更多。不要过度设计您的解决方案 - 否则您最终可能会得到意大利面条代码。
答案 3 :(得分:1)
我在另一个关于C ++工厂的问题中回答过。如果您感兴趣的是灵活的工厂,请参阅there。我尝试用ET ++描述一种旧方法来使用对我来说很有用的宏。
该方法基于宏,易于扩展。
ET++是一个将旧MacApp移植到C ++和X11的项目。为此,Eric Gamma等开始考虑设计模式
答案 4 :(得分:0)
首先,你的系统不是那么开放,因为你打开一个8位字符,所以你的消息类型数不会超过256; - )
只是开玩笑说,这是一种情况我会使用一个模板化的工厂类(无状态,如果你将你的char消息类型放在非类模板arg中,或只有那个char作为状态)接受你的流&amp;并在其T模板arg上传递新内容,传递流和&amp;并返回它。您需要一个小注册器类来声明具有全局范围的静态,并将一个具体的T实例化工厂(通过抽象基类指针)注册到一个管理器(我们有一个带有“工厂域”键的通用工厂) 。在你的情况下,我不会使用地图,而是直接使用256“slot”数组来放置factory_base *。
你有一个工厂框架,它很容易重复使用。 --DD