IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Téléchargé 7 fois
Vote des utilisateurs
1 
0 
Détails
Licence : Non renseignée
Mise en ligne le 23 janvier 2017
Plate-formes : Linux, Mac, Windows
Langue : Français
Référencé dans
Navigation

Object factory

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.
Avatar de 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 koala01
Expert éminent sénior https://www.developpez.com
Le 14/12/2013 à 20:31
Citation Envoyé par imperio Voir le message
Eh bien la, ce que tu proposes revient a ce que j'avais auparavant. A utiliser c'est genial. A mettre en place ca devient vite catastrophique quand tu as une trop grande diversite de constructeurs et d'objets. Ca fait beaucoup de code pour pas grand chose au final. C'est pour cette raison que j'en suis arrive a faire mon object factory de cette facon. Faire une veritable object factory est C++ n'est pas vraiment possible. On est oblige de bidouiller pour y parvenir et je trouve ca dommage (mais que serait la programmation sans bidouille ? ).
En effet, et c'est pour cela que je vais te proposer une évolution supplémentaire.

Car, à bien y réfléchir, je me dis qu'il y a sans doute pas mal d'objets concrets dont le constructeur a besoin du même nombre de paramètres et de même types. Et c'est vrai que cela commence à faire beaucoup, surtout si tu as de nombreux objets concrets dont les constructeur acceptent les mêmes types d'arguments

Car, après tout, tu as sûrement dans ta hiérarchie quelques type dont le constructeur prend exactement les même paramètres, me trompes-je

Dés lors, pourquoi ne pas plutôt créer un trait pour chaque type et une liste de paramètres qui serait utilisée pour tous les types dont le constructeur est similaire

Et pourquoi n'utiliserait-on pas cette liste de paramètres directement dans le constructeur

Je m'explique.

Mettons les classes dérivées (de Base, toujours ) suivantes (pour l'instant, je ne m'intéresse qu'aux membres qui doivent être initialisé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
class Derivee1 :public Base{
    private:
        int i;
        Color color;
};
class Derivee2 : public Base{
    private:
        std::string str;
        Color color;
};
class Derivee3 : public Base{
    private:
        int i;
        Color color;
};
class Derivee4 : public Base{
    private:
        std::string str;
        Color color;
};
De toute évidence (même si j'ai fait expres de les alterner pour le cacher un tout petit peu ) les constructeurs de Derivee1 et de Derivee3 vont prendre les mêmes paramètres et il en va de même pour les constructeurs de Derivee2 et de Derivee4 parce que les constructeurs pourraient prendre les formes 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 Derivee1 :public Base{
    public:
        Derivee1(int i):i(i){}
        Derivee1(int i, Color const & color):i(i),color(color){}
    private:
        int i;
        Color color;
};
class Derivee2 : public Base{
    public:
        Derivee2(std::string const & str):str(str){}
        Derivee2(std::string const & str, Color const & color):str(str),color(color){}
    private:
        std::string str;
        Color color;
};
class Derivee3 : public Base{
        Derivee3(int i):i(i){}
        Derivee3(int i, Color const & color):i(i),color(color){}
    private:
        int i;
        Color color;
};
class Derivee4 : public Base{
    public:
        Derivee4(std::string const & str):str(str){}
        Derivee4(std::string const & str, Color const & color):str(str),color(color){}
    private:
        std::string str;
        Color color;
};
Tel que j'ai prévu les choses jusqu'à présent, il faudrait créer huit listes de paramètres différentes (une pour chaque constructeur spécifique à un type particulier).

Et pourtant, quatre à peine seraient amplement suffisantes !

Parce que si nous avons des 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 OnlyIntParam{
    int i;
};
struct IntAndColorParam{
    int i;
    Color color;
};
struct OnlyStringParam{
    std::string str;
};
struct StringAndColorParam{
    std::string str;
    Color color;
}
Les deux premières pourraient aussi bien servir pour les constructeurs de Derivee1 et de Derivee3 et les deux dernières pour les constructeur de Derivee2 et de Derivee4, 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
/* Allez, profitons que nous somme en C++11 :D */
class Derivee1 :public Base{
    public:
        Derivee1(int i):i(i){}
        Derivee1(int i, Color const & color):i(i),color(color){}
        Derivee1(OnlyIntParam const & pl):Derivee1(pl.i){}
        Derivee1(IntAndColorParam const & pl):Derivee1(pl.i, pl.color){}
    private:
        int i;
        Color color;
};
class Derivee2 : public Base{
    public:
        Derivee2(std::string const & str):str(str){}
        Derivee2(std::string const & str, Color const & color):str(str),color(color){}
        Derivee2(OnlyStringParam const & pl):Derivee2(pl.str){}
        Derivee2(StringAndColorParam const & pl):Derivee2(pl.str, pl.color){}
    private:
        std::string str;
        Color color;
};
class Derivee3 : public Base{
        Derivee3(int i):i(i){}
        Derivee3(int i, Color const & color):i(i),color(color){}
        Derivee3(OnlyIntParam const & pl):Derivee3(pl.i){}
        Derivee3(IntAndColorParam const & pl):Derivee3(pl.i, pl.color){}
    private:
        int i;
        Color color;
};
class Derivee4 : public Base{
    public:
        Derivee4(std::string const & str):str(str){}
        Derivee4(std::string const & str, Color const & color):str(str),color(color){}
        Derivee4(OnlyStringParam const & pl):Derivee4(pl.str){}
        Derivee4(StringAndColorParam const & pl):Derivee4(pl.str, pl.color){}
    private:
        std::string str;
        Color color;
};
Tu me diras sans doute que le problème, c'est qu'on en revient au point où la seule liste de paramètre n'est plus suffisante pour permettre de décider du type réel de l'objet à créer. Et tu auras raison

C'est pour cela qu'il faudra revenir sur l'idée de créer un trait représentant chaque type concret que tu pourrais envisager. Dans le cas présent, nous aurions donc besoin de quatre traits qui pourrais ressembler à
Code : Sélectionner tout
1
2
3
4
struct Derivee1Trait{};
struct Derivee2Trait{};
struct Derivee3Trait{};
struct Derivee4Trait{};
et nous devrions bien sûr introduire ce point de variation dans la classe ConcreteVisitableList sous la forme de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
template <typename Type, typename List>
class ConcreteVisitableList : public List,
                              public VisitableList{
    public:
    ConcreteVisitableList(List const & l):List(l){
    }
    virtual Base * accept(Factory const &) const;
};
(oui, j'ai tout simpement ajouté un paramètre template à la classe en question ). Ne t'en fais pas, je reviens sur sa fonction accept un peu plus tard.

Pour prendre en compte ce point de variation supplémentaire au niveau de la fabrique, le mieux, c'est encore de déléguer la création de l'objet réel à une classe qui pourra justement tenir compte des deux points de variations que nous avons (respectivement le type de l'objet à créer (au travers des traits) et la liste de paramètres à utiliser).

Cette classe pourrait parfaitement ressembler à ceci (meme si, en l'occurrence, c'est une structure ) :
Code : Sélectionner tout
1
2
3
4
template <typename Type, typename List>
struct TypeCreator{
    Base * create( List const &) const;
};
Et, comme nous aurions fournit un constructeur qui prend une liste de paramètres spécifique, nous n'aurions plus qu'à spécialiser cette structure, non pas pour tous les constructeurs de tous les types concrets, mais uniquement pour chaque type concret sous la forme de spécialisations partielles proches 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
template <typename List>
struct TypeCreator<Derivee1Trait, List>{
    Base * create(List const & list) const{
        return new Derived1(list);
    }
};
template <typename List>
struct TypeCreator<Derivee1Trait, List>{
    Base * create(List const & list) const{
        return new Derived2(list);
    }
};
template <typename List>
struct TypeCreator<Derivee3Trait, List>{
    Base * create(List const & list) const{
        return new Derived3(list);
    }
};
template <typename List>
struct TypeCreator<Derivee4Trait, List>{
    Base * create(List const & list) const{
        return new Derived4(list);
    }
};
(nota : ces spécialisations partielles prendront avantageusement place dans le fichier Factory.cpp, pour éviter de les éparpiller et surtout, parce que c'est là qu'elles seront nécessaires )
Et, au final, notre classe Factory pourrait se transformer en
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
class Factory
{
    public:
        Factory();
        Base * create(VisitableList const & tl) const{
            return tl.accept(*this);
        }
        template <typename Type, typename List>
        Base * visit(Type, ConcreteVisitableList<Type, List> const & list) const{
            return TypeCreator<Type, List>().create(list);
        }
    private:
};
Nous n'aurons alors "plus qu'à" fournir (également dans Factory.cpp) l'implémentation de la fonction ConcreteVisitableList<Type, List>::accept sous la forme de
Code : Sélectionner tout
1
2
3
4
template <typename Type, typename List>
Base * ConcreteVisitableList<Type, List>::accept(Factory const & factory) const{
    return factory.visit(Type(), *this);
}
Et, sous cette forme, le code à rajouter en cas d'évolution est particulièrement limité :
Tu veux rajouter le type concret Derived5 dont les constructeurs prennent les même paramètres que Derived2 (ou derived4) Pas de problème:
  1. tu crées un trait supplémentaire sous la forme de struct Derived5Trait{};
  2. tu crées une spécialisation partielle de TypeCreator qui utilise ce trait et dont l'implémentation la fonction create prend la forme de Base * create(List const & list) const{return new Derived5(list);}
  3. Tu termines en rajoutant les insiations explicites qui vont bien pour la classe ConcreteVisitableList sous la forme de template class ConcreteVisitableList<Derived5Trait, OnlyStringParam>; et de template class ConcreteVisitableList<Derived5Trait, OnlyStringParam>;
Tu as besoin d'une nouvelle liste de paramètres pour un tout noueau type qui ne peut pas se satisfaire de l'existant Aucun problème, crée la structure qui t'intéresse et tu crées l'instanciation explicite adéquate

Notes au passage que tu peux parfaitement envisager l'héritage au niveau de tes listes de paramètres...

J'aurais tout aussi bien pu faire quelque chose de fort proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
struct OnlyIntParam : private NonCopyable{
    int i;
};
struct IntAndColorParam : public OnlyIntParam{
    Color color;
};
struct OnlyStringParam : private NonCopyable{
    std::string str;
};
struct StringAndColorParam : public OnlyStringParam{
    Color color;
};
Bon, les noms sont mal choisis maintenant parce qu'on se dit que LSP ne peut pas être respecté, mais ca fonctionnera aussi bien

NOTA: Et si ca te semble encore trop, tu pourrais aussi envisager de définir un type associé dans les traits et les transormer en quelque chose qui serait proche de
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
struct Derivee1Trait{
    using object_type = Derived1;
};
struct Derivee2Trait{
    using object_type = Derived2;
};
struct Derivee3Trait{
    using object_type = Derived3;
};
struct Derivee4Trait{
    using object_type = Derived4;
};
Ceci t'éviterait de devoir fournir une spécialisation partielle pour TypeCreator, dont l'implémentation de la fonctin create pourrait alors prendre la forme de
Code : Sélectionner tout
1
2
3
4
template<typename Trait, typename List>
Base * TypeCreator<Trait, typename List>::create(List const & list) const{
    return new Trait::object_type(list);
}
(toujours à placer dans Factory.cpp).

Mais il faudra alors sans doute penser à rajouter une instanciation explicite pour tous les types en question (donc : un par constructeur particulier de chaque type concret), je ne suis pas sûr que tu y gagnes au final (en terme de code écrit )
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 14/12/2013 à 23:03
Citation Envoyé par imperio Voir le message
Y a pas a dire, ton raisonnement est beton. Je t'avouerai que j'ai meme appris 2-3 trucs au passage . Ce que tu proposes est - je pense - ce qui se rapproche le plus d'une object factory en C++ (tu devrais la proposer en code qu'en penses-tu ?).
Ca peut s'envisager

Je vais peut être faire un peu d'ordre dans le projet de test que j'ai utilisé, rajouter les commentaires doxygen et le poster
Cependant je vois toujours ce meme petit defaut : si le developpeur veut rajouter une classe (fille de la principale bien evidemment) qui n'a pas un constructeur qui existe deja, ca l'oblige a rajouter ce code lui-meme et c'est tres exactement ce point que je trouve derangeant.
Mais, dis toi que, quoi que tu fasse, il faudra toujours à un moment ou à un autre fournir les valeurs correctes à transmettre au constructeur, d'où qu'elles viennent et quelle que soit la manière dont elles seront utilisées.

Dés lors, pourquoi râler si, parce que tu as un type d'objet tout à fait nouveau, tu dois "formaliser" plus ou moins la liste des arguments que son constructeur attend

Parce que c'est finalement à peu près tout ce que tu as à faire : la création d'un trait prend dix secondes montre en main et la spécialisation de TypeCreator demandera à peine plus (un copier coller suivi d'une adaptation de trait)

Je vois trois facons de faire une object factory, soit comme ca :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
Object *createObject(std::string name)
{
  if (name == "dollar") {
    return new USA();
  } else if (name == "livre") {
    return new UK();
  }
  ...
}
Et si je veux ajouter le yen ou le rouble

Autant essayer de respecter l'OCP, tu ne crois pas

La méthode que je viens de te montrer le respecte

Soit le cas sur lequel nous debattons a savoir, on possede les arguments de la classe et selon cette liste d'argument, nous creons le bon objet.
De toutes manières, à partir du moment où tu décide de transmettre toutes les informations pertinentes au constructeur d'un objet, c'est que tu disposes, ad minima, des informations en question, non

Soit le code que j'ai propose qui, bien que s'eloignant du concept de base d'une object factory n'est pas completement hors-sujet.
pas tout à fait hors sujet, mais qui ne me convainc personnellement pas .

Maintenant, ce n'est jamais que mon avis personnel et il n'engage que moi

Je t'avouerai ne plus vraiment savoir ou donner de la tete. Les deux premieres methodes requierent une mise en place proportionnellement lourde aux nombres de classe potentiellement existantes dans le code.
Proportionnellement lourde par rapport aux nombre de classes existantes

A priori, la fabrique sera l'une des premières choses que tu devrais créer, dés le moment où tu ne fait ne serait-ce qu'envisager la mise en place d'une hiérarchie de classe et le mécanisme que je te présente est prévu pour évoluer au fur et à mesure que de nouveaux types dérivés apparaissent. Et tout cela au prix de quoi
  • Une ligne pour le trait
  • Une structure qui dresse la liste des paramètres (et qui pourrait être systématiquement le seul paramètre passé à ton constructeur), et encore, uniquement si elle n'existe pas encore
  • Six lignes de code pour la spécialisation partielle de TypeCreator
  • une ligne de code pour l'instanciation explicite (je crois d'ailleurs qu'un typedef pourrait avoir le même effet)

Pour moi, ce n'est pas vraiment lourd comparé à l'évolutivité et à la sécurité offerte

Parce que, si quelque chose manque ou est mal fait, tu seras bloqué à la compilation (au grand plus tard à l'édition des liens)

La troisieme ne requiert rien mais n'est pas a proprement parler une object factory (d'ailleurs comment je pourrais appeler ce que j'ai cree ?). J'espere que tu comprends mon dilemme . Je trouve le concept de base d'une object factory trop specialise, mais peut-etre est-ce le but en fin de compte...
Bien sur que le but d'une fabrique est d'être extrêmement spécialisé

Le principe est d'avoir un concept qui ne s'occupe que d'une chose : créer des objets! On peut difficilement faire plus spécialisé, même si on parle d'une grosse hiérarchie de classes
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 15/12/2013 à 3:16
Citation Envoyé par imperio Voir le message
Ce que tu proposes est - je pense - ce qui se rapproche le plus d'une object factory en C++ (tu devrais la proposer en code qu'en penses-tu ?)
Allez, tu l'as voulu, tu l'as eu!

Il se trouve ==>ici<==
Avatar de 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
Membre émérite 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
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
Membre émérite 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 imperio
Membre émérite 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 ?
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.