2009-11-16 3 views
2

Я работаю над игрой и пытаюсь реализовать умный способ создания npc-объектов в C++ для разбора текстового файла.Generic factory in C++

В настоящее время это жесткий код в Factory-объекте. Например:

IActor * ActorFactory::create(string actortype, Room * r, string name, int hp) 
{ 
    if(actortype == "Troll") 
    { 
     return new Troll(r, name, hp); 
    } 
    if (actortype == "Dragon") 
    { 
     return new Dragon(r, name, hp); 
    } 
    // ... and so on 
    throw "Can't recognize type '"+actortype+"'."; 
} 

Это, на мой взгляд, очень уродливый способ сделать это. Так как он (между прочим) разбивает Open/Closed principle.

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

Редактировать: Мне также хотелось бы иметь возможность вызвать конструктор с переменным числом/типом аргументов.

Как это сделать на C++? Это можно сделать?

ответ

0

Вы можете использовать карту для хранения указателей функций, которые возвращают Actor *, указав на объект, который создается. так, то код будет просто

std::map<std::string,IActor* (*) (Room*,std::string,int)> constructorMap  
constructorMap["Troll"]=&TrollConstructor 
//etc... 
IACtor* ActorFactory::create(string actortype,Room* r,string name,int hp){ 
    return (*constructorMap[actortype])(r,name,hp); 
} 

(пожалуйста, простите любые возможные винт окна, которые я сделал с указателями на функции, они не мой конек)

+0

Спасибо за ответ, но это не позволяет мне иметь список переменных параметров (различное количество параметров и разные типы) –

3

В C++, вы, как правило, использовать Abstract Factory дизайн рисунок.

Дело в том, что «решение о типе создаваемого актера не несет ответственности за ActorFactory::create()». В вашем случае этот метод не должен определять, какой класс следует создавать на основе строки, но скорее полагаться на тип; этот тип фактический завод класс.

  1. Каждый класс актер имеет свой собственный класс фабрики: TrollFactory, DragonFactory и т.д., производный от базового класса ActorFactory2 (2 заднего потому ActoryFactory уже принято);

  2. Каждый специализированный заводский класс реализует виртуальный метод create() без параметра, возвращающего указатель на вновь созданный класс актера;

  3. Если вам нужны параметры для создания актера, передайте их на заводский объект до, создав актера: передайте их в ctor и сохраните их в качестве переменных-членов; create() будет получать их позже после создания актера;

  4. Таким образом, вы можете легко передать разные аргументы для разных участников, и ваш заводский механизм будет масштабируемым (шаг к принципу Open/Closed);

  5. Теперь ActorFactory::create() принимает указатель на объект вытекающего из ActorFactory2 и вызывает метод ActorFactory2::create(): он будет создавать требуемый актер с соответствующими аргументами без переключателя заявления.

    class ActorFactory2 
    { 
        string m_name; // Each IA actor has a name 
        int m_hp;  // and some HP 
    public: 
        ActorFactory2(const string &p_name, int p_hp) 
        : m_name(p_name), m_hp(p_hp) {} 
        virtual IActor * create() const = 0; 
    }; 
    
    class TrollFactory : public ActorFactory2 
    { 
        // No special argument needed for Troll 
    public: 
        TrollFactory(const string &p_name, int p_hp) 
        : ActorFactory2(p_name, p_hp) {} 
        virtual IActor * create() const { return new Troll(m_name, m_hp); } 
    }; 
    
    class DragonFactory : public ActorFactory2 
    { 
        FlameType m_flame; // Need to indicate type of flame a dragon spits 
    public: 
        DragonFactory(const string &p_name, int p_hp, const FlameType &p_flame) 
        : ActorFactory2(p_name, p_hp) 
        , m_flame(p_flame) {} 
        virtual IActor * create() const { return new Dragon(m_name, m_hp, m_flame); } 
    }; 
    
    IActor * ActorFactory::create(const ActorFactory2 *factory) 
    { 
        return factory->create(); 
    } 
    
    int main(int, char **) 
    { 
        ActorFactory af; 
        ... 
        // Create a dragon with a factory class instead of a string 
        ActorFactory2 *dragonFactory = new DragonFactory("Fred", 100, RedFlames); 
        IActor *actor = af.create(dragonFactory); // Instead of af.create("dragon", ...) 
        delete dragonFactory; 
    } 
    
+0

Это имеет смысл, но будет генерировать много классов (если есть много актеров). –

+0

Преимущество в том, что вы можете передавать переменное количество аргументов в ctors. –

+1

Я не вижу много различий между заводским методом или абстрактным шаблоном завода. Потому что нам еще нужно знать конкретный класс, прежде чем мы его используем. В моем случае мне нужно создать экземпляр базы классов в имени в файле конфигурации. –

1

я ответил в другой SO вопрос о фабриках C++. Пожалуйста, см. there, если интересна гибкая фабрика. Я пытаюсь описать старый путь из ET ++, чтобы использовать макросы, которые отлично поработали для меня.

ET++ был проектом по переносу старого MacApp на C++ и X11. В попытке этого Эрик Гамма и т. Д. Начал думать о Шаблоны проектирования

+0

Я бы подумал, что это взломать. Но это определенно делает именно то, что я хочу. –

1

Конкретный термин: параметризованный заводский метод и является частью шаблона проектирования фабричного метода.

Чтобы использовать общий завод, удерживайте классы на карте и получайте доступ через строку. Если имена классов пригодны для использования, зарегистрируйте класс на заводе с помощью «typeid (MyClass) .name() и верните копию класса, предоставив функцию-член clone().

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

Я не могу ответить на ваш вопрос о передаче более переменных параметров, но для десериализации это достаточно, чтобы передать часть в класс и позволить ей десериализовать себя (поскольку вы уже