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 :

SRP vs OCP - fondamentaux peu solides


Sujet :

C++

  1. #21
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 704
    Points
    2 704
    Par défaut
    @rod : admettons que tu stockes toutes tes formes dans un vecteur, mais que tu ne sais pas encore à ce moment là quelle bibliothèque d'affichage tu vas utiliser (cf DP Stratégie). Cela appelle immédiatement au polymorphisme dynamique, avec inclusion de la fonction d 'affichage dans la classe.

    Je n'ai pas le nom du DP en tête, mais en donnant comme membre à la classe un pointeur vers une interface abstraite, et en affectant ce pointeur à une instance concrète déduite via l'utilisation d'une Stratégie, on devrait satisfaire le SRP et lOCP.

  2. #22
    Membre expérimenté

    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2011
    Messages
    685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2011
    Messages : 685
    Points : 1 418
    Points
    1 418
    Par défaut
    Citation Envoyé par oodini Voir le message
    Non. Je m'interrogeais sur le fait de les mettre dans la même classe.
    Après tout, une des fonctions pourrait très bien modifier un membre privé utilisé par l'autre fonction.
    D'accord. Mais qu'une des deux méthodes modifient un membre privé utilisé par l'autre n'est pas du tout en contradiction avec SRP et OCP. Par exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    myRec = new Rectangle();
    //[...] dans une boucle plus loin
    myRec->updatePosition(); //calcul trajectoire et nouvelle position => modifie la pair (x,y) utilisée pour draw
    Window.draw(myRec); //ici, window.draw va appeler le service draw de la classe rectangle
    Dans mon petit exemple, updatePosition à pour seule responsabilité la mise à jour de la position de mon Rectangle, et draw rend le service unique "dessine moi". SRP est respecté, OCP aussi si on imagine draw et updatePosition virtuelles pour permettre l'évolution potentielle de ces dernières.

    Ce n'est pas ce que dit la littérature :
    A class should have one, and only one, reason to change.
    Je t'avoue que je ne comprends pas trop cette formulation :/

    Exact. Mais je n'ai pas encore d'opinion. Je lis, et j'essaye de comprendre.
    Moi aussi
    Nullius in verba

  3. #23
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par oodini Voir le message
    A class should have one, and only one, reason to change.
    Note l'utilisation de "should" (pas "must").
    Je n'aime pas cette phrase. Elle ne veut rien dire selon moi. Imagine si tu créée une classe Rectangle dont la seule responsabilité est de s'afficher dans un contexte donné. Cet objet a besoin de données pour s'afficher (couleur et position disons). Si on applique cette phrase, alors il faudrait que couleur et position soient deux classes distinctes de Rectangle, et sans agrégation! ça n'a pas de sens.
    Il faut choisir une granularité qui convient au contexte précis du programme. Personne ne va t'insulter si tu as une structure POD de base qui implémente une fonction to_string(). ça ne rentre pas dans les grands principes, mais ça ne change rien à ton programme et il serait ridicule de créer 3 classes supplémentaires bourrées de templates pour gérer la transformation de ton objet en string alors que c'est juste pour le mettre dans ton fichier de log.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  4. #24
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par oodini Voir le message
    @rod : admettons que tu stockes toutes tes formes dans un vecteur, mais que tu ne sais pas encore à ce moment là quelle bibliothèque d'affichage tu vas utiliser (cf DP Stratégie). Cela appelle immédiatement au polymorphisme dynamique, avec inclusion de la fonction d 'affichage dans la classe.
    Nan mais là il y a un truc qui ne colle pas. Ou alors je passe à côté.
    Tout d'abord, un objet a besoin du contexte pour s'afficher (par exemple une RenderWindow dans la SFML, ou un Widget, pour prendre un exemple plus général). En gros, la fonction Draw ressemblera à ceci: Draw( Widget & context );
    Donc cette fonction dépend totalement de la bibliothèque. Je ne vois donc pas comment un objet pourrait ignorer, à la compilation, dans quel contexte il va s'afficher.

    Citation Envoyé par oodini Voir le message
    Je n'ai pas le nom du DP en tête, mais en donnant comme membre à la classe un pointeur vers une interface abstraite, et en affectant ce pointeur à une instance concrète déduite via l'utilisation d'une Stratégie, on devrait satisfaire le SRP et lOCP.
    Une abstract factory?
    Attention, tu utilises le mot "interface" au sens java. En c++, l'interface d'une classe est l'ensemble de ses fonctions membres, des ses variables membres et de ses amis. La notion d'interface au sens java n'a pas vraiment de sens en c++. On peut parler de classe abstraite, mais ce n'est quand même pas la même chose.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  5. #25
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par r0d Voir le message
    Nan mais là il y a un truc qui ne colle pas. Ou alors je passe à côté.
    Tout d'abord, un objet a besoin du contexte pour s'afficher (par exemple une RenderWindow dans la SFML, ou un Widget, pour prendre un exemple plus général). En gros, la fonction Draw ressemblera à ceci: Draw( Widget & context );
    Donc cette fonction dépend totalement de la bibliothèque. Je ne vois donc pas comment un objet pourrait ignorer, à la compilation, dans quel contexte il va s'afficher.
    Tu n'ignoreras sans doute pas dans quel contexte d'affichage tu vas te trouver au moment où tu vas compiler.

    Par contre, la question qu'il est surement intéressant de te poser est sans doute "tiens que se passerait-il si ... je venais à vouloir passer de Qt à SFML ou à une autre bibliothèque "

    Les données métier que tu as à afficher sont strictement identiques, mais le moyen de les afficher dépend de la bibliothèque utilisée et même sans doute de la vue dans laquelle tu te trouves.

    Si je prends l'exemple du jeu d'échecs que j'ai pris comme étude de cas dans le bouquin :

    j'ai clairement indiqué dans mes spécifications que je voulais que l'étude de cas reste simple, et qu'il était hors de question de commencer à expliquer l'utilisation de l'une ou l'autre bibliothèque IHM.

    Mais je sais que, sorti de cette étude de cas, la première chose que l'on fera sans doute, c'est d'adapter les spécifications et de permettre un affichage plus user friendly, en décidant d'utiliser l'une de ces bibliothèques.

    Selon l'OCP, je ne devrais pas avoir à modifier le code existant de mon jeu d'échecs pour y arriver, et l'on peut même estimer comme probable qu'il soit intéressant de permettre au joueur de choisir entre l'affichage "console", l'affichage "2D" et pourquoi pas un affichage 3D.

    Ce sont autant d'affichages qui peuvent utiliser différentes bibliothèques pour y arriver.

    La solution nous est donnée par le D de solid : la gestion (au niveau du jeu d'échecs) de l'affichage ne doit pas dépendre de détails propres aux différentes bibliothèque, mais tout doit passer par... une abstraction supplémentaire (une façade )

    De cette manière, si je veux changer de bibliothèque d'affichage (et donc de système d'entrées, car les deux sont intimement liés), je n'ai "qu'à" ... fournir une implémentation spécifique de cette façade qui se base sur la bibliothèque utilisée pour ce dont j'ai besoin.

    Cela peut sembler quelque peu "overkill", mais, si tu veux pouvoir faire facilement évoluer ton application dans "n'importe quelle direction" (y compris celles que tu pourrait considérer comme étant les plus improbables), il est préférable de prévoir le "point de variation" qui te permettra de le faire le plus tôt possible, afin d'éviter d'avoir à tout casser lorsque tu en viendras à envisager ce genre d'évolution
    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

  6. #26
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    J'entends bien (enfin je crois), mais ça ne fait que déplacer le problème.
    Que le code spécifique à ta lib d'affichage soit dans un intermédiaire (façade), dans le moteur de rendu lui-même, ou dans l'objet affiché, ça revient au même (dans le cadre de SOLID). Ce qui change, c'est le contexte réel de l'implémentation: dans quel sens le programme est supposé évoluer, quelle est l'architecture globale (MVC, 3 tiers, client/serveur, ...), granularité des modules compilés (une lib pour chaque classe jusqu'à un seul gros exécutable), etc.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  7. #27
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par r0d Voir le message
    J'entends bien (enfin je crois), mais ça ne fait que déplacer le problème.
    Que le code spécifique à ta lib d'affichage soit dans un intermédiaire (façade), dans le moteur de rendu lui-même, ou dans l'objet affiché, ça revient au même (dans le cadre de SOLID). Ce qui change, c'est le contexte réel de l'implémentation: dans quel sens le programme est supposé évoluer, quelle est l'architecture globale (MVC, 3 tiers, client/serveur, ...), granularité des modules compilés (une lib pour chaque classe jusqu'à un seul gros exécutable), etc.
    Cela ne fait peut etre que déplacer le problème, mais cela le déplace à un endroit du code où il ne risque plus d'interférer avec "autre chose", et donc, où il ne risque plus de "tout casser" en cas de changement.

    Je m'explique :

    1- Tu peux envisager énormément de responsabilités pour tes données métier (dans la mesure où chaque donnée métier n'a qu'une responsabilité bien déterminée du moins), sauf celle qui consiste à s'afficher.

    La responsabilité de l'affichage des données métier doit échoir à "quelque chose d'autre" (une classe dérivée de DrawableItem )

    En agissant de la sorte, tu permets déjà à tes données métier d'évoluer indépendamment du contexte d'affichage, tout en permettant à ce dernier d'évoluer de manière relativement indépendante de tes données métier.

    2- On l'oublie souvent, mais un contexte d'affichage va généralement de paire avec un contexte d'entrées de la part de l'utilisateur : Si tu utilises le mode console, les entrées utilisateurs se feront... via le clavier (ou via des fichiers), alors que si tu utilises Qt ou SFML, les entrées de l'utilisateur se feront en fonction des événement ou des signaux émis par les éléments visuels.

    De là à estimer qu'il n'y aura de toutes manière que les entrées susceptibles d'être comprises par un contexte d'entrées spécifique qui devront être gérées par le système d'affichage, il n'y a qu'un pas, que l'on peut allègrement franchir

    En effet, si j'introduis une commande proche de "blancs" en mode console, et que le système d'entrées de l'utilisateur interprète cette ligne de caractères comme étant la commande qui demande l'affichage de l'ensemble des pièces de l'équipe des blancs qui se trouve sur l'échiquier, la réaction "visuelle" du système d'affichage sera... d'afficher la liste des pièces blanches se trouvant encore sur l'échiquier.

    D'un autre coté, si le système d'entrées ne comprends pas cette chaine de caractères, il devrait sans doute passer par défaut dans une situation dans laquelle la réaction visuelle sera... de m'afficher un message d'erreur m'indiquant qu'il n'a pas compris ce que je lui voulais.

    Sous Qt ou avec SFML, cette même commande prendra sans doute la forme d'un clique sur un bouton particulier, ou que sais-je

    Mais la réaction visuelle sera forcément de même nature : soit la commande est comprise et les pièces blanches sont mise en évidence sur l'échiquier, soit la commande n'est pas comprise et j'obtiens un message (quelle qu'en soit la forme !!) qui me l'indique.

    De la même manière, si j'introduis un déplacement sous la forme de A1C1 pour déplacer une tour, soit le système d'entrées est capable de reconnaitre cette chaine de caractère, et le déplacement doit s'effectuer (avec l'affichage correspondant), s'il est légal, soit (si le déplacement est illégal) le système d'affichage doit me faire savoir que le déplacement est illégal, soit, enfin, la commande n'est pas reconnue et on "retombe" dans la situation précédente.

    Ce qu'il importe de se rendre compte, c'est que ce n'est pas la donnée métier qui correspond à la pièce déplacée ou au déplacement qui dépend du contexte, mais bel et bien... la commande qui va manipuler les données métier pour s'exécuter correctement.

    Or, la commande, elle va être émise ... par un système d'entrées spécifique au contexte d'entrées / sorties utilisé à un instant T.

    Et qui mieux que le contexte d'entrées pourrait indiquer le contexte de l'affichage, sachant que, de toutes manières, il n'y aura que les commandes reconnues qui devront avoir un équivalent à l'affichage

    A partir de là, tu t'organises comme tu veux pour que le système d'entrées soit en mesure de fournir, à une commande qui lui est spécifique, le contexte d'affichage correspondant vers lequel elle devra se tourner pour provoquer son propre affichage, mais, ce qui importe, c'est que chaque commande se divise finalement en deux grandes responsabilités :

    une fonction "execute()" qui fait les vérifications éventuelles et qui modifie les données métier en conséquence et une fonction "printeMe()" (par exemple, voir d'autres fonctions supplémentaires) dont le comportement est parfaitement susceptible de s'adapter à un contexte d'affichage spécifique.

    On pourrait estimer que l'on ne fait que "déplacer le problème", mais ce qui importe surtout, c'est que, au final tu te retrouves avec différentes abstractions (pièces, couleur, coordonnée, case, échiquier, render, input, command, ...) pour lesquelles il est possible, en cas de besoin d'adapter certains comportements en fonction des situations spécifiques :
    -les premières dans cette liste (de pièces à échiquier inclus) n'ayant strictement aucun besoin de connaitre le contexte d'entrées / sorties,

    - les dernières (de render à command inclus) ayant toutes comme point de variation possible le contexte d'entrées / sorties utilisé et donc susceptibles d'adapter les comportements (qui nécessitent de l'être) en fonction de ce point de variation et qu'il t'appartient de spécialiser en fonction du contexte d'entrées / sortie que tu envisages d'utiliser.

    A partir de là, les différentes options que tu pourras envisager en terme d'architecture ou de modularisation n'auront plus qu'un impact extrêmement limité sur tout le reste
    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

  8. #28
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    De toutes façons, faire de la conception ne revient finalement qu'à déplacer des problèmes. Le but étant de les mettre au bon endroit et dans le bon ordre.

    Ton exemple de commande est intéressant, car en plus, on pourrait imaginer pouvoir contrôler le programme par deux systèmes de commandes différents (console+GUI) simultanément. Par exemple, deux joueurs joue une partie; un joueur joue via une interface graphique, et l'autre en ligne de commande.

    Prenons le cas où on a un MVC. Les contrôleurs (ligne de commande/GUI) sont dans le contrôleur. Les données (l'état de l'échiquier: position des pièces etc.) sont dans le modèle, et la vue se contente d'afficher le modèle. On aurait alors un contrôleur qui aurait deux input (console et GUI), qui utiliserait le modèle pour vérifier la faisabilité des commandes, et qui enverrai des notifications au modèle et éventuellement à la vue. Les deux inputs, comment seraient-ils implémentés à ton avis?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  9. #29
    Membre expérimenté

    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2011
    Messages
    685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2011
    Messages : 685
    Points : 1 418
    Points
    1 418
    Par défaut
    Citation Envoyé par r0d Voir le message
    Citation Envoyé par oodini
    Je n'ai pas le nom du DP en tête, mais en donnant comme membre à la classe un pointeur vers une interface abstraite, et en affectant ce pointeur à une instance concrète déduite via l'utilisation d'une Stratégie, on devrait satisfaire le SRP et lOCP.
    Une abstract factory?
    Non je crois qu'il s'agit de Dependency Injection. C'est un design pattern qui se rapproche de ce qu'expliquait Koala01, à savoir qu'on devrait ne pas avoir à s'occuper de "qui" gère le contexte, si on veut respecter SOLID(Dependency inversion Principle). Et c'est vrai qu'en y réfléchissant c'est génial et j'ai un cas concret pour le démontrer :

    je suis en train de développer un jeu de carte multijoueur avec SFML. J'ai choisi également une lib (SFGUI basée sur SFML) pour l'interface graphique. Et depuis quelques temps je regrette sincèrement de ne pas avoir choisi Qt pour l'interface graphique parce que l'autre est moins complète et plus amateur. Malheureusement je n'ai pas ajouter d'abstraction entre la logique métier de mon appli et les classes responsables de la gestion de mon contexte(affichage+events), du coup je devrai passer dans mon code au marteau-piqueur si je voulais effectivement passer à Qt, ou Qt+SFML-SFGUI, ou autre.

    C'est là qu'on s'aperçoit que des gars sont passé par là avant nous des milliers de fois et que SOLID est composé de principes très solides (pour rester dans l'humour de l'OP ), qui respirent le vécu.
    Nullius in verba

  10. #30
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par r0d Voir le message
    De toutes façons, faire de la conception ne revient finalement qu'à déplacer des problèmes. Le but étant de les mettre au bon endroit et dans le bon ordre.
    Tout à fait
    Ton exemple de commande est intéressant, car en plus, on pourrait imaginer pouvoir contrôler le programme par deux systèmes de commandes différents (console+GUI) simultanément. Par exemple, deux joueurs joue une partie; un joueur joue via une interface graphique, et l'autre en ligne de commande.
    Je t'avouerai n'avoir pas vraiment réfléchi à la question, mais, si tu as correctement défini tes responsabilités, cela ne poserait finalement pas énormément de problème
    Prenons le cas où on a un MVC. Les contrôleurs (ligne de commande/GUI) sont dans le contrôleur. Les données (l'état de l'échiquier: position des pièces etc.) sont dans le modèle, et la vue se contente d'afficher le modèle. On aurait alors un contrôleur qui aurait deux input (console et GUI), qui utiliserait le modèle pour vérifier la faisabilité des commandes, et qui enverrai des notifications au modèle et éventuellement à la vue. Les deux inputs, comment seraient-ils implémentés à ton avis?
    Par "simple" agrégation des deux

    De la manière dont j'ai implémenté les choses, il y a en réalité quatre abstraction de base:
    1. Input (le système d'entrée)
    2. Render (le système d'affichage)
    3. Command (la commande à effectuer)
    4. CommandSender (une abstraction qui permet au système d'entrées d'émettre la commande qu'il a reconnue).
    J'ai basé un certain nombre de classes template sur ces abstraction de base :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename Tag>
    class ConcreteInput;
    que j'adapte au contexte pour les entrées
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename Tag>
    class ConcreteRender;
    que j'adapte au contexte pour l'affichage
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename CommTag>
    class ExecutableCommand;
    que j'adapte en fonction de la commande à exécuter (comprends : pour laquelle je prévois d'adapter le comportement de execute() )
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template < typename ITag, typename CommTag>
    class ConcreteCommand;
    dont chaque spécialisation pour CommTag hérite de ExecutableCommand, et pour laquelle ITag correspond au contexte d'entrée / sortie
    et enfin
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename ITag, typename CommandTag>
    class ConcreteCommandSender;
    dont le but est de renvoyer la commande équivalent à CommandTag et spécifique au contexte d'entrées / sortie équivalent à Itag.

    Je peux donc avoir une spécialisation de la commande, par exemple pour la commande d'aide, avec un tag proche de
    qui ressemble à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template <>
    class ExecutableCommand<HelpTag> : public Command{
        public:
            bool execute() const{
                /* ne fait rien à part provoquer l'affichage de l'aide */
                printMe();
                return false;
            }
           /* comportement à adapter selon le contexte */
           virtual void printMe() const = 0;
    };
    Pour ce qui est de la commande concrète, je peux fournir une spécialisation partielle, le tag pour la commande étant connu, celui pour le contexte d'affichage ne l'étant pas, 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
    template <typenam ITag>
    class ConcreteCommand<ITag, HelpTag> : public ExecutableCommand<HelpTag>{
        public:
            ExecutableCommand(ConcreteRender<ITag> & render):render_(render){}
            /* comportement pour lequel on fournit à chaque fois une
             * spécialisation complète en fonction du contexte d'affichage
             */
           void printMe() const;
           ConcreteRender<ITag> & render_;
    };
    et une spécialisation partielle pour la classe ConcreteCommandSender 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 <typename ITag>
    class ConcreteCommandSender<ITag, HelpTag> : public CommandSender{
        public:
           ConcreteCommandSender(Game & game):game(game){}
           Command const & send() const{
               static ConcreteCommand<ITag, HelpTag> command(
                   static_cast<ConcreteRender<ITag> &>(game.render());
               return command;
           }
        private:
            Game & game_;
    }
    (on considère que la fonction render() de Game renvoie un Render & )

    Tant que l'on travaille dans un contexte exclusivement console, on crée un tag spécifique pour ce contexte, mettons
    et la seule chose que l'on ait à préciser (pour chacune des commandes reconnues par le système, s'entend) est le comportement équivalent à printMe() pour la spécialisation partielle de la commande.

    Cela pourrait donc prendre la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <>
    void ConcreteCommand<ConsoleTag, HelpTag>::printMe() const{
        /* une succession de std::cout pour  provoquer l'affichage */
    }
    Le jour où je décide d'utiliser une bibliothèque IHM pour rendre les choses plus user friendly, je crées un tag de contexte spécifique, par exemple:
    et je fournis une implémentation spécifique pour le comportement printMe(), sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <>
    void ConcreteCommand<SfmlTag, HelpTag>::printMe() const{
        /* ce qu'il faut faire pour afficher l'aide avec SFML */
    }
    Et le jour où j'ai envie d'avoir les deux regroupés, qu'est ce qui m'empêche de fournir une spécialisation totale pour ConcreteCommand et pour ConcreteCommandSender

    Qu'est ce qui m'empêcherait d'avoir un tag proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    struct ConsoleAndSfml{};
    de spécialiser render en fonction de ce tag pour qu'il envoie les informations à afficher aussi bien vers la console que vers SFML, de spécialiser ConcreteInput pour qu'il reconnaisse les commandes aussi bien en mode console qu'avec SFML, et ainsi de suite

    Je pourrait tout aussi bien spécialiser le comportement de printMe sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <>
    void ConcreteCommand<ConsoleAndSfml, HelpTag>::printMe() const{
        printInConsole();
        printInSfml();
    }
    et fournir une déclaration et une implémentation de ces fonctions en conséquence

    Avec un peu de chance, la spécialisation de ConcreteCommand pourrait même plutôt ressembler à
    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 <>
    class ConcreteCommand<ConsoleAndSfml, HelpTag> : plublic ExecutableCommand<HelpTag>{
        public:
        ConcreteCommand(ConcreteRender<ConsoleAndSfml> & render):
             consolePrinter_(render.console()), 
             sfmlPrinter_(render.sfml()){}
             void printMe() const{
                 consolePrinter_.printMe();
                 sfmlPrinter_.printMe();
             }
        private:
            ConcreteCommand<ConsoleTag, HelpTag> consolePrinter_;
            ConcreteCommand<SfmlTag, HelpTag> sfmlPrinter_;
    };
    (code non testé ) et le tour serait joué
    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. #31
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 704
    Points
    2 704
    Par défaut
    Je suis content d'avoir lancé ce fil de discussion, car SOLID revient régulièrement sur le tapis (notamment par notre marsupial préféré), sans qu'on ait vraiment eu un sujet qui y était dédié (du moins, dans mes souvenirs).

  12. #32
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par oodini Voir le message
    Je suis content d'avoir lancé ce fil de discussion, car SOLID revient régulièrement sur le tapis (notamment par notre marsupial préféré), sans qu'on ait vraiment eu un sujet qui y était dédié (du moins, dans mes souvenirs).
    Le fait est que SOLID est un groupe de principes qu'il est bon d'envisager dans son ensemble (chacun d'eux étant très certainement plus facile à respecter si l'on est déjà dans une situation dans laquelle on respecte les autres ), mais que l'on peut souvent se limiter au rappel d'un ou deux des principes dans la plupart des discussions.

    Dans bien des cas de discussions, le simple fait d'inciter l'OP à respecter le SRP et le LSP vont l'orienter vers une solution où le respect du ISP (et du DIP) sera renforcé, et où l'on fera un grand pas dans le respect de l'OCP.

    Du coup, il devient assez difficile d'aborder SOLID dans son ensemble sans partir dans une discussion purement théorique dans laquelle chacun verra forcément midi à sa porte, et dans laquelle chaque exemple peut apporter un nombre incalculable de contre exemples

    La meilleure preuve en est que tu es arrivé avec un exemple tout simple et que la première chose que j'ai faite est d'introduire deux concepts basés sur cet exemple, afin de te démontrer que c'étaient deux choses tout à fait orthogonales qui pouvaient parfaitement être prises en compte séparément
    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

+ Répondre à la discussion
Cette discussion est résolue.
Page 2 sur 2 PremièrePremière 12

Discussions similaires

  1. [XSL-DELPHI] Peu orthodoxe... Mais ça marche : )
    Par stailer dans le forum Bases de données
    Réponses: 6
    Dernier message: 09/12/2003, 23h18
  2. je m'y perd un peu dans tous ces plugin
    Par Stessy dans le forum Eclipse Java
    Réponses: 7
    Dernier message: 30/09/2003, 23h33
  3. RTL60 ( la jsuis un peu confused)
    Par magdoz dans le forum Outils
    Réponses: 7
    Dernier message: 23/07/2002, 11h20
  4. DirectX 6, un peu en retard ... :\
    Par multani dans le forum DirectX
    Réponses: 3
    Dernier message: 28/05/2002, 19h19

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