2010-11-11 23 views
1

Je passe environ 2 heures à lire beaucoup de sujets liés à l'usine et je ne suis toujours pas sûr que ce serait la bonne approche.Création de classe paramétrée avec affirmation de la présence de paramètres. Utiliser l'usine?

Voici la chose: j'ai un ennemi classe qui a contient des variables membres comme nom _ et santé _. Je veux créer des instances de celui-ci avec des valeurs différentes de ces variables membres et ma première approche a consisté à obtenir les paramètres d'un tableau statique avec les propriétés:

Enemy::Enemy(int type) : name_(properties[type].name), 
          health_(properties[type].health) 
{ 
... 
} 

Le problème ici est que je ne peux pas vérifier si la tableau propriétés est déjà rempli. Je devrais le vérifier de l'extérieur en appelant une fonction Enemy :: initArray() statique mais cela annulerait l'encapsulation de la classe Enemy.

Est-ce le temps de créer une fabrique qui initialise la matrice dans son constructeur? Et puis créez ennemis avec:

Enemy* EnemyFactory::create(type); 

je lis que généralement les usines sont créées lorsque vous avez des hiérarchies de classes complexes ou l'appelant usine n'a besoin que de connaître l'interface des classes créées. J'ai seulement besoin de l'encapsulation de la création et de la vérification du tableau. Y a-t-il une solution "plus légère"?

EDIT: Je vais essayer d'être plus clair:

1.) Je pense que je sais comment pour créer une usine. La question principale est de savoir s'il existe une alternative !

2.) Je ne veux pas définir les propriétés à l'intérieur de l'objet mais utiliser des listes d'initialisation. Si je fais le premier je peux juste vérifier le tableau dans le constuctor et n'ai pas besoin de l'usine du tout.

Répondre

1

Il existe essentiellement trois options:

  1. Tout le monde est libre de spécifier le nom initial et de la santé d'un Enemy, qui accepte les deux comme paramètres dans son constructeur (public).

  2. Le constructeur de classe Enemy mappe un 'ID ennemi' aux valeurs appropriées des propriétés. Les problèmes de cette approche surviennent lorsque vous devez vérifier la validité de l'identifiant de l'ennemi et/ou la présence du mappage, tout en utilisant des listes d'initialisation pour les propriétés. Ceci est généralement résolu en ajoutant un membre factice/base-classe, qui est initialisé en appelant une fonction de vérification.
    Cela ressemblerait généralement comme ceci:

class Enemy 
    { 
     bool dummy_must_be_first; 
    public: 
     Enemy(int type) : dummy_must_be_first(validate(type), name(properties[type].name), health(properties[type].health) {} 

    private: 
     bool validate(int type) 
     { 
      // ensure properties is initialised and verify type is within range... 
      // on failure, throw an exception 

      return true; 
     } 

     string name; 
     int health; 
    }; 
  1. Vous utilisez une fonction usine pour effectuer la correspondance entre un « ID ennemi » et les propriétés qui sont utilisées pour initialiser l'objet Enemy.

Il n'est pas nécessaire d'avoir une classe de fabrique distincte. Pour cela, une méthode d'usine serait suffisante:

class Enemy 
{ 
private: 
    Enemy(string name_, int health_) : name(name_), health(health_) {} 

    string name; 
    int health; 
public: 
    static auto_ptr<Enemy> createEnemy(int type) 
    { 
     // ensure properties is initialised and verify type is within range... 

     return auto_ptr<Enemy>(new Enemy(properties[type].name, properties[type].health)); 
    } 
}; 
+0

C'est ce que je voulais. Aussi peu de code que possible tout en maintenant l'encapsulation de la classe. En fait assez facile mais je n'ai pas pensé à une méthode statique simple en tant que constructeur. Je vous remercie! – problemofficer

+0

Les solutions 2 et 3 ne sont-elles pas essentiellement les mêmes? Les deux créent une fonction factice/usine qui vérifie les paramètres et initialise la classe avec des paramètres ou avec l'initialisation des paramètres. – problemofficer

+0

@problemofficer: J'ai ajouté un exemple d'option 2, pour montrer en quoi il diffère de 3 –

0

Je vois deux possibilités.

name_(default_name_value), health_(default_health_value) puis faites l'affectation réelle dans le corps du constructeur

ou

utiliser la méthode de construction

0

Votre usine aurait une référence aux propriétés. L'ennemi devrait prendre le nom et la santé comme paramètres dans son constructeur.

Alors quelque chose comme:

Enemy* EnemyFactory::create(int type) const 
{ 
    return new Enemy(properties[type].name, properties[type].health); 
} 

Si ces propriétés ne sont pas trouvées, vous devez gérer ce cas, que ce soit en lançant un message d'erreur approprié ou permettant une valeur par défaut.

0

Suggestion 1

Que diriez-vous l'introduction d'un EnemyCreator?Puis, modifiez votre EnemyFactor pour gérer un tas d'objets EnemyCreator.

Suggestion 2

Vous dites que le nombre de définir la propriété et les types sont les mêmes, ils varient tout d'ennemi en ennemi. Une autre façon serait d'avoir une classe de base qui met en place des propriétés ennemies de manière générique (suppose _property est membre de Properties et accessible):

class EnemyProperties : public Properties 
{ 
protected: 
    virtual void initialiseImpl() = 0; 

public: 
    void initialise() 
    { 
     // set up the properties all enemies have 
     _property["healh"] = 5; 
     _property["skill"] = 0.5f; 
     _property["skillWithABanana"] = 1.0f; 

     // now let derived classes specialise 
     initialiseImpl(); 
    }; 
}; // eo class EnemyProperties 

Maintenant, nous pouvons faire des ensembles de propriétés pour un certain nombre de types et changez seulement ce que nous voulons:

class BadBananaEnemy : public EnemyProperties 
{ 
protected: 
    void initialiseImpl() 
    { 
     _property["skillWithABanana"] = 0.0f; // he's bad at this. 
    }; // eo initialiseImpl() 
}; // eo class BadBananaEnemy 

etceteras.

Vous pouvez ensuite les mettre dans une usine qui peut bomber ces par nom:

class EnemyFactory 
{ 
private: 
    typedef std::pair<std::string, EnemyProperties> EnemyPair; 
    typedef std::map<std::string, EnemyProperties> EnemyMap; 
    EnemyMap m_EnemyTypes; 

public: 
    // register a type 
    void registerType(const std::string& _name, const EnemyProperties& _prop) 
    { 
     m_EnemyTypes.insert(EnemyPair(_name, _prop)); 
    }; // eo registerType 

    // create an enemy 
    Enemy* createEnemy(const std::string& _type) 
    { 
     EnemyMap::iterator it(m_EnemyTypes.find(_type)); 
     if(it != m_EnemyTypes.end()) 
      return(NULL); 
     else 
      return(new Enemey(it->second)); 
    }; // eo createEnemy 
}; // eo class EnemyFactory 
+0

N'aurais-je pas alors un cours pour chaque ennemi? Ensuite, je pourrais simplement créer des enfants d'une classe ennemie abstraite et coder en dur les propriétés. Je voulais atteindre une redondance minimale et utiliser une classe pour tous. – problemofficer

+0

J'étais sous l'hypothèse que vous aviez un ensemble différent de propriétés pour différents ennemis. Ces classes "créatrices" prendraient soin d'initialiser toutes les propriétés que vous voulez et retourner un nouvel ennemi avec ces propriétés. Peut-être ai-je mal compris votre question? –

+0

Oui, la seule différence entre les ennemis sont les valeurs des paramètres, mais la quantité et les types de paramètres sont les mêmes. – problemofficer