2009-11-16 8 views
2

Je travaille sur un jeu et j'essaie d'implémenter une manière intelligente de créer des objets npc en C++ à partir de l'analyse d'un fichier texte.Usine générique en C++

Actuellement, ceci est codé en dur dans un objet Factory. Comme ceci:

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+"'."; 
} 

Ceci est à mon avis une façon très moche de le faire. Comme il (entre autres choses) casse le Open/Closed principle.

Je suis formé en Java, et en Java je ferais quelque chose comme si chaque IActor signalait son nom de classe et son type de classe à ActorFactory au début de l'exécution du programme. L'usine stockera alors la relation dans une carte et pourra alors facilement rechercher quelle chaîne correspond à quel objet et l'instancier facilement.

Editer: Je voudrais aussi avoir la possibilité d'appeler le constructeur avec un nombre/type variable d'arguments.

Comment cela se ferait-il en C++? Peut-il être fait?

Répondre

0

Vous pouvez utiliser une carte pour stocker des pointeurs de fonction qui retournent Actor *, avec un pointeur vers l'objet en cours de création. donc alors le code serait juste

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

(s'il vous plaît excuser les fiascos possibles que j'ai fait avec les pointeurs de fonction, ils ne sont pas mon point fort)

+0

Merci pour la réponse, mais cela ne me permet pas d'avoir une liste de paramètres variables (quantité différente de paramètres et de types différents) –

3

En C++, vous utilisez généralement la Fabrique Abstraite design pattern. Le point est: "la décision sur le type d'acteur à créer ne devrait pas être la responsabilité de ActorFactory::create()." Dans votre cas, cette méthode ne devrait pas décider quelle classe instancier en fonction d'une chaîne mais plutôt s'appuyer sur un type; ce type est la classe de fabrique réelle.

  1. Chaque classe acteur a sa propre classe d'usine: TrollFactory, DragonFactory, etc. provenant d'une classe de base ActorFactory2 (2 arrière parce ActoryFactory est déjà pris);

  2. Chaque classe de fabrique spécialisée implémente une méthode virtuelle create() sans paramètre renvoyant un pointeur vers une classe d'acteur nouvellement créée;

  3. Si vous avez besoin de paramètres pour construire un acteur, de les transmettre à l'objet usine avant créer l'acteur: les passer dans le cteur et les stocker en tant que variables membres; create() va les récupérer plus tard lors de la création de l'acteur; Ainsi, vous pouvez facilement passer différents arguments pour différents acteurs et votre mécanisme d'usine sera évolutif (un pas vers le principe Ouvert/Fermé);

  4. Maintenant, ActorFactory::create() accepte un pointeur vers un objet provenant de ActorFactory2 et appelle la méthode ActorFactory2::create(): il va créer l'acteur demande avec les arguments appropriés sans instruction switch.

    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

Cela a du sens, mais générerait beaucoup de classes (s'il y a beaucoup d'acteurs). –

+0

L'avantage est que vous pouvez passer un nombre variable d'arguments aux cteurs. –

+1

Je ne vois pas beaucoup de différences entre la méthode d'usine et le modèle d'usine abstrait. Parce que nous avons encore besoin de connaître la classe concrète avant de l'utiliser. Dans mon cas, j'ai besoin d'instancier ma base de classe sur le nom dans le fichier de configuration. –

1

J'ai répondu à une autre question sur les usines SO C++. S'il vous plaît voir there si une usine flexible est d'intérêt. J'essaie de décrire une ancienne façon d'utiliser ET ++ pour utiliser des macros qui ont bien fonctionné pour moi.

ET++ était un projet pour le port de MacApp ancien à C++ et X11. Dans l'effort de cela, Eric Gamma etc a commencé à penser à Design Patterns

+0

Je considérerais cela comme un hack. Mais ça fait exactement ce que je veux. –

1

Le terme spécifique est: méthode d'usine paramétrée et fait partie du modèle de conception de la méthode d'usine.

Pour utiliser une fabrique générique, maintenez les classes dans une carte et accédez via une chaîne. Si vos noms de classe sont utilisables, enregistrez la classe à l'usine avec "typeid (MyClass) .name() et renvoyez une copie de la classe en fournissant une fonction membre clone()

Cependant, pour simplifier ne pas étendre usines, j'utilise l'approche de votre question

Je ne peux pas répondre à votre question sur le passage de paramètres plus variables, mais pour désérialiser, il suffit de passer la partie à la classe et de la laisser se désérialiser (comme vous l'avez déjà fait) semble faire)