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

C++/CLI Discussion :

Design de polymorphisme lié à des if / else


Sujet :

C++/CLI

  1. #1
    Membre du Club
    Inscrit en
    Février 2013
    Messages
    92
    Détails du profil
    Informations forums :
    Inscription : Février 2013
    Messages : 92
    Points : 49
    Points
    49
    Par défaut Design de polymorphisme lié à des if / else
    Bonjour à toutes et à tous,
    Je parcourais Stack ce matin, et je suis tombé sur ce sujet de polymorphisme:
    https://stackoverflow.com/questions/...use-it-outside
    En particulier sur ce bout de code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Alma* Andor;
     
     if(b00lvar){
        Andor = new Birs();
        std::cout<<Andor->Getwidth()<<" "<<Andor->Getheight()<<std::endl;
      }else{
        Andor = new Citrom();  
        std::cout<<Andor->Getwidth()<<" "<<Andor->Getdepth()<<std::endl;
      }
    posté en réponse. On voit ici que l'initialisation de la classe dépend d'une variable 'b00lvar'. Je me posais la question d'un cas plus large, ou nous aurions une classe de base et beaucoup de classes filles, suivant le même schéma, par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    Alma* Andor;
     
    switch( une_variable ) {
      case 0 : { Andor = new Birs(param1, param2); }
      case 1 : { Andor = new Citrom(param1, param2, param3); }
      case 2 : { Andor = new Blabla(param1,param4,param5); }
      etc.
      }
    Est-il possible de généraliser ces multiples initialisations sous une forme plus élégante plutôt que d'avoir une liste de if/else en fonction d'un paramètre 'une variable' sans pour autant altérer la performance (en termes de temps de calcul) de l'ensemble ? Je ne vois pas de solution magique, mais j’admets que ma connaissance du langage est plutôt limité.

    Merci!

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 675
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 675
    Points : 10 689
    Points
    10 689
    Par défaut
    Cela ressemble furieusement au patron de conception fabrique/ usine (<- lien wikipedia en anglais)

    lien developpez

    Les 2 liens utilisent 1 tableau associatif pour stocker 1 couple objet/ clef "chaîne de caractères"

    Je voyais + comme celle ci sur cette page : on va mettre ton if/ else dans 1 méthode create.
    C'est du code C++11 avec des std::unique_ptr.
    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
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    #include<iostream>
    #include<memory>
     
    //Pizza
    class Pizza {
    public:
        virtual void BakePizza()=0; 
        virtual void PackPizza()=0;
    };
     
    class DominoPanerrPiza : public Pizza {
    public:
        void BakePizza() {
            std::cout<<"DominoPanerrPiza is ready"<<std::endl;
        }
        void PackPizza() {
            std::cout<<"DominoPanerrPiza is packed"<<std::endl;
        }
    };
     
    class DominoCheesePiza : public Pizza {
    public:
        void BakePizza() {
            std::cout<<"DominoCheesePiza is ready"<<std::endl;
        }
        void PackPizza() {
            std::cout<<"DominoCheesePiza is packed"<<std::endl;
        }
    };
     
    //class PizzaFactory
    class PizzaFactory {
    protected:
        std::unique_ptr<Pizza> _mPizza;
    public:
        Pizza* GetPizza(std::string type) {  
            _mPizza.reset(CreatePizza(type));
            return _mPizza.release();     
        }
    private:
        virtual Pizza* CreatePizza(std::string type)=0;
    };
     
    //DominoPizzaFactory
    class DominoPizzaFactory: public PizzaFactory {
    private:
        Pizza* CreatePizza(std::string type) {
            Pizza* pz = nullptr;
            if(type == "Paneer")
                pz = new DominoPanerrPiza;
            else if (type == "Cheese")
                pz = new DominoCheesePiza;
            else
                nullptr;
     
            return  pz;
        }
    };
     
    //Client
    int main()
    {
        std::cout<<"In Main"<<std::endl;
        //Select Factory
        std::unique_ptr<PizzaFactory>ptr{nullptr};
        ptr.reset(new DominoPizzaFactory);
        //Order the Pizza with type
        std::cout<<"Ordering Paneer Pizza from Domino"<<std::endl;
        std::unique_ptr<Pizza>upPz{nullptr};
        upPz.reset(ptr->GetPizza("Paneer"));
        upPz->BakePizza();
        upPz->PackPizza();
        std::cout<<"!!!!!!!!!!Got the Pizza!!!!!!!!!"<<std::endl;
        return 0;
    }
    Ou encore comme celle ci : on va surcharger autant d'usines que de types et ensuite, c'est au client d'utiliser soit la bonne usine surchargée soit le polymorphisme (ici injection de dépendances)
    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
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    /**
     * The Product interface declares the operations that all concrete products must
     * implement.
     */
    class Product {
     public:
      virtual ~Product() {}
      virtual std::string Operation() const = 0;
    };
     
    /**
     * Concrete Products provide various implementations of the Product interface.
     */
    class ConcreteProduct1 : public Product {
     public:
      std::string Operation() const override {
        return "{Result of the ConcreteProduct1}";
      }
    };
     
    class ConcreteProduct2 : public Product {
     public:
      std::string Operation() const override {
        return "{Result of the ConcreteProduct2}";
      }
    };
     
    /**
     * The Creator class declares the factory method that is supposed to return an
     * object of a Product class. The Creator's subclasses usually provide the
     * implementation of this method.
     */
    class Creator {
      /**
       * Note that the Creator may also provide some default implementation of the
       * factory method.
       */
     public:
      virtual ~Creator(){};
      virtual Product* FactoryMethod() const = 0;
     
      /**
       * Also note that, despite its name, the Creator's primary responsibility is
       * not creating products. Usually, it contains some core business logic that
       * relies on Product objects, returned by the factory method. Subclasses can
       * indirectly change that business logic by overriding the factory method and
       * returning a different type of product from it.
       */
     
      std::string SomeOperation() const {
        // Call the factory method to create a Product object.
        Product* product = this->FactoryMethod();
        // Now, use the product.
        std::string result = "Creator: The same creator's code has just worked with " + product->Operation();
        delete product;
        return result;
      }
    };
     
    /**
     * Concrete Creators override the factory method in order to change the
     * resulting product's type.
     */
    class ConcreteCreator1 : public Creator {
      /**
       * Note that the signature of the method still uses the abstract product type,
       * even though the concrete product is actually returned from the method. This
       * way the Creator can stay independent of concrete product classes.
       */
     public:
      Product* FactoryMethod() const override {
        return new ConcreteProduct1();
      }
    };
     
    class ConcreteCreator2 : public Creator {
     public:
      Product* FactoryMethod() const override {
        return new ConcreteProduct2();
      }
    };
     
    /**
     * The client code works with an instance of a concrete creator, albeit through
     * its base interface. As long as the client keeps working with the creator via
     * the base interface, you can pass it any creator's subclass.
     */
    void ClientCode(const Creator& creator) {
      // ...
      std::cout << "Client: I'm not aware of the creator's class, but it still works.\n"
                << creator.SomeOperation() << std::endl;
      // ...
    }
     
    /**
     * The Application picks a creator's type depending on the configuration or
     * environment.
     */
    int main() {
      std::cout << "App: Launched with the ConcreteCreator1.\n";
      Creator* creator = new ConcreteCreator1();
      ClientCode(*creator);
      std::cout << std::endl;
      std::cout << "App: Launched with the ConcreteCreator2.\n";
      Creator* creator2 = new ConcreteCreator2();
      ClientCode(*creator2);
     
      delete creator;
      delete creator2;
      return 0;
    }

    Édit: Après pour les différents types de paramètres pour le constructeur c'est vrai que cela pose problème
    Mais, dans 1 premier temps, on peut utiliser 1 usine avec donc des "lazy constructors". Et ensuite, dans chaque type concret, faire 1 méthode d'initialisation init (*) avec 1 DTO (1 structure par exemple) qui contient TOUS les paramètres (c'est (*) qui choisira quels paramètres lui convient)

    C'est d'ailleurs 1 peu la réponse de ton sujet Stack Overflow (qui parle de boost::variant, qui est 1 union), et qui met 1 booléen par paramètre pour savoir si la "classe mère" peut utiliser tel ou tel paramètre d'1 classe fille concrète.

  3. #3
    Membre du Club
    Inscrit en
    Février 2013
    Messages
    92
    Détails du profil
    Informations forums :
    Inscription : Février 2013
    Messages : 92
    Points : 49
    Points
    49
    Par défaut
    Bonjour,
    Tout d'abord merci pour ce retour rapide!
    Les liens correspondent exactement à ma question, je ne connaissais pas du tout cette architecture de factory/fabrique, c'est vraiment intéressant!
    Effectivement j'ai mis des constructeurs différents pour les classes filles, en me disant que ce cas là pouvait se produire aussi, rajoutant de la complexité à l'ensemble. Je vois l'édit et je me pose une autre question, plus liée à la performance (en termes de running-time du code): si on passe tous les paramètres lors de l'initialisation et qu'on imagine que ces classes sont initialisés dans un thread, ca ne risque pas de ralentir l'ensemble? Autrement dit, passer tous les paramètres qui peuvent être des objets "complexes"/lourds vs. la liste nécessaire à la construction de chaque fille ?

    Merci pour les informations!

  4. #4
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 382
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 382
    Points : 41 588
    Points
    41 588
    Par défaut
    Pour l'instant, il n'est pas de question de multithreading ici: Tout se fait dans le thread courant.

    Après, un exemple plus complexe consisterait à se passer des switch complètement:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    std::map<std::string, std::unique_ptr<IPizzaFactory>> factories;
    factories["Domino"] = make_unique<DominoPizzaFactory>();
    factories["PizzaDelArte"] = make_unique<PizzaDelArteFactory>();
     
    ...
     
    UniversalPizzaRequest whatIWant {};
    factories["Domino"]->CreatePizza(whatIWant);
    On peut même envisager que les factories soient implémentées dans des DLLs, et listées dans un fichier de configuration.
    Ou encore, de choisir la factory non pas via son nom, mais ces capacités (genre chaque factory supporte une certaine liste de toppings, la requête contient elle aussi une liste, et une fonction de recherche retournerait la première factory trouvée qui supporte tous les toppings de la requête).

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 675
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 675
    Points : 10 689
    Points
    10 689
    Par défaut
    Si c'est 1 question de "threads", il faut donc tester : en l'état impossible de dire quoi ce soit

    Mais les DTO (Data Transfer Object) sont justement utilisés pour transmettre des données (0 logique) (il ne restent pas ""en mémoire"")
    En C++, 1 structure est 1 classe publique, et donc bénéficie des constructeur/ destructeur.

    Ensuite pour la taille, tu mets des pointeurs ("smart "ou "raw") dans tes DTO. Si ce sont de gros objets, il faut les créer à 1 moment ou 1 autre et donc appeler "1 constructeur" et donc pointeur
    1 pointeur c'est 8 octets (64 bits) ce n'est pas grand chose.
    Tu as aussi les union qui permettent d'optimiser et de séparer les différentes "combinaisons" de paramètres (la taille d'1 union, c'est la taille de la plus grosse "donnée").

  6. #6
    Membre du Club
    Inscrit en
    Février 2013
    Messages
    92
    Détails du profil
    Informations forums :
    Inscription : Février 2013
    Messages : 92
    Points : 49
    Points
    49
    Par défaut
    Merci à tous pour ces réponses éclairantes!
    Je marque comme résolu

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Les design pattern pour créer des jeux
    Par alex6891 dans le forum Design Patterns
    Réponses: 4
    Dernier message: 26/11/2018, 20h59
  2. Réponses: 4
    Dernier message: 06/03/2008, 11h22
  3. [2.0] polymorphisme avec des types générique
    Par mister3957 dans le forum Général Dotnet
    Réponses: 3
    Dernier message: 31/05/2007, 09h11
  4. Problème de formulaire avec des If-Else
    Par MasterChief78 dans le forum Access
    Réponses: 21
    Dernier message: 29/08/2006, 13h54

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