Object factory

Présentation
La sortie de la norme C++11 nous a ouvert pas mal d'horizons. J'ai donc cree une classe ObjectFactory qui permet grace aux templates variadiques de creer n'importe quel type d'objet. Dans l'archive il y a la fameuse classe ainsi qu'un fichier fournissant un exemple de son utilisation. N'hesitez pas a me donner vos avis.
Téléchargement
Compatibilité
Linux Mac Windows
1  0 
Téléchargé 19 fois Voir les 14 commentaires
Détails
Catégories : Design patterns
Avatar de imperio
Membre chevronné
Voir tous les téléchargements de l'auteur
Licence : Autre
Date de mise en ligne : 23 janvier 2017




Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 13/12/2013 à 22:12
Salut,

Ben, perso, je ne suis pas convaincu...

La raison va te paraître idiote mais j'ai du mal à imaginer devoir créer une collection de fabriques pour être en mesure de créer mes objets...

Généralement, tu as une seule fabrique qui s'occupe de créer les différents types d'objets dérivant d'un type de base, mais ici, tu te retrouve avec autant de fabriques que de type dérivés Tu perds littéralement tout l'attrait du patron factory (car, l'idée, c'est que tu ne doive pas t'inquiéter du type réel de l'objet créé par la fabrique.

Or, du simple fait que tu es obligé de créer une fabrique par type d'objet à créer, tu te trouves bel et bien face à la nécessité de connaître tous les types d'objets qui seront créés.

Tu vois où je veux en venir
Avatar de imperio imperio - Membre chevronné https://www.developpez.com
le 13/12/2013 à 22:32
Je dois admettre avoir un peu de mal a te suivre... On peut templater la classe ObjectFactory et du coup permettre d'avoir une seule et unique factory, le but etant juste de demontrer comment faire une ObjectFactory en C++11. En tout cas je ne cracherais pas sur une explication un peu plus complete de ce qui te pose probleme.

En ce qui me concerne, je m'en sers pour stocker tous les objets de mon moteur graphique. Du coup ca me permet de faire une page de chargement en stockant le type de l'objet a creer et de l'initialiser plus tard.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 13/12/2013 à 23:04
Le but de la fabrique est que son utilisateur puisse se "contenter" de basarder les informations dont elle a besoin pour créer les objets qu'on lui demande sans avoir à s'inquiéter du type réel de l'objet qu'il (l'utilisateur) obtiendra.

En gros, en orienté objet pur, la fabrique de ton exemple ressemblerait à
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Factory{ 
    /* renvoie un pointeur sur un objet de type MotherClass 
     * qui est réellement un objet de type MotherClass 
     */ 
    MotherClass * create(); 
    /* renvoie un pointeur sur un objet de type MotherClass 
     * qui est en réalité un objet de type DaughterClass1 
     */ 
    MotherClass * create( int); 
    /* renvoie un pointeur sur un objet de type MotherClass 
     * qui est en réalité un objet de type DaughterClass2 
     */ 
    MotherClass * create( char *, float ); 
};
(je fais simple et je ne m'intéresse pas trop aux détails )

Cette fabrique pourrait être utilisée sous une forme proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
int main(){ 
    /* tant qu'à faire, utilisons les pointeurs intelligents :D */ 
    std::vector<std::unique_ptr<MotherClass>> datas; 
    Factory factory; 
    data.emplace_back(std::unique_ptr<MotherClass>(factory.create()); 
    data.emplace_back(std::unique_ptr<MotherClass>(factory.create(5)); 
    data.emplace_back(std::unique_ptr<MotherClass>(factory.create("hello", 3.1415926)); 
    /* ... */ 
}
Or, je n'ai regardé le code que de manière assez distraite, mais ce qui me pose problème, c'est la nécessité d'avoir un
Code : Sélectionner tout
1
2
3
4
 std::vector<ObjectFactory*>	vec; 
vec.push_back(ObjectFactory::createNewObject<DaughterClass1, int>(18)); 
vec.push_back(ObjectFactory::createNewObject<MotherClass>()); 
vec.push_back(ObjectFactory::createNewObject<DaughterClass2, const char*, float>("test", 4.f));
ou, du moins, de devoir préciser non seulement le type d'objet que tu veux que ta fabrique crée, mais aussi les paramètres qu'elle devra accepter.

Autrement dit, tu nous explique que tu as une fabrique qui devrait s'utiliser sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
int main(){ 
    std::vector<MotherClass *> datas; 
    datas.push_back(ObjectFactory::createNewObject<MotherClass>()->createObject()); 
    datas.push_back(ObjectFactory::createNewObject<DaughterClass1, int>( 18)->createObject()); 
    datas.push_back(ObjectFactory::createNewObject<DaughterClass2, const char*, float>("hello", 3.1415926)->createObject()); 
}
(free style, je n'ai pas testé )Bien sur, je simplifie (parce qu'il y a le problème de la fuite mémoire que ce code engendre ).

Dés lors, je te pose la question : Si tu dois connaître exactement le type de l'objet réel pour lequel tu veux récupérer le pointeur, pourquoi te faire ch..er à passer par une fabrique alors que tu aurais tout aussi bien pu directement créer ton objet au travers de new

Bon, d'accord, tu me diras que tu peux te contenter d'avoir une déclaration anticipée dans l'histoire et que tu peux aller planquer l'inclusion du fichier d'en-tête des classes dérivées dans un quelconque fichier xxx_explicit.cpp, mais j'ai quand même l'impression que tu te fais du mal pour pas grand chose

Attention, du pur point de vue de la technique pure, je n'ai pas grand chose à redire sur l'implémentation (que je n'ai regardé que d'un œil distrait ), mais une belle technique inutile reste toujours inutile aussi poussée soit elle
Avatar de imperio imperio - Membre chevronné https://www.developpez.com
le 14/12/2013 à 0:16
Non, je crois que tu as bien cerne le tout. A l'origine j'avais justement une classe avec des methodes qui ressemblaient a ce que tu as montre :

Code : Sélectionner tout
1
2
3
4
5
class Factory{ 
    MotherClass * create(); 
    MotherClass * create( int); 
    MotherClass * create( char *, float ); 
};
Le probleme c'est que le nombre d'objet possedant chacun leur propre constructeur heritant tous d'une meme classe a tres vite augmente (chacun de ses objets avait bien evidemment plusieurs constructeurs). Sans parler du fait que certains avait exactement le meme constructeur, comment differencier mon Cube de ma Box (ceci est un exemple bidon ) ?
J'ai donc tout simplement cherche a contourner le probleme mais je ne savais pas comment stocker les arguments dans ma classe sans m'obliger a repasser par quelque chose comme ca. C'est donc de la que vient la creation de cette classe ObjectFactory.

Par pure curiosite, comment aurais-tu resolu mon probleme ? Je l'avais fait de cette facon :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ObjectFactory 
{ 
public: 
  static ObjectFactory *cube(arg1, arg2); 
  static ObjectFactory *sphere(arg1, arg2, arg3, arg4); 
  static ObjectFactory *model(arg1); 
  ... 
  Object *create(); 
 
private: 
  arg1  a1; 
  arg2  a2; 
  ... 
};
Je ne sais pas si mon code exemple est tres clair sur ce que j'ai fait.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 14/12/2013 à 5:19
J'y réfléchis à peu près en même temps que je n'écris cette réponse, donc, il y aura sans doute quelques arrangements à faire

Mais déjà, j'aurais commencé par m'assurer qu'il n'y a qu'un seul constructeur non trivial par objet concret à créer.

Ensuite, je crois que j'aurais travaillé de la sorte.

J'aurais déjà une structure qui me permet d'empêcher la copie et l'affectation des objet ayant sémantique d'entité sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
 
struct NonCopyable{ 
    NonCopyable(NonCopyable const &) = delete; 
    NonCopyable operator=(NonCopyable const &) = delete; 
protected: 
    NonCopyable(){} 
    ~NonCopyable(){} 
};
(oui, je sais, c'est très ressemblant à boost, hein )
et, pour une hiérarchie de classe proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Base : private NonCopyable 
{ 
    public: 
        Base(); 
        virtual ~Base(); 
        virtual void print() const = 0; 
    protected: 
    private: 
}; 
class Derivee1 : public Base 
{ 
    public: 
        Derivee1(int i); 
        virtual ~Derivee1(); 
        void print() const; 
    protected: 
    private: 
        int i; 
}; 
class Derivee2 : public Base 
{ 
    public: 
        Derivee2(std::string const & str); 
        virtual ~Derivee2(); 
        void print() const; 
    protected: 
    private: 
        std::string str; 
}; 
/* .. */ 
class DeriveeN : public Base 
{ 
    public: 
        DeriveeN(std::string const & str, double d); 
        virtual ~DeriveeN(); 
        void print() const; 
    protected: 
    private: 
        std::string str; 
        double d; 
};
j'aurais créé un trait pour chaque type concret qu'il me faudra créer. cela aurait pris la forme de
Code : Sélectionner tout
1
2
3
4
5
 
struct d1_trait{}; 
struct d2_trait{}; 
/* ... */ 
struct dN_trait{};
Ensuite, j'aurais créé une structure de base qui aurait représenté une liste de paramètre et j'y aurais adjoint la déclaration anticipée d'une classe template, sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
struct ParameterList : private NonCopyable 
{ 
    public: 
    protected: 
        ParameterList(); 
        ~ParameterList(); 
    private: 
}; 
template <typename Trait> 
struct ConcreteParameterList;
Et j'aurais fournis des spécialisations de ConcreteParameterList pour chaque trait, sous une forme proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <> 
struct ConcreteParameterList<d1_trait> : public ParameterList{ 
    int i; 
}; 
template <> 
struct ConcreteParameterList<d2_trait> : public ParameterList{ 
    std::string str; 
}; 
/* ... */ 
template <> 
struct ConcreteParameterList<dN_trait> : public ParameterList{ 
    std::string str; 
    double d; 
};
Et j'aurais créé une classe template pour la création des objets dérivés de base sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
class Base; 
class ParameterList; 
template <typename Type> 
struct DerivedCreator{ 
    Base * create(ParameterList const &) const; 
};
J'aurais terminé en fournissant la fabrique, qui disposerait d'une surcharge de la fonction create pour pour chaque trait envisagé et qui recevrait également une ParameterList, sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
class Base; 
class ParameterList; 
class Factory 
{ 
    public: 
        Factory(); 
        Base * create(d1_trait const &, ParameterList const &) const; 
        Base * create(d2_trait const &, ParameterList const &) const; 
        /* ... */ 
        Base * create(dN_trait const &, ParameterList const &) const; 
    protected: 
    private: 
};
Et, pour finir, dans le fichier d'implémentation de la factory, j'aurais fourni une spécialisation totale de la fonction Base * DerivedCreator::create(ParameterList const &) const; en même temps que l'implémentation de la fonction create de la fabrique, sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template <> 
Base * DerivedCreator<d1_trait>::create(ParameterList const & pl) const{ 
    ConcreteParameterList<d1_trait> const & temp = 
            static_cast<ConcreteParameterList<d1_trait> const & >(pl); 
     return new Derivee1(temp.i); 
} 
Base * Factory::create(d1_trait const &, ParameterList const & pl) const{ 
    return DerivedCreator<d1_trait>()::create(pl); 
} 
template <> 
Base * DerivedCreator<d2_trait>::create(ParameterList const & pl) const{ 
    ConcreteParameterList<d2_trait> const & temp = 
            static_cast<ConcreteParameterList<d2_trait> const & >(pl); 
     return new Derivee2(temp.str); 
} 
 
Base * Factory::create(d2_trait const &, ParameterList const & pl) const{ 
    return DerivedCreator<d2_trait>()::create(pl); 
} 
/* ... */ 
/* ... */ 
template <> 
Base * DerivedCreator<dN_trait>::create(ParameterList const & pl) const{ 
    ConcreteParameterList<dN_trait> const & temp = 
            static_cast<ConcreteParameterList<dN_trait> const & >(pl); 
     return new DeriveeN(temp.str, temp.d); 
} 
Base * Factory::create(dN_trait const &, ParameterList const & pl) const{ 
    return DerivedCreator<dN_trait>()::create(pl); 
}
Je sais, cela t'oblige à fournir deux spécialisations à chaque fois que tu veux rajouter un type dérivé de Base, mais d'un autre coté, tu ne dévoile absolument plus rien des types dérivés de Base à l'utilisateur de ta fabrique.

Tout ce qu'il connaîtra, c'est le type Base, une série de traits qui lui permettent de définir le type réel et les arguments qu'il doit passer

Le tout pourrait être utilisé sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <Base.h> // la classe Base 
#include <D1Params.h> // les paramètres pour Derivee1 
#include <D2Params.h> // les paramètres pour Derivee2 
/* ... */ 
#include <DNParams.h>// les paramètres pour DeriveeN 
#include <Factory.h>  // la fabrique 
 
int main() 
{ 
    ConcreteParameterList<d1_trait> pl1; 
    pl1.i = 12; 
    ConcreteParameterList<d2_trait> pl2; 
    pl2.str = "salut"; 
    /* ... */ 
    ConcreteParameterList<dN_trait> plN; 
    plN.str = "hello"; 
    plN.d= 3.1415926; 
    Factory factory; 
    Base * ptr1 = factory.create(d1_trait(),pl1); 
    Base * ptr2 = factory.create(d2_trait(),pl2); 
    /* ... */ 
    Base * ptrN = factory.create(dN_trait(),plN); 
    ptr1->print(); 
    ptr2->print(); 
    /* ... */ 
    ptrN->print(); 
    /* ... */ 
   delete ptr1; 
    delete ptr2; 
    /* ... */ 
    delete ptrN; 
    return 0; 
}
PS: bon, je sais, j'ai peut être été un peu loin en utilisant un DerivedCreator vu que, tant qu'à surcharger la fonction create de ma fabrique, j'aurais tout aussi bien pu faire la conversion directement à l'intérieur de celle-ci...

Mais cette indirection supplémentaire pourrait peut être te mettre sur la voir d'une amélioration réelle
Avatar de imperio imperio - Membre chevronné https://www.developpez.com
le 14/12/2013 à 14:02
Le probleme c'est vraiment que chaque objet a plusieurs constructeurs (texture ou couleur ? Rotation ou pas ?). De plus, un systeme comme ca me semble beaucoup trop lourd a mettre en place. Cependant ca correspond bien plus a une object factory pour le coup. Arf, dilemme...
Le fait de devoir recreer un type pour chaque classe par contre je trouve ca moyen. Au final, mettre la classe dans le template serait revenu au meme si je ne m'abuse ?
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 14/12/2013 à 14:58
Citation Envoyé par imperio  Voir le message
Le probleme c'est vraiment que chaque objet a plusieurs constructeurs (texture ou couleur ? Rotation ou pas ?).

Pour la texture ou la couleur, je peux admettre l'objection.

Bien qu'il serait peut être intéressant d'envisager une abstraction qui puisse présenter "le remplissage"de l'objet et qui pourrait être utilisé sous cette forme pour la cause
Pour la rotation, c'est sans doute une action qui sera prise après création de ton objet.

Pourquoi vouloir absolument l'appliquer à la création Le but du constructeur est de fournir un objet dans un état cohérent, directement utilisable. Ce n'est pas forcément de fournir un objet dans l'état précis dans lequel tu veux qu'il soit, du moment que l'état soit cohérent
De plus, un systeme comme ca me semble beaucoup trop lourd a mettre en place.

Lourd, peut être, mais quelle évolutivité! Et le mieux de l'histoire, c'est que tout est vérifié à la compilation

Cependant ca correspond bien plus a une object factory pour le coup. Arf, dilemme...

Ah, ca, c'est le but
Le fait de devoir recreer un type pour chaque classe par contre je trouve ca moyen.

Il faut remettre les choses à leur place!

La création de tes objets devrait être centralisée. Dés que tu décides de créer un nouvel objet, tu tombes dans un système particulièrement restreint dans le quel interviennent:
  • la fabrique
  • le "gestionnaire" responsable de la durée de vie de tes objets
  • "quelques classes" qui te permettent de sélectionner l'objet qui sera créé et les paramètres qui devront servir pour ce faire.
Alors, oui, effectivement, quand tu te trouves à l'intérieur de ce système tu te retrouves avec trois fois plus de types que n'importe où ailleurs. Mais une fois que tu en sors, il ne reste que ta hiérarchie d'objets et, pour une grosse partie, que ton objet de base

Et, surtout, tu ne donnes pas l'impression dans ce système qu'il est "normal" de manipuler tes objets autrement que comme s'ils était du type de base parce que le seul endroit où la relation entre le type dérivé et le trait est réellement fait, c'est dans la "popote interne" de ta fabrique.

Au final, mettre la classe dans le template serait revenu au meme si je ne m'abuse ?

Tu veux dire avoir quelque chose qui ressemblerait à
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
class ParameterList : private NonCopyable{ 
    public: 
    virtual Base * create() const = 0; 
}; 
 
template <> 
class ConcreteParameterList<d1_trait> : public ParameterList{ 
    public: 
        int i; 
        Base * create() const{return new Derived1(i);} 
};
et avoir, au niveau de ta fabrique quelque chose comme
Code : Sélectionner tout
1
2
3
4
5
6
class Factory{ 
    public: 
        Base * create(ParameterList const & pl) const{ 
            return pl.create(); 
        } 
};
Ce serait un très mauvais plan.

Parce que, en l'état, tu ne pourrais pas implémenter le code de ConcreteParameterList<d1_trait>::create ailleurs que dans la déclaration de la classe elle-même. Tu ne pourrais pas, par exemple, avoir l'implémentation des différentes spécialisation pour cette fonction dans Farctory.cpp sous une forme proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
template<> 
Base * ConcreteParameterList<d1_trait>::create() const{ 
    return new Derived1(i); 
} 
template<> 
Base * ConcreteParameterList<d2_trait>::create() const{ 
    return new Derived2(str); 
} 
template<> 
Base * ConcreteParameterList<dN_trait>::create() const{ 
    return new DerivedN(str, d); 
}
parce que le compilateur ne trouvera simplement pas la déclaration de la fonction

Du coup, pour pouvoir créer ton objet (Derived1 ... DerivedN) dans la fonction, tu devrais inclure le fichier d'en-tête de ta classe dérivée directement dans le fichier dans lequel tu définis la spécialisation totale de ConcreteParameterList.

En soi, ce n'est pas catastrophique, mais ca crée un précédant.

Je veux dire par là que tu "déroges à la règle" qui te conseille très fortement de préférer les déclarations anticipées à chaque fois que possible en incluant ce fichier dans un fichier d'en-tête.

Cette dérogation serait, en l'espèce justifiée, mais le gros risque, en voyant ton fichier D1ParameterList.hpp qui aurait la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
#include <ParameterList.h> 
#include <Derived1.h> 
struct d1_trait{}; 
template <> 
class ConcreteParameterList<d1_trait> : public ParameterList{ 
    public: 
        int i; 
        Base * create() const{return new Derived1(i);} 
};
c'est que le lecteur se dise "bah, il a inclu Derived1.h (dans un fichier d'en-tête !!! ), pourquoi est ce que je ne le ferais pas

Et tu vas observer un véritable effet boule de neige, parce qu'une fois que quelqu'un aura décidé d'inclure Derived1.h et tous les autres fichiers similaires dans un autre fichier, cela fera "tâche d'huile".

Le résultat ne fera pas un pli : il y aura toujours un imbécile pour se dire "Mais pourquoi je me fais ch...er à manipuler mes objets comme des objets de type Base, alors que j'ai la connaissance des types concrets qui en dérivent "

Et du coup, il va commencer à te faire une jolie fonction qui prendra la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
void foo(Base * b){ 
    if(dynamic_cast<Derived1*>(b) ){ 
 
    }else  if(dynamic_cast<Derived2*>(b) ){ 
 
    }else  if(dynamic_cast<DerivedN*>(b) ){ 
 
    } 
}
Tant qu'il n'y en aura qu'une seule, cela pourrait passer (mais bon, on dit déjà adieu à l'OCP)... Mais le même imbécile se dira que "ca a été si bien avec foo, pourquoi pas faire pareil ave bar, avec foobar, et avec XXXfooYYYbar "

Et quand tu voudras ajouter DerivedXXX à ta hiérarchie, tu te retrouveras avec un tas de cas face auxquels tu t'étonnera que "tiens, pourquoi est ce que cela fonction avec Derived1 et pas avec DerivedXXX " (si tu as la chance que l'application ne plante purement et simplement pas )
Avatar de imperio imperio - Membre chevronné https://www.developpez.com
le 14/12/2013 à 16:34
Citation Envoyé par koala01  Voir le message
Pourquoi vouloir absolument l'appliquer à la création Le but du constructeur est de fournir un objet dans un état cohérent, directement utilisable. Ce n'est pas forcément de fournir un objet dans l'état précis dans lequel tu veux qu'il soit, du moment que l'état soit cohérent

C'est justement parce que je voulais un constructeur qui fournisse un objet directement utilisable que je force l'utilisateur a mettre tous ces parametres. Sinon on pourrait faire de meme avec la couleur.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
Object *p = new Object; 
 
p->setColor(Color(r, g, b)); 
p->setPosition(Vector3D(x, y, z)); 
p->setRotation(Rotation(speed, x_angle, y_angle, z_angle)); 
 
p->initialize(); 
p->draw(); 
...
Alors qu'on pourrait faire :

Code : Sélectionner tout
1
2
3
4
5
Object *p = new Object(Color(r, g, b), Vector3D(x, y, z), Rotation(speed, x_angle, y_angle, z_angle)); 
 
p->initialize(); 
p->draw(); 
...
Je prefere clairement la 2e syntaxe a la premiere. Apres je pense que c'est un point de vue personnel mais je trouve la 2e plus lisible et surtout moins lourde.

Citation Envoyé par koala01  Voir le message
Tu veux dire avoir quelque chose qui ressemblerait à

A vrai dire je parlais plutot de ca :

Code : Sélectionner tout
struct d1_trait{};
Pourquoi ne pas juste laisser le type de la classe directement ? Donc au lieu de faire ca :

Code : Sélectionner tout
1
2
3
4
template <> 
struct ConcreteParameterList<d1_trait> : public ParameterList{ 
    int i; 
};
Faire plutot ca :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
template <> 
struct ConcreteParameterList<Object> : public ParameterList{ 
    int i; 
}; 
template <> 
struct ConcreteParameterList<Object2> : public ParameterList{ 
    int i; 
    float y; 
};
L'interet des structures dN_trait me semble pas franchement present... A part pour apporter une abstraction que je trouve mal-venue dans une object factory qui est deja tres lourde a mettre en place.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 14/12/2013 à 18:39
En effet, je suis de toutes manières globalement opposé aux setXXX

Ceci dit, j'ai décidé d'avoir un trait par type, mais il n'y a strictement rien qui t'interdise d'avoir plusieurs traits, correspondants aux constructeurs qui devront être appelés, par type

Tu pourrait très bien avoir quelque chose comme
s
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
truct posAndSizeBallTrait{}; 
template<> 
class ConcreteParameterList<posAndSizeBallTrait> : public ParameterList{ 
    public: 
        Position position; 
        double size; 
} 
template <> 
struct DerivedCreator<posAndSizeBallTrait>{ 
    Base * create(ParameterList const & pl) const{ 
        ConcreteParameterList<posAndSizeBallTrait> const & temp= 
              static_cast<ConcreteParameterList<posAndSizeBallTrait>const &)(pl); 
        return new Ball(temp.position, temp.size); 
    } 
}; 
struct colorSizeAndPositionBallTrait{}; 
struct posAndSizeBallTrait{}; 
template<> 
class ConcreteParameterList<colorSizeAndPositionBallTrait> : public ParameterList{ 
    public: 
        Position position; 
        double size; 
        Color color; 
} 
template <> 
struct DerivedCreator<posAndSizeBallTrait>{ 
    Base * create(ParameterList const & pl) const{ 
        ConcreteParameterList<colorSizeAndPositionBallTrait> const & temp= 
              static_cast<ConcreteParameterList<colorSizeAndPositionBallTrait>const &)(pl); 
        return new Ball(temp.position, temp.size, temp.color); 
    } 
};
(ne t'en fais pas trop pour les noms )

Ou bien, tu peux aussi envisager une autre solution basée sur le patron de conception visiteur.

On crée une hiérarchie de classes dont la classe de base ressemble à
Code : Sélectionner tout
1
2
3
4
5
6
7
8
 
class Factory; 
class Base; 
class VisitableList{ 
public: 
    virtual Base * accept(Factory const &) const =0; 
    virtual ~VisitableList(); 
};
Et on crées une classe template qui hérite de cette classe de base sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
 
template <typename List> 
class ConcreteVisitableList : public List, 
                              public VisitableList{ 
    public: 
    ConcreteVisitableList(List const & l):List(l){ 
    } 
    virtual Base * accept(Factory const &) const; 
};
Et, par ailleurs, on crée nos liste de paramètre de manière tout à fait indépendante (enfin, une par constructeur pour un type précis envisagé).

si donc on a une hiérarchie d'objet proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Base 
{ 
    public: 
        Base(); 
        virtual ~Base(); 
        virtual void print() const = 0; 
}; 
class Derived1 : public Base 
{ 
    public: 
        Derived1(int i); 
        Derived1(int i, Color const & color); 
        virtual ~Derived1(); 
        void print() const; 
    protected: 
    private: 
        int i; 
        Color color; 
}; 
class Derived2 : public Base 
{ 
    public: 
        Derived2(std::string const & str); 
        Derived2(std::string const & str, Color const & color); 
        virtual ~Derived2(); 
        void print() const; 
    protected: 
    private: 
        std::string str; 
        Color color; 
};
On se retrouvera avec quatre listes de paramètres, qui prendraient la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Derived1SingleParam{ 
    int i; 
}; 
struct Derived1TwoParams{ 
    int i; 
    Color color; 
}; 
struct Derived2SingleParam{ 
    std::string  str; 
}; 
struct Derived2TwoParams{ 
    std::string str; 
    Color color; 
};
Et notre fabrique deviendrait le vistieur de tout cela, sous une forme proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
class Base; 
class Factory 
{ 
    public: 
        Factory(); 
        Base * create(VisitableList const & tl) const; 
        Base * visit(ConcreteVisitableList<Derived1SingleParam> const &) const; 
        Base * visit(ConcreteVisitableList<Derived1TwoParams> const &) const; 
        Base * visit(ConcreteVisitableList<Derived2SingleParam> const &) const; 
        Base * visit(ConcreteVisitableList<Derived2TwoParams> const &) const; 
    protected: 
    private: 
};
Le tout, avec un Factory.cpp qui ressemblerait à
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "Factory.h" 
#include <Derived1.h> 
#include <Derived2.h> 
template <typename List> 
Base * ConcreteVisitableList<List>::accept(Factory const & factory) const{ 
    return factory.visit(*this); 
} 
Factory::Factory() 
{ 
    //ctor 
} 
Base * Factory::create(VisitableList const & tl) const{ 
    return tl.accept(*this); 
} 
Base * Factory::visit(ConcreteVisitableList<Derived1SingleParam> const & pl) const{ 
    return new Derived1(pl.i); 
} 
Base * Factory::visit(ConcreteVisitableList<Derived1TwoParams> const & pl) const{ 
    return new Derived1(pl.i, pl.color); 
} 
Base * Factory::visit(ConcreteVisitableList<Derived2SingleParam> const & pl) const{ 
    return new Derived2(pl.str); 
} 
Base * Factory::visit(ConcreteVisitableList<Derived2TwoParams> const & pl) const{ 
    return new Derived2(pl.str, pl.color); 
} 
/** instanciation explicite des spécialisation de ConcreteVisitableList, 
  * nécessaires pour générer la version correcte de la fonction accept() 
  */ 
template class ConcreteVisitableList<Derived1SingleParam>; 
template class ConcreteVisitableList<Derived1TwoParams>; 
template class ConcreteVisitableList<Derived2SingleParam>; 
template class ConcreteVisitableList<Derived2TwoParams>;
Le tout pourrait être utilisé sous une forme proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <Base.h> 
#include <Factory.h> 
#include <Derived1Params.h> 
#include <Derived2Params.h> 
#include <VisitableList.h> 
using namespace std; 
 
int main() 
{ 
    Derived1SingleParam parameters1Derived1; 
    parameters1Derived1.i = 1; 
     
    Derived1TwoParams parameters2Derived1; 
    parameters2Derived1.i=12; 
    parameters2Derived1.color = Color(5,5,5); 
 
    Derived2SingleParam parameters1Derived2; 
    parameters1Derived2.str = "hello"; 
     
    Derived2TwoParams parameter2Derived2; 
    parameter2Derived2. str = "world"; 
    parameter2Derived2.color = Color(5,5,5); 
     
    Factory factory ; 
     
    Base * ptrDerived1param1 = factory.create(ConcreteVisitableList<Derived1SingleParam>(parameters1Derived1)); 
    Base * ptrDerived1param2 = factory.create(ConcreteVisitableList<Derived1TwoParams>(parameters2Derived1)); 
    Base * ptrDerived2param1 = factory.create(ConcreteVisitableList<Derived2SingleParam>(parameters1Derived2)); 
    Base * ptrDerived2param2 = factory.create(ConcreteVisitableList<Derived2TwoParams>(parameter2Derived2)); 
     
    ptrDerived1param1->print(); 
    ptrDerived1param2->print(); 
    ptrDerived2param1->print(); 
    ptrDerived2param2->print(); 
     
    delete ptrDerived1param1; 
    delete ptrDerived1param2; 
    delete ptrDerived2param1; 
    delete ptrDerived2param2; 
    return 0; 
}
Est ce que tu préfères cette formule
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.
Contacter le responsable de la rubrique C++