IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Téléchargez C++ Discussion :

Object factory


Sujet :

Téléchargez C++

  1. #1
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut Object factory
    Bonjour,

    Je vous propose un nouvel élément à utiliser : 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. N'hesitez pas a me donner vos avis.

    Qu'en pensez-vous ?

    PS: koala01 a poste une alternative pour faire une ObjectFactory ==> ici <==.

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    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.

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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.

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    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 ?

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 :

    Pourquoi ne pas juste laisser le type de la classe directement ? Donc au lieu de faire ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <>
    struct ConcreteParameterList<d1_trait> : public ParameterList{
        int i;
    };
    Faire plutot ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #11
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    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 ? ).

  12. #12
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  13. #13
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    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 ?).
    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.

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

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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();
      }
      ...
    }
    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.

    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.

    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. 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...

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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 - Visualiser dans une fenêtre à part
    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
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    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<==
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. object factory et classes templates
    Par [Hugo] dans le forum C++
    Réponses: 11
    Dernier message: 03/01/2012, 18h33
  2. [JAXB] Génerer un object factory a partir d'un schema xml
    Par bel09 dans le forum Persistance des données
    Réponses: 0
    Dernier message: 02/06/2009, 17h24
  3. template, singleton et object factory
    Par [Hugo] dans le forum Langage
    Réponses: 13
    Dernier message: 04/05/2009, 14h53
  4. [Data Access Object]Intérêt de la factory ?
    Par le Daoud dans le forum Général Java
    Réponses: 2
    Dernier message: 21/04/2005, 10h06
  5. Comment inserer des donnee de type Large Object !!
    Par josoft dans le forum Requêtes
    Réponses: 4
    Dernier message: 20/07/2003, 12h21

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo