使用工厂模式按名称实例化类

4
假设我有一个类列表,它们都继承自Base。
我从用户那里以字符串形式获取类名,并希望实例化正确的类并返回指向Base的指针。您将如何实现这一点?
我考虑使用哈希表,其中类名作为键,函数指针指向实例化正确类并返回Base *的函数。
然而,我认为我可能可以在这里使用工厂模式,使其更加简单,但我只是记不太清楚了,所以我想请教建议。

你使用哈希表和函数指针的想法听起来很像一个工厂。 - Andreas Brinck
4个回答

7
这是一个通用的工厂示例实现:链接
template<class Interface, class KeyT=std::string>
struct Factory {
    typedef KeyT Key;
    typedef std::auto_ptr<Interface> Type;
    typedef Type (*Creator)();

    bool define(Key const& key, Creator v) {
        // Define key -> v relationship, return whether this is a new key.
        return _registry.insert(typename Registry::value_type(key, v)).second;
    }
    Type create(Key const& key) {
        typename Registry::const_iterator i = _registry.find(key);
        if (i == _registry.end()) {
            throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) +
                                        ": key not registered");
        }
        else return i->second();
    }

    template<class Base, class Actual>
    static
    std::auto_ptr<Base> create_func() {
        return std::auto_ptr<Base>(new Actual());
    }

private:
    typedef std::map<Key, Creator> Registry;
    Registry _registry;
};

这并不意味着在任何情况下都是最佳选择,但它旨在成为第一个近似值,比手动实现stijn提到的函数类型更有用。每个层次结构应该如何注册自己并不是Factory所规定的,但你可能会喜欢gf提到的方法(它简单、清晰、非常有用,并且是克服此情况中宏固有问题的方法)。
这里有一个简单的示例工厂:
struct Base {
    typedef ::Factory<Base> Factory;
    virtual ~Base() {}
    virtual int answer() const = 0;

    static Factory::Type create(Factory::Key const& name) {
        return _factory.create(name);
    }
    template<class Derived>
    static void define(Factory::Key const& name) {
        bool new_key = _factory.define(name,
            &Factory::template create_func<Base, Derived>);
        if (not new_key) {
            throw std::logic_error(std::string(__PRETTY_FUNCTION__) +
                                   ": name already registered");
        }
    }

private:
    static Factory _factory;
};
Base::Factory Base::_factory;

struct A : Base {
    virtual int answer() const { return 42; }
};

int main() {
    Base::define<A>("A");
    assert(Base::create("A")->answer() == 42);
    return 0;
}

你为什么将它定义为结构体?有什么原因吗? - Gabor Forgacs

3

在很多领域,最快且非常实用的方式可能是类似于以下内容:

Base* MyFactoryMethod( const std::string& sClass ) const
{
  if( sClass == "A" )
    return CreateNewA();
  else if( sClass == "B" )
    return new CreateClassB();
  //....
  return 0;
}

A* CreateClassA() const
{
  return new A();
}

是的,我通常也会从类似的东西开始 - 带有相似“创建”方法的类。然后,如果有很多类(或者库用户应该添加一些),我会在其后面添加“真正”的工厂(映射、哈希等)并进行注册。 - MaR
也许我应该读一下关于工厂模式的内容……我已经记不清它是如何实现的了。 - Idan

2
您可以查看Boost类工厂实现。
  • 如果只有几个派生类,可以使用“if,else”列表。
  • 如果计划有许多派生类,则最好解决类注册过程(如Georg所述),而不是使用“if,else”列表。

这是一个使用Boost工厂方法和类注册的简单示例:

typedef boost::function<Parent*()> factory;

// ...

std::map<std::string, factory> factories;

// Register derived classes
factories["Child1"] = boost::factory<Child1*>();
factories["Child2"] = boost::factory<Child2*>();

// ...

// Instantiate chosen derived class
auto_ptr<Parent> pChild = auto_ptr<Parent>(factories["Child1"]());

1
嗨,我尝试了这个解决方案,但无法编译。我的问题出在factories["Child1"] = boost::factory<Child1*>();这一行。存在类型检查问题,等式未定义。如果我写boost::factory<Parent*>();它可以工作,但没有用处。我错过了什么吗? - Alexandre

1

首先,是的,这正是工厂模式的用途。
(顺便说一句,你的另一个想法是工厂模式的一种可能实现方式)

如果您打算在大型项目中使用此方法(如果不是,请使用stijns answer),您可能希望考虑在某个地方使用关联容器,而不是显式分支,甚至将注册责任移动到类中

  • 避免在一个额外的地方(即您的工厂)进行代码更改
  • 进而避免添加类时可能非常长的重新编译时间(对于头文件实现
为了方便地在类中进行注册,您可以使用类似于this suggestion的东西,并向实例化派生类并返回指向基类的指针的条目添加函数指针或functor
如果您不害怕宏,那么您只需向其声明添加一个微小的宏即可将类添加到工厂中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接