我正在保存并重新加载从公共基础派生的一堆不同对象到文件,显然我需要存储类名(或类似的东西),以便在重新加载时创建正确的对象类型。 / p>
保存很简单:
class Base
{
virtual string className() const = 0;
void saveToFile()
{
write(className());
... other writing stuff
}
}
class Derived1: public Base
{
string className() const { return "Derived1"; };
...
}
class Derived2: public Base
{
string className() const { return "Derived2"; };
...
}
并且,如果你不介意重复字符串,加载很容易......
static Base * Base::factory(const String &cname)
{
if (cname == "Derived1")
return new Derived1;
else if (cname == "Derived2")
return = new Derived2;
else ...
}
void load()
{
String cname = readString();
Base * obj(Base::factory(cname);
obj->readIt();
}
但是,重复的字符串会冒犯我的DRY感觉:理想情况下,className()
可能是static virtual
,但这是不允许的。我有一种感觉,我错过了一个明显的“清洁”方式,但我还没有看到它。有什么建议吗?
注意:好的,使用工厂方法略微调整代码。请注意,这实际上并没有解决问题!
注意#2:上面的代码并不是最终工厂模式的完美表现。我不关心基类和派生类之间的不必要的链接,或者扩展层次结构的潜在困难。我正在寻找能够简化代码而不是使代码复杂化的答案。
答案 0 :(得分:3)
关于你能做的最好的事情,你可以通过将if
包裹在工厂类中来清理它。
答案 1 :(得分:3)
这里有两件事。首先,要避免两次写出名称, 我过去曾经使用过以下内容:
class Derived : public Base
{
// ...
static char const* className() { return "Derived"; }
virtual char const* getClassName() const { return className(); }
// overrides virtual function in the base class...
};
此外,如果您希望能够从外部读取该类
来源,你需要某种静态工厂功能,其中
使用这些功能的地图注册自己。我会继续这样做
在Base
:
class Base
{
// ...
protected:
class Factory
{
protected:
Factory( std::string const& type );
public:
virtual Base* constructFromFile( std::istream const& source ) const = 0;
};
typedef std::map <std::string, Factory const*> FactoryMap;
static FactoryMap& factories();
template <typename Derived>
class ConcreteFactory : public Factory
{
public:
ConcreteFactory() : Factory( Derived::className() ) {}
virtual Base* constructFromFile( std::istream const& source ) const
{
return new Derived( source );
}
};
public:
static Base* readFromFile( std::istream& source );
};
Base::FactoryMap&
Base::factories()
{
static FactoryMap theOneAndOnly;
return theOneAndOnly;
}
Base::Factory::Factory( std::string const& type )
{
std::pair <FactoryMap::iterator, bool> results
= factories().insert( std::make_pair( type, this ) );
assert (results.second);
}
Base* Base::readFromFile( std::istream& source )
{
std::string type = readType( source );
FactoryMap::const_iterator factory = factories().find( type );
if ( factory == factories().end() ) {
throw UnknownType(...);
}
return factory->second->constructFromFile( std::istream& source );
}
最后,对于每个派生类,您必须定义构造函数
取std::istream&
和静态实例
Base::ConcreteFactory <Derived>
。 (如上所述,这必须是一个
静态成员。)
答案 2 :(得分:2)
您显然需要 Factory 模式。 在这里阅读更多 http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus
答案 3 :(得分:2)
您可以使用工厂方法(AKA虚拟构造函数)这在“设计模式”一书中以及整个互联网的许多地方进行了解释 - 您可以使用这些术语进行Google搜索。 StackOverflow中可能已经讨论过这个问题了。
答案 4 :(得分:2)
Id'将字符串映射到函数,创建类:
std::hash_map<std::string, std::function<Base*()> creators;
然后,您可以创建填充地图的功能
template <typename T> void add()
{
creators.insert(std::pair(T::class_name(), []()-> Base* { return new T(); }));
}
用法很简单:
//factory constructor
add<Derived1>();
add<Derived2>();
//creation
Base* r = 0;
auto it = creators.find(string);
if (it != creators.end()) {
r = (*it)();
}
答案 5 :(得分:0)
我遇到了同样的问题,在本文中找到了一个非常简洁的答案: Industrial Strenght Pluggable Factories 足够接近詹姆斯坎泽的答案,但我建议你阅读本文并亲自尝试。