现在看来,这个问题是我们的工作相当普遍。
我们我们发送通过网络int或枚举值,那么我们接受它,我们想创建/调用特定的对象/功能。
最简单的解决办法是使用switch语句,象下面这样:
switch (value) {
case FANCY_TYPE_VALUE: return new FancyType();
}
它工作正常,但我们有足够的这些开关块,当我们创造新的价值和类型,我们就需要改变他们。 它似乎正确。
另一种可能是使用的模板。 但是,我们不能,因为枚举值在运行时被定义。
是否有任何正确的设计模式,或者任何正确的方法?
这似乎是在每天编码非常普遍和常见的问题...
实际上,你可以使用一些模板诡计做到这一点:
#include <map>
template <typename Enum, typename Base>
class EnumFactory {
public:
static Base* create(Enum e) {
typename std::map<Enum,EnumFactory<Enum,Base>*>::const_iterator const it = lookup().find(e);
if (it == lookup().end())
return 0;
return it->second->create();
}
protected:
static std::map<Enum,EnumFactory<Enum,Base>*>& lookup() {
static std::map<Enum,EnumFactory<Enum,Base>*> l;
return l;
}
private:
virtual Base* create() = 0;
};
template <typename Enum, typename Base, typename Der>
class EnumFactoryImpl : public EnumFactory<Enum,Base> {
public:
EnumFactoryImpl(Enum key)
: position(this->lookup().insert(std::make_pair<Enum,EnumFactory<Enum,Base>*>(key,this)).first) {
}
~EnumFactoryImpl() {
this->lookup().erase(position);
}
private:
virtual Base* create() {
return new Der();
}
typename std::map<Enum,EnumFactory<Enum,Base>*>::iterator position;
};
这可以让你从一个给定的创建一个新的派生类对象enum
,说
// will create a new `FancyType` object if `value` evaluates to `FANCY_TYPE_VALUE` at runtime
EnumFactory<MyEnum,MyBase>::create(value)
但是,你必须有一些EnumFactoryImpl的对象,这可能是在某些功能或命名空间的静态。
namespace {
EnumFactoryImpl<MyEnum,MyBase,Derived1> const fi1(ENUM_VALUE_1);
EnumFactoryImpl<MyEnum,MyBase,Derived2> const fi2(ENUM_VALUE_2);
EnumFactoryImpl<MyEnum,MyBase,Derived3> const fi3(ENUM_VALUE_3);
EnumFactoryImpl<MyEnum,MyBase,FancyType> const fi1(FANCY_TYPE_VALUE); // your example
}
这些行是单点源代码映射enum
值派生类型。 所以,你必须在同一地点的一切,并没有冗余(这消除了忘记去改变它在一些地方,添加新的派生类型时的问题)。
尝试地图:
struct Base { };
struct Der1 : Base { static Base * create() { return new Der1; } };
struct Der2 : Base { static Base * create() { return new Der2; } };
struct Der3 : Base { static Base * create() { return new Der3; } };
std::map<int, Base * (*)()> creators;
creators[12] = &Der1::create;
creators[29] = &Der2::create;
creators[85] = &Der3::create;
Base * p = creators[get_id_from_network()]();
(当然,这是真正的原油;至少,你得错误检查,以及每类自登记制度,所以你不能忘记注册类)。
一种选择是保持创作者的一个字典(其具有相同的接口),可以创建一个具体类型。 现在创建代码将在字典int值(从客户端发送的枚举得到的)搜索并调用创建方法,该方法通过基类指针返回具体对象。
字典可在一个地方与对应于每个可能的枚举值的具体的创作者被初始化。
这里的问题是,你有当你添加一个新类型的对象扩展这个字典初始化代码。 避免的方法是如下。
- 让创作者寻找一个单工厂实例,并与类型枚举(整数),用它可以创建一个形而下对象的构造注册。
- 创建一个创作者/集的DLL和有创的全局实例。
- 该DLL的名称可以在由厂方在初始化时读取配置文件进行输入。 工厂负荷在这个文件中的所有DLL文件,这导致创建与工厂注册自己的静态对象。
- 现在工厂已全部枚举类型,它可以与具体对象创建立的地图。
- 同样的对象创建者查找机制,实现创建对象。
现在,工厂并不需要,因为在所有的步骤3,4和5再延长引入了新的对象不会改变。 步骤1可以在一个地方被实现。
你唯一需要做的事情是增加一个全局对象为每个新的具体类型应该是有因为C ++不支持原生反思的。
古特,我不建议这作为一个答案,但既然你问我对我在你原来的问题意见扩大这里是什么样的.NET环境中为您提供了一个非常简短的总结...
public enum MyEnum
{
[MyAttribute(typeof(ClassNone))]
None,
[MyAttribute(typeof(ClassOne))]
One,
[MyAttribute(typeof(ClassTwo))]
Two,
[MyAttribute(typeof(ClassThree))]
Three
}
所以,你有你的基本枚举一,二,三等等这些作品就像....呃....枚举!
但你也编写了一个名为MyAttribute类(其实在这方面更多的信息,只需搜索属性)。 但是你可以看到这个让你说,在设计时,这样,和这样一个枚举值与某某,这样的类关联。
此信息存储在枚举的元数据(托管环境的价值!),并可以在运行时(使用反射)进行询问。 不用说,这是非常强大的,我用这个机制来系统地剔除其他回答您的问题提出的一种映射的负荷。
实用性的一个例子是这样的......在一个客户端和我一起工作,该公约是状态存储为理由数据库字符串,他们将更具可读性谁需要运行一个表查询人。 但是本作中得到应用,其中状态是通过推为枚举没有任何意义。 采取上述方法(用字符串,而不是一种类型的),并且该变换上的一行代码发生作为数据读取和写入。 另外,当然,一旦你定义MyAttribute可以标记到你喜欢的任何枚举。
我的语言,如果选择这些天是C#,但这也将是(管理)良好的C ++。