4

我正在保存并重新加载一堆不同的对象,这些对象都从一个公共基础派生到一个文件中,显然我需要存储类名(或类似的名称)以便在重新加载时创建正确的对象类型。

保存很容易:

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:上面的代码并没有试图成为最终工厂模式的完美表示。我不关心基类和派生类之间不需要的链接,或者扩展层次结构的潜在困难。我正在寻找可以简化代码而不是使其复杂化的答案。

4

6 回答 6

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>. (如上所述,这必须是静态成员。)

于 2012-09-17T13:27:45.163 回答
3

这是你能做的最好的事情,你可以通过将它包装if在一个工厂类中来清理它。

于 2012-09-17T12:26:15.820 回答
2

您可以使用工厂方法(AKA 虚拟构造函数) 这在设计模式一书和 Internet 上的许多地方都有说明——您可以在 Google 上搜索这些术语。StackOverflow 中可能已经对此进行了讨论。

于 2012-09-17T12:30:13.173 回答
2

您显然需要工厂模式。在此处阅读更多信息 http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

于 2012-09-17T12:26:51.300 回答
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)();
}
于 2012-09-17T13:13:38.877 回答
0

我遇到了同样的问题,并在这篇文章中找到了一个非常简洁的答案: Industrial Strenght Pluggable Factories Close enough to James Kanze's answer,但我建议你阅读这篇文章并自己尝试一下。

于 2012-09-18T13:09:53.083 回答