2009-12-02 2 views
4

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

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

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

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

+0

Ваша идея с хэш-таблицы с указателями на функции звучат очень похоже на заводе мне. –

ответ

7

Вот общая factory example реализация:

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, но вам может понравиться упомянутый method gf (это просто, понятно и очень полезно, и да, это преодолевает присущие проблемы с макросами в этом случае).

Вот simple example завода:

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

Почему вы определяете его как структуру? Есть ли причина ? –

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(); 
} 
+0

Да, я также обычно начинаю с чего-то подобного - класса с похожим методом «создать». Тогда, если есть много классов (или пользователь библиотеки должен добавить некоторые), я добавляю за собой «истинную» фабрику (карту, хеш, что угодно) с регистрациями. – MaR

+0

Может быть, я должен прочитать о заводской схеме ... Я не помню, как это реализовано. – Idan

1

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

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

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

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

2

Вы также можете посмотреть в класс подталкивания factory реализации.

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

Вот простой пример, использующий метод подталкивания фабрики и регистрации класса:

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

Привет, я пробовал это решение, но не могу его скомпилировать. Проблема у меня на линии 'фабрики [" Child1 "] = boost :: factory ();' Есть проблема с typecheck, равенство не определено. Если я напишу 'boost :: factory ();' он работает, но это не полезно. Я что-то пропустил? – Alexandre

+0

Хм, моя ошибка, я не скопировал код правильно ... – Alexandre

Смежные вопросы