Создание классов по имени с фабричным образцом - PullRequest
4 голосов
/ 02 декабря 2009

Предположим, у меня есть список классов A, B, C, ..., которые все наследуются от Base.

Я получаю имя класса в виде строки от пользователя и хочу создать экземпляр нужного класса и вернуть указатель на Base. Как бы вы это реализовали?

Я думал об использовании хеш-таблицы с именем класса в качестве ключа и указателем на функцию, которая создает экземпляр нужного класса и возвращает Base *.

Тем не менее, я думаю, что я мог бы использовать здесь фабричный шаблон и сделать его намного проще, но я просто не совсем хорошо помню его, поэтому я, хотя и попросил бы предложения.

Ответы [ 4 ]

7 голосов
/ 03 декабря 2009

Вот общий фабричный пример реализация:

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;
};

Это не означает, что это лучший в каждом случае, но он предназначен для первого приближения и более полезного значения по умолчанию, чем ручная реализация типа функции, упомянутой выше. То, как каждая иерархия должна регистрироваться самостоятельно, не является обязательным для 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;
}
3 голосов
/ 02 декабря 2009

самый быстрый, но очень полезный способ во многих областях, будет что-то вроде

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();
}
2 голосов
/ 05 января 2011

Вы также можете взглянуть на реализацию фабрики класса Boost.

  • Если есть только несколько производных классов, вы можете использовать список «если, еще».
  • Если вы планируете иметь много производных классов, лучше отсортировать процесс регистрации классов (как упомянуто Georg ), чем использовать список "если, иначе".

Вот простой пример использования фабричного метода 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 голос
/ 02 декабря 2009

Во-первых, да, это как раз то, для чего предназначен заводской шаблон.
(Кстати, ваша другая идея - возможная реализация фабричного шаблона)

Если вы собираетесь сделать это для большого проекта (если нет, просто укажите stijns answer ), вы можете рассмотреть возможность использования где-нибудь ассоциативного контейнера вместо явного ветвления и, возможно, даже перемещения регистрация ответственности в классы до

  • избегайте изменений кода в одном дополнительном месте (на вашем заводе)
  • и, в свою очередь, избегайте, возможно, очень продолжительного времени перекомпиляции (для реализаций в заголовке ) при добавлении класса

Чтобы добиться удобной регистрации в классах, вы можете использовать что-то вроде этого предложения и добавить указатель функции или функтор к записям, которые создают производный класс и возвращают указатель на база.
Если вы не боитесь макросов, вы можете добавить классы к фабрике, просто добавив крошечный макрос в его объявление.

...