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++ Discussion :

Factory : factorisation pour la fabrication


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 99
    Par défaut Factory : factorisation pour la fabrication
    Bonjour,

    d'une manière générale, lorsqu'on a une application conçue sous forme de modules, il semble intéressant d'exploiter le patron de conception Factory pour regrouper la création de ces derniers et gérer leur cycle de vie (destruction par exemple).

    Comme les modules ont la plupart du temps une logique adoptée par le développeur au niveau de leur nom, on est tenté de vouloir factoriser le code de création sous une forme proche de :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    #define CREATE_MODULE(Name) \
    return new Module##Name();
    L'idée serait donc de profiter de la concaténation par une directive de préprocesseur.
    Or, ceci n'est pas toujours approuvé par les développeurs.
    L'une des raisons pourrait être la possibilité d'utiliser CREATE_MODULE n'importe où (à condition d'inclure le header). Peut être faudrait-il donc garder l'idée en exposant dans le define quelque chose de moins sensible comme juste un GetTypeModule(Name) qui retournerait Module##Name ?

    Qu'en pensez-vous ?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    En fait, tu parles spécifiquement des modules, mais les modules en eux-même ne sont pas forcément le problème.

    Quand on décide de découper une application en modules, on ne fait que se mettre dans une position idéale pour créer une séparation plus ou moins artificielle (mais tout à fait logique) des différentes fonctionnalités offertes par l'ensemble de l'application.

    Typiquement, un module va :
    1. Etre, a priori, totalement indépendant des autres modules, ou, du moins, être en mesure de fonctionner sans avoir besoin d'autres modules particuliers (exception faite éventuellement du module regroupant les données métier, et encore)
    2. Regrouper un ensemble de fonctionnalités qui "vont bien ensembles" car elles suivent un objectif commun (le rendu sonore, le rendu visuel, la communication par le réseau, la sérialisation, la gestion des données métiers, ...)
    3. Servir de "passerelle" permettant aux autres modules d'accéder aux seules fonctionnalités indispensables de "transmission de l'information" entre les modules
    4. maintenir, de manière transparente pour l'utilisateur (du module) l'ensemble des données nécessaire à l'utilisation des fonctionnalités envisagée
    5. n'exister jamais que sous la forme d'une instance unique afin d'éviter les éventuels problèmes de synchronisation entre les instances
    6. être initialisé "très tôt" dans l'application : généralement, avant même que l'on ne commence à manipuler réellement les données métier ou -- de manière beaucoup plus générale -- à faire communiquer différents modules entre eux.
    Ces différents points méritent pour certains une explication, et, pour d'autres, il est possible d'en tirer certaines conclusions. Voici la manière dont j'envisage les choses :

    Le point (1) est très important parce qu'il impose plusieurs conclusions:

    La plus importante est sans doute le fait qu'il n'y a aucune raison de créer une classe de base -- mettons AbstractModule -- dont hériteraient publiquement les différents modules.

    Si l'on n'a pas de classe de base, on n'a pas besoin du polymorphisme d'inclusion, et, si l'on n'a pas besoin du polymorphisme d'inclusion, on n'a pas besoin de l'allocation dynamique de la mémoire pour la création des différents modules. On pourra (le cas échéant) les transmettre sous la forme de référence (ce sera obligatoire pour assurer l'unicité référentielle), mais on peut parfaitement les créer par valeur.

    On n'a donc pas besoin d'une "fabrique de module". La directive préprocesseur devient alors tout bonnement inutile car un simple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main(){
        RenderModule render; // le module de rendu d'affichage
        SoundModule sound; // le module de rendu sonore
        LanModule lan; // le module de communication "vers l'extérieur"
        BusinessModule business; // le module de gestion des données métier
        /* ... */
    }
    suffit plus qu'amplement

    Les points (2) et (3) méritent une explication conjointe. La première chose à constater, c'est qu'un module ne va très certainement pas exposer l'ensemble des fonctionnalités et des données qu'il manipule "en interne" : il va exposer certains signaux capables de transmettre un message "à qui est intéressé par la teneur du message" (ex: monstre 3542 attaque avec le sort 24), quelques fonctions qui permettront de transmettre des ordres bien particulières (ex : charges les musiques du niveau 2), et c'est tout!

    Cela nous permet de respecter le DIP parce qu'un signal reste un signal, quelle qu'en soit la forme, quelle que soit la teneur du message émis.

    Si l'un des message est "monstreAttaque(id_monstre, id_sort)", le module business, le module son et le module d'affichage pourront s'y connecter afin que, lorsqu'il est émis, le module business crée le sort, avec comme origine la position du monstre, le module d'affichage sélectionne le sprite correspondant au sort pour l'afficher et le module son émet le son spécifique du sort lancé. Notes d'ailleurs que le module réseau pourrait lui aussi s'y connecter afin de transmettre cette information

    Cela permet aussi de respecter le SRP : le sort peut être limité à sa plus simple expression parce que la seule chose commune qui intéresse réellement tous les modules, c'est l'identifiant du sort (le sort numéro 24 dans mon exemple), sa position et éventuellement quelques données connexes comme la direction, l'orientation et la vitesse de déplacement.

    Le module business pourra créer une donnée qui l'intéresse réellement et qui contient la position du sort et les dégats qu'il provoque si on est touché, le module d'affichage pourra y associer un "volume" à afficher ainsi que des textures, le module son pourra y associer les sons lorsque le sort est lancé et lorsqu'il touche quelque chose et le module réseau pourra se contenter des de transmettre les informations relatives à sa position, sa trajectoire et son identifiant

    Le point (4) fera idéalement appel à un mécanisme de fabrique (en plus d'un mécanisme de maintient des données). Mais ce qu'il est important de constater, c'est que ces mécanismes ne sont que de la "popote interne" au module : chaque module peut parfaitement travailler avec ses propres types de données, qui seraient totalement (ou du moins, dans une très large mesure) différents des types de données manipulés par les autres modules. Et pour cause : je viens de t'expliquer que chaque module donnera un sens qui lui est propre au "sort numéro 24".

    Le point (5) et le point (6) correspondent très certainement l'expression du besoin qui a donné naissance à l'anti-pattern désigné sous le terme de "singleton" par le GoF.

    Chaque module ne doit effectivement exister qu'une seule fois. Chaque module peut, effectivement, être considéré comme global dans le sens où il doit être créé "dés le lancement de l'application" et où il doit continuer d'exister "jusqu'à la fin de l'application".

    Mais on se rend compte que ces deux limites ne font au final que définir une portée bien particulière : celle de l'application. Et l'on se rend aussi compte qu'il n'y a qu'à l'intérieur de cette portée particulière qu'il est important de connaître l'ensemble des modules, afin de pouvoir générer les différentes interconnexions qui existent entre les différents modules.

    Quand on sait que le meilleur moyen pour s'assurer de l'unicité d'un objet est, d'abord et avant tout de s'assurer qu'il n'y aura jamais qu'une seule variable correspondant à cet objet particulier, on se rend compte qu'il est particulièrement facile d'éviter le recours au singleton. Cela se fait en trois étapes:
    1. Rendre les types non affectables et non copiables.
    2. Créer une portée spécifique qui contient les différents modules.
    3. Gérer les relations entre les modules au niveau de cette portée générale uniquement
    La première étape est facile à franchir : le mécanisme permettant de rendre une classe non copiable et non affectable est bien connu depuis longtemps.

    Cela peut passer par l'utilisation de boost::non_copyable ou d'une classe équivalente écrite par toi, par l'utilisation de la possibilité offerte par C++11 de déclarer les fonctions delete ou par la technique "à l'ancienne" qui consiste à déclarer sans les définir le constructeur de copie et l'opérateur d'affectation dans l'accessibilité privée.

    La deuxième étape n'est pas plus compliquée. La portée spécifique peut être la fonction main ou une classe spécifique (comme la classe Application (ou nom similaire) proposée par les bibliothèques d'IHM).

    Enfin, la troisième étape est, peut être, celle qui te demandera le plus d'attention : elle nécessite que les différents modules soient, réellement, indépendants les uns des autres

    Je crois avoir fait le tour de ce qu'il faut prendre en considération pour les modules et, par là, avoir démontré l'inutilité de la directive préprocesseur que tu proposes

    Notes enfin que j'ai, à titre purement personnel, horreur des directives préprocesseur. Elle présentent, je dois l'avouer, un avantage majeur qui est de faciliter énormément l'écriture. Mais cet avantage est largement contrebalancé par le fait qu'elles cachent ce qui est réellement fait à celui qui les utilise.

    Du coup, lorsqu'une erreur de compilation survient, avec "un peu de chance", on se retrouve avec une erreur de 2500 lignes qui développe l'ensemble des directives préprocesseurs par lesquelles ont est passé pour obtenir le résultat erroné. Mais c'est difficile à décrypter. Avec "un peu de malchance", on obtient juste une ligne d'erreur qui correspond à l'erreur telle qu'elle se serait présentée si on n'avait pas eu recours à la directive préprocesseur, et il est particulièrement difficile de "refaire tout le chemin" d'évaluation de la dite directive.

    Je tiens à préciser que ce n'est qu'un avis strictement personnel et qu'il n'engage donc bien évidemment que moi. Mais je le partage
    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 confirmé
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 99
    Par défaut
    La plus importante est sans doute le fait qu'il n'y a aucune raison de créer une classe de base -- mettons AbstractModule -- dont hériteraient publiquement les différents modules.

    Si l'on n'a pas de classe de base, on n'a pas besoin du polymorphisme d'inclusion, et, si l'on n'a pas besoin du polymorphisme d'inclusion, on n'a pas besoin de l'allocation dynamique de la mémoire pour la création des différents modules. On pourra (le cas échéant) les transmettre sous la forme de référence (ce sera obligatoire pour assurer l'unicité référentielle), mais on peut parfaitement les créer par valeur.

    On n'a donc pas besoin d'une "fabrique de module". La directive préprocesseur devient alors tout bonnement inutile
    Logique imparable .
    En fait, on peut quand même trouver des fonctionnalités communes entre les différents modules comme par exemple une méthode setConfig. On aurait donc une classe de base même si elle resterait très succincte.
    Aussi, peut être que dans ce cas l'appellation fabrique est mal choisie mais j'envisageais de créer un objet de construction qui aurait connaissance de la config et pourrait distribuer les paramètres spécifiques à chaque module lors de leur création.

    lorsqu'une erreur de compilation survient, avec "un peu de chance", on se retrouve avec une erreur de 2500 lignes qui développe l'ensemble des directives préprocesseurs par lesquelles ont est passé pour obtenir le résultat erroné. Mais c'est difficile à décrypter. Avec "un peu de malchance", on obtient juste une ligne d'erreur qui correspond à l'erreur telle qu'elle se serait présentée si on n'avait pas eu recours à la directive préprocesseur, et il est particulièrement difficile de "refaire tout le chemin" d'évaluation de la dite directive.
    C'est vrai que si on utilise de façon abusive les directives, ça peut devenir rapidement illisible et difficile à déboguer. Après, si ça reste très ciblé et explicite, la puissance que ça apporte peut en valoir le détour.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par sfarc Voir le message
    Logique imparable .
    En fait, on peut quand même trouver des fonctionnalités communes entre les différents modules comme par exemple une méthode setConfig. On aurait donc une classe de base même si elle resterait très succincte.
    Quelle horreur!!!

    Il y a deux (et même trois, à bien y réfléchir) raisons pour lesquelles ce serait une très mauvaise chose :

    La première, c'est la loi de Déméter : En créant une fonction setConfig, tu obliges l'utilisateur de ton module à connaître un détail d'implémentation de ton module qu'il n'a normalement pas à connaître.

    Au pire, si le module expose la possibilité de modifier des options de configurations, ce seraient des fonctions très spécifiques (modifyXXXconfigValue(Type valeur) ) qui permettront à l'utilisateur du module (ou de la façade au travers de laquelle le module est manipulé) de n'avoir pas à s'inquiéter de la forme que prend la configuration à l'intérieur du module en lui-même.

    La deuxième raison, c'est que la présence d'une fonction similaire dans différentes classes n'implique pas forcément qu'il doit y avoir un lien de sororité ou d'héritage entre deux classes.

    Imagines une classe InventoryItem, qui serait la classe de base pour "tout élément susceptible de prendre place dans l'inventaire" d'une société et une classe Employee qui représenterait les employés de cette société.

    Ces deux classes pourraient avoir comme caractéristique d'être identifiable de manière unique et non ambigüe par une fonction int id() const. Mais ce n'est absolument pas une raison pour envisager la possibilité de créer une collection d'objets qui mélangerait allègrement des employés et des éléments de l'inventaire:

    Ce n'est pas parce que ces deux classes présentent une caractéristique commune que l'héritage public devient cohérent. Dans le meilleur des cas, tu peux considérer que la fonction id() fait partie d'une interface spécifique, mais tu ne peux absolument pas considérer que l'utilisation de cette interface spécifique en vienne à créer une relation telle que, d'une manière ou d'une autre, les employés et les éléments d'inventaires puissent intervenir dans la même hiérarchie de classes.

    Notes qu'il y a tout à fait moyen d'arriver à avoir une interface commune mais sans créer cette relation de hiérarchie de classe en C++, en créant une interface à base de classe template et en profitant du CRTP:
    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
    template <typename T>
    class TIdentifiable{
        public:
            size_t id() const{return id_;}
        protected:
            template<typename ValueType>
            TIdentifiable(ValueType const& idKey):id_(std::ash(idKey)){}
            ~TIdentifiable(){}
        private:
            size_t id_;
    };
    class Employee : public TIdentifiable<Employee>{
        public:
            Employee(std::string const & name):TIdentifiable<Employee>(name){}
    };
    class InventoryItem : public TIdentifiable<InventoryItem>{
        public
            InventoryItem(size_t productNumber): TIdentifiable<InventoryItem>(productNumber){}
    };
    Les deux exposent bel et bien une fonction id(), mais tu ne pourras jamais transmettre un objet d'un type quand c'est un objet de l'autre type qui est attendu.

    Et, comme je l'ai dit, il y a une troisième raison qui devrait t'inciter à éviter la logique que tu envisages : les informations nécessaires à la configuration d'un module sont spécifiques au module en question : pour le module de rendu visuel, ce seront des options de résolution, de couleurs, de transparence ou autres, alors que pour le module son, ce seront des options relatives au volume, au nombre de haut parleurs ou que sais-je et que pour le module réseau, ce seront des options comme le nom d'utilisateur, l'adresse du serveur à contacter ou le mot de passe.

    Cela signifie que tu devrais transmettre une abstraction qui corresponde à "n'importe quel ensemble d'informations de configuration" à ta fonction setConfig afin de pouvoir en profiter.

    Le résultat est que tu finiras invariablement face à la nécessité de "jouer" avec les dynamic_cast pour déterminer si, oui ou non, l'ensemble d'options de configuration auquel tu es confronté est cohérent avec les options dont le module a besoin (sous peine d'essayer de transmettre les options de configuration du module d'affichage au module son ). Et tu perdras d'un seul coup toutes les possibilités d'évolutions que la modularisation te permettait pourtant d'espérer
    Aussi, peut être que dans ce cas l'appellation fabrique est mal choisie mais j'envisageais de créer un objet de construction qui aurait connaissance de la config et pourrait distribuer les paramètres spécifiques à chaque module lors de leur création.
    Les options de configuration sont du seul recours du module lui-même.

    Soit, il charge en interne un fichier dont le nom lui est donné "par défaut ou par ailleurs" (ailleurs étant dans le cas présent une fonction proche de loadConfig(filename) ), soit il expose des fonctions qui permettent de modifier spécifiquement une information de configuration particulière ( modifyConnectionUserName(newUserName) ou modifyScreenResolution(int resX, int resY, int deep) par exemple)

    Quoi qu'il en soit, comme les options de configuration de chaque module sont strictement adaptées au module en question, il n'y a aucun héritage envisageable, et il n'y a pas nécessité ni possibilité de faire en sorte que tous les modules interviennent dans une hiérarchie de classe commune
    C'est vrai que si on utilise de façon abusive les directives, ça peut devenir rapidement illisible et difficile à déboguer. Après, si ça reste très ciblé et explicite, la puissance que ça apporte peut en valoir le détour.
    Attention, je ne nie absolument pas la puissance des possibilités offertes par les directives préprocesseur! Mon avis personnel est juste que la technique présente souvent plus d'inconvénients que d'avantages réels, et que, dans le cas particulier que tu proposes, les inconvénients sont plus importants que les avantages espérés
    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 confirmé
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 99
    Par défaut
    La deuxième raison, c'est que la présence d'une fonction similaire dans différentes classes n'implique pas forcément qu'il doit y avoir un lien de sororité ou d'héritage entre deux classes.
    Ce n'est pas parce que ces deux classes présentent une caractéristique commune que l'héritage public devient cohérent.
    Le lien n'est peut être pas assez conséquent mais on aurait quand même des modules qui recevraient une configuration, donc une fonctionnalité commune et on resterait dans une logique de module.

    La première, c'est la loi de Déméter : En créant une fonction setConfig, tu obliges l'utilisateur de ton module à connaître un détail d'implémentation de ton module qu'il n'a normalement pas à connaître.
    En fait non, je voulais surtout créer une classe de fabrique pour lui confier le rôle de distribuer la configuration à chaque module. L'utilisateur aurait, dans mon idée, juste à faire un createModule avec le nom du module. Après c'est vrai que la méthode setConfig qui serait exposée va à l'encontre de mes propos mais dans ce cas, peut être déclarer amie la fabrique auprès de chaque module et rendre priver le setConfig ? C'est peut être un peu tiré par les cheveux remarque, parce que l'intérêt serait vraiment limité à la relation d'amitié et la méthode setConfig rendue privée n'aurait pas d'autre raison d'être.

    Et, comme je l'ai dit, il y a une troisième raison qui devrait t'inciter à éviter la logique que tu envisages : les informations nécessaires à la configuration d'un module sont spécifiques au module en question : pour le module de rendu visuel, ce seront des options de résolution, de couleurs, de transparence ou autres, alors que pour le module son, ce seront des options relatives au volume, au nombre de haut parleurs ou que sais-je et que pour le module réseau, ce seront des options comme le nom d'utilisateur, l'adresse du serveur à contacter ou le mot de passe.

    Cela signifie que tu devrais transmettre une abstraction qui corresponde à "n'importe quel ensemble d'informations de configuration" à ta fonction setConfig afin de pouvoir en profiter.

    Quoi qu'il en soit, comme les options de configuration de chaque module sont strictement adaptées au module en question, il n'y a aucun héritage envisageable, et il n'y a pas nécessité ni possibilité de faire en sorte que tous les modules interviennent dans une hiérarchie de classe commune
    C'est peut être pas terrible mais l'une de mes idées était de passer une map contenant les paramètres et leur valeur à la méthode commune setConfig. Ensuite, à l'intérieur de chaque module, on récupérerait de cette map la configuration spécifique.
    Pour la récupération de la map, on aurait un objet config sur lequel la fabrique ferait un getParams avec en paramètre le nom du module à considérer au moment même de sa création.
    Après je comprends que l'idée d'avoir un objet commun ne soit pas tout à fait logique étant donné que chaque module à sa propre configuration. Peut être que je vais rester finalement sur le chargement d'un fichier par module en interne directement.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    [EDIT] oupps, je m'a trompé de bouton : envoyer au lieu de prévisualiser
    Citation Envoyé par sfarc Voir le message
    Le lien n'est peut être pas assez conséquent mais on aurait quand même des modules qui recevraient une configuration, donc une fonctionnalité commune et on resterait dans une logique de module.
    Cela irait dans "une logique de module", mais cela irait à l'encontre au LSP (Liskov Substitution Principle, ou, si tu préfères, au principe de substitution de Liskov).

    En programmation orientée objet, le meilleur moyen d'obtenir quelque chose de totalement ingérable, d'impossible à faire évoluer "sans tout casser" et de très difficile à débuger est de ne respecter ni la loi de Déméter ni les principes SOLID.

    Le seul service que tu pourrais avoir en commun pour l'ensemble des modules serait un service (setConfig(AbstractConfiguration * config) ), mais cela irait à l'encontre de la loi de Déméter, et je t'ai expliqué pourquoi ce serait une très mauvaise idée d'y recourir. (si tu n'as pas compris mon argumentation, ou si tu n'es pas tout à fait d'accord avec elle, n'hésites pas à me le faire savoir, cela débouche souvent sur un débat passionnant )

    Aux termes même de LSP, il y a énormément de chances pour que tu te retrouves à vouloir déclarer des services à l'intérieur de ta classe de base qui ne seront, quoi qu'il arrive, réellement utilisables qu'au niveau d'un module spécifique. Ce faisant, tu rajoutes un invariant du style "Tout module expose un service XXX" qui n'est absolument pas respecté par les différents modules, vu que cet invariant n'est valable que pour un module bien particulier. La règle de programmation par contrat qui t'impose que les invariants soient respectés ne l'est pas, l'héritage n'a pas lieu d'être
    (c'est, en gros, la même raison qui fait qu'une classe ListeTriee ne peut pas hériter de la clase Liste ou que celle qui fait que la classe Carre ne peut pas hériter de la classe Rectangle )
    En fait non, je voulais surtout créer une classe de fabrique pour lui confier le rôle de distribuer la configuration à chaque module. L'utilisateur aurait, dans mon idée, juste à faire un createModule avec le nom du module.
    Mais pourquoi vouloir passer par une fabrique alors qu'il est si facile de créer une instance de chaque module

    Ne crois tu pas qu'il est plus facile d'avoir quelque chose ressemblant à (c++11 inside)
    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
    class RenderModule{
    public:
        RenderModule():RenderModule("render.ini"){
        }
        RenderModule(std::string const & filename):configFilename_(filename){
            loadConfig();
        } 
        void loadConfig(std::string const & filename){
           configFilename_=filename;
           loadConfig();
        }
    private :
        void loadConfig(){
            /* ... */ 
        }
        std::string configFilename_;
    }
    class SoundModule{
    public:
        SoundModule():SoundModule("sound.ini"){
        }
        SoundModule(std::string const & filename):configFilename_(filename){
            loadConfig();
        } 
        void loadConfig(std::string const & filename){
           configFilename_=filename;
           loadConfig();
        }
    private :
        void loadConfig(){
            /* ... */ 
        }
        std::string configFilename_;
    }
    class LanModule{
    public:
        LanModule():LanModule("lan.ini"){
        }
        LanModule(std::string const & filename):configFilename_(filename){
            loadConfig();
        } 
        void loadConfig(std::string const & filename){
           configFilename_=filename;
           loadConfig();
        }
    private :
        void loadConfig(){
            /* ... */ 
        }
        std::string configFilename_;
    };
    int main(){
        SoundModule sound;    // ici, on utilise le fichier de configuration par défaut
        LanModule lan("mySpecialLanConfig.ini"); // et ici, un fichier particulier
        RenderModule render;
    }
    que d'avoir 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
    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
    class AbstractModule{
    public:
        AbstractModule(){
        }
        virtual ~AbstractModule(){}
        void loadConfig(std::string const & filename){
           configFilename_=filename;
           loadConfig();
        }
    protected:
       std::string const & configFilename() const{return configFileName_;}
    private :
        virtual void loadConfig() = 0;
        std::string configFilename_;
    };
     
    class RenderModule : public AbstractModule{
    public:
        RenderModule(){
        }
    private :
        void loadConfig(){
            /* ... */ 
        }
    }
    class SoundModule : public AbstractModule{
    public:
        SoundModule(){
        }
    private :
        void loadConfig(){
            /* ... */ 
        }
    }
    class LanModule : public AbstractModule{
    public:
        LanModule(){
        }
    private :
        void loadConfig(){
            /* ... */ 
        }
    };
    class ModuleFactory{
        public:
        AbstractModule * createSoundModule(){
            return new SoundModule;
        }
        AbstractModule * createRenderModule(){
            return new RenderModule;
        }
        AbstractModule * createLanModule(){
            return new LanModule;
        }
    };
     
     
    int main(){
        ModuleFactory factory;
        std::vector<AbstractModule *> allModules;
        allModules.push_back(factory.createSoundModule);
        allModules.push_back(factory.createRenderModule);
        allModules.push_back(factory.createLanModule);
        /* ... */
       allModules[0]->loadConfig("render.ini"); // Pas de bol : c'est un SoundModule :-S
    }
    Après c'est vrai que la méthode setConfig qui serait exposée va à l'encontre de mes propos mais dans ce cas, peut être déclarer amie la fabrique auprès de chaque module et rendre priver le setConfig ?
    Moi je te propose déjà de te passer de la fabrique, qui n'a purement et simplement pas lieu d'être si tu n'as pas d'héritage.

    L'héritage public est la relation la plus forte qui puisse exister entre deux types d'objets : celle qui permet de faire passer "n'importe quel objet d'un type dérivé" pour un objet du type de base.

    Vu que c'est la relation la plus forte qui existe, c'est celle qui ne doit être envisagée que lorsque l'on est parfaitement sûr qu'elle est utile, intéressante et sensée
    C'est peut être pas terrible mais l'une de mes idées était de passer une map contenant les paramètres et leur valeur à la méthode commune setConfig. Ensuite, à l'intérieur de chaque module, on récupérerait de cette map la configuration spécifique.
    Encore une fois, pourquoi vouloir mélanger les torchons et les serpillières

    La création d'une map <nom de l'option, valeur de l'option> va t'obliger à avoir recours à des mécanismes particulièrement contraignants. Ne serait-ce que pour t'assurer que la valeur que tu veux indiquer pour une clé donnée correspond au type attendu pour cette donnée.
    Pour la récupération de la map, on aurait un objet config sur lequel la fabrique ferait un getParams avec en paramètre le nom du module à considérer au moment même de sa création.
    Ce n'est de toutes manières pas le role de la fabrique.

    Idéalement, nous pourrions même aller plus loin en insistant sur le fait que chaque module devrait être utilisable dés le moment où il a été créé (que ce soit avec new dans une fabrique ou en déclarant simplement une variable du type adéquat comme je te le propose), c'est à dire, qu'il ne faudrait même pas que la fabrique ait besoin d'appeler la moindre fonction (ou toute utilisation équivalente des membres privés du module) du module entre le moment où elle le crée avec new et le moment où elle renvoie le pointeur
    Après je comprends que l'idée d'avoir un objet commun ne soit pas tout à fait logique étant donné que chaque module à sa propre configuration.
    C'est surtout que, à part les éventuels services tout à fait génériques qui seraient getConfig et setConfig, il n'y a strictement aucun service commun entre tes différents modules!

    La fonction getConfig pourrait éventuellement s'envisager afin d'être en mesure de récupérer la configuration propre à chaque module "quelque part" (qui ne soit dans aucun module en particulier) et de créer un fichier de configuration au départ de toutes ces données aggrégées, Mais setConfig n'a strictement aucun sens

    Et, encore une fois, ce n'est absolument pas parce que deux classes exposent un service similaire qu'il faut obligatoirement envisager le fait qu'elles fassent partie d'une hiérarchie de classe commune
    Peut être que je vais rester finalement sur le chargement d'un fichier par module en interne directement.
    Ceci dit, rien ne t'empêche d'avoir un seul et même fichier qui reprendrait l'ensemble des configurations des différents modules, 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
    [screen]
       resolution = 1920 x 1200
       color_machin = RRGGBBTT
       color_bidule = RRGGBBTT
       ...
    [sound]
       ambiance = 98%
       ffx =50%
       speech = 100%
       ...
    [lan]
        server = http://
        username = koala01
        ...
    [ ... ]
    et de faire en sorte que chaque module ne s'intéresse qu'à la "section" qui a du sens pour lui

    Un accesseur permettant de récupérer l'ensemble des informations de configuration pourrait, dans de ce cas, faciliter le travail lorsqu'il s'agit de sauvegarder la configuration
    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. [PR-2010] ms project pour atelier fabrication mecanique
    Par sabeurchebil dans le forum Project
    Réponses: 1
    Dernier message: 18/03/2014, 12h33
  2. [PC fixe] Conseil pour la fabrication d'une tour
    Par DannyM9 dans le forum Ordinateurs
    Réponses: 1
    Dernier message: 10/09/2013, 08h49
  3. Modélisation BDD pour traçabilité fabrication
    Par head059 dans le forum Modélisation
    Réponses: 12
    Dernier message: 15/08/2012, 17h47
  4. Réponses: 0
    Dernier message: 26/02/2009, 22h06
  5. Réponses: 4
    Dernier message: 06/11/2003, 10h37

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