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

Langage C++ Discussion :

Héritage par dominance et alternatives à l'héritage en diamant


Sujet :

Langage C++

  1. #1
    Nouveau membre du Club
    Inscrit en
    Mars 2007
    Messages
    53
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 53
    Points : 38
    Points
    38
    Par défaut Héritage par dominance et alternatives à l'héritage en diamant
    Bonjour,

    J'ai un code du type ci-dessous (simplifié pour s'y retrouver). Ce code me génère un warning

    warning C4250: 'B1C2' : inherits 'B1::B1::b' via dominance
    warning C4250: 'B1C2' : inherits 'C2::C2::c' via dominance
    Warning que je peux désactiver avec :
    #pragma warning(disable : 4250)
    J'ai en gros deux questions :
    1) Est-il possible d'éviter ce warning en gardant le même esprit d'architecture ?
    2) Sinon, existe-t-il des alternatives à cette architecture diamant qui ne me parait pas très propre. Je pense peut-être à utiliser des templates mais je ne sais pas trop comment m'y prendre ici malgré lecture de tutos/faq. J'ai également consulté le tuto sur les classes de traits et de politiques ici mais mes fonctions de sont pas statiques.

    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
    #include <stdio.h>
    #include <stdlib.h> 
     
    //#pragma warning(disable : 4250)
     
    class A{
    public:
    	A(){};
    	void a(){ b(); c(); };
    	virtual void b() = 0;
    	virtual void c() = 0;
    };
     
    class B1 : public virtual A{
    public:
    	B1():A(){};
    	void b(){ printf("B1"); };
    };
     
    class B2 : public virtual A{
    public:
    	B2() :A(){};
    	void b(){ printf("B2"); };
    };
     
    class C1 : public virtual A{
    public:
    	C1() :A(){};
    	void c(){ printf("C1"); };
    };
     
    class C2 : public virtual A{
    public:
    	C2() :A(){};
    	void c(){ printf("C2"); };
    };
     
    class B1C2 : public B1, public C2{
    public:
    	B1C2() : A(), B1(), C2(){};
    };
     
    void main(int argc, char* argv[])
    {
    	B1C2* b1c2 = new B1C2();
    	b1c2->a();
    	printf("\n");
    	system("pause");
    }
    Merci de votre aide

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 074
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 074
    Points : 12 120
    Points
    12 120
    Par défaut
    1) Est-il possible d'éviter ce warning en gardant le même esprit d'architecture ?
    Heu, donc une architecture foireuse ???
    2) Sinon, existe-t-il des alternatives à cette architecture diamant
    Heureusement, mais il faut plus de détail sur le vrai besoin initial, pour pouvoir choisir une bonne solution.

  3. #3
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 565
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 565
    Points : 7 648
    Points
    7 648
    Par défaut
    Bonjour,

    Le warning nous prévient que B1C2::b() préférera B1C2::B1::b() à B1C2::C2::B::b(), un moyen de le faire disparaitre est de faire 'remonter' la fonction dans B1C2 pour lever toute ambiguïté. Un using B1C2::B1::b; dans la class B1C2 devrait ôter le warning.

  4. #4
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Bonjour,

    Citation Envoyé par Bloodista Voir le message
    2) Sinon, existe-t-il des alternatives à cette architecture diamant qui ne me parait pas très propre.
    Solution avec l'héritage et la virtualité, sans l'héritage multiple : patron de conception Stratégie :
    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
    #include <iostream>
     
    class B {
    public:
    	virtual void b() = 0;
    };
     
    class C {
    public:
    	virtual void c() = 0;
    };
     
    class A{
    private:
    	B& m_b;
    	C& m_c;
    public:
    	A(B& x, C& y) : m_b(x), m_c(y) {}
    	void a() { m_b.b(); m_c.c(); }
    };
     
    class B1 : public B {
    public:
    	void b() override { std::cout << "B1"; }
    };
     
    class B2 : public B {
    public:
    	void b() override { std::cout << "B2"; }
    };
     
    class C1 : public C {
    public:
    	void c() override { std::cout << "C1"; }
    };
     
    class C2 : public C {
    public:
    	void c() override { std::cout << "C2"; }
    };
     
    int main()
    {
    	B1 b1;
    	C1 c1;
    	A obj(b1, c1);
    	obj.a();
    	return 0;
    }
    Solution avec les templates : classes de politique :
    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
    #include <iostream>
     
    template<class B, class C>
    class A{
    private:
    	B m_b;
    	C m_c;
    public:
    	A(const B& x = B(), const C& y = C()) : m_b(x), m_c(y) {}
    	void a() { m_b.b(); m_c.c(); }
    };
     
    class B1 {
    public:
    	void b() { std::cout << "B1"; }
    };
     
    class B2 {
    public:
    	void b() { std::cout << "B2"; }
    };
     
    class C1 {
    public:
    	void c() { std::cout << "C1"; }
    };
     
    class C2 {
    public:
    	void c() { std::cout << "C2"; }
    };
     
    int main()
    {
    	A<B1, C1> obj;
    	obj.a();
    	return 0;
    }

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    En dehors du patron de conception stratégie proposé par pyramidev, on peut déjà se poser pas mal de question, entre autre, quant au respect du LSP et de l'ISP dans ton architecture.

    Pour rappel, le principe de substitution de Liskov nous oblige à veiller à ce que la classe de base n'expose que les propriétés qui sont communes à toutes les classes dérivées. Ainsi, si une fonction "truc" n'as de sens que pour une seule des classes dérivées, il n'y a aucune raison pour qu'elle soit exposée dans la classe de base, parce qu'on ne peut pas estimer en toute conscience que [tout élément passant pour un objet du type de base dispose d'une propriété "expose la fonction truc"]

    Dans le cas présent, on pourrait estimer que la seule fonction qui devrait effectivement être exposée par ta classe A (qui est au final la seule vraie classe de base) est ... la fonction a(), et que tu aurais sans doute intérêt à utiliser le NVI, car son comportement a de grandes chances de devoir s'adapter de différentes manières.

    En effet, tu ne définis jamais le comportement des deux autres fonctions en même temps dans les classes dérivées : tu définis le comportement de b() dans B1 et dans B2 et de c() dans C1 et dans C2. Si bien que tes classes B1, B2, C1 et C2 restent malgré tout... des classes abstraites (car il y a toujours une fonction virtuelle pure qui traine "quelque part") .

    Dés lors, tu devrais envisager de sortir tes fonctions b() et c de ta classe A, en les mettant dans des interfaces séparées, 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
    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
     
    /* la classe de base
     */
    class A{
    public:
        /* utilise le NVI pour permettre malgré tout au comportement de s'adapter de manière transparente */
        void a(){
            doA();
        }
    private:
        virtual void doA() = 0;
    };
    /* l'interface permettant d'avoir la fonction b
     */
    class BInterface{
    public:
        virtual void b() = 0;
    };
    /* L'interface permettant d'avoir la fonction c
    class CInterface{
    public:
        virtual void c() = 0;
    };
     
    /* On peut désormais créer une classe qui ne dispose que de la fonction b
     */
    class B1: public A, public BInterface{
    public:
        void b() override;
    private:
       void doA() override{
           b();
       }
    };
    /* Mais b peut n'être qu'un "comportement interne", et l'héritage privé fonctonne aussi...
     * !!! on ne pourra pas la transmettre à une fonction proche de bar(BInterface & i);
     * car la composante BInterface est privée 
     */
    class B2 : public A, private BInterface{
    private:
        // héritée de BInterface
        void b() override{
        }
        // héritée de A...
        void doA() override{
            b();
        }
     };
    /* même chose pour les classes qui n'ont besoin que de c() */
    class C1 : public A, public CInterface{
    public:
        void c() override;
    private:
        void doA() override{
            c();
        }
    };
    /* Et, si on veut une classe qui a b() et c(), on hérite des deux interfaces */
    class D : public A, public BInterface, public CInterface{
    public:
        // héritée de BInterface
        void b() override;
        // héritée de CInterface
        void c() override;
    private:
       // héritée de A
       void doA() override{
          b();
         c();
       }
    };
    // D'ailleurs, une autre classe disposant de b() et de c() pourrait resembler à D, 
    //mais en inversant les appels à b() et à c() dans doA ;)
    En plus, ce qu'il y a de bien avec C++, c'est que ce que l'on considère comme une interface n'est jamais qu'une classe tout à fait classique, dont il n'est normalement pas envisageable de créer une instance sans passer par les classes dérivées.

    Mais il n'y a a priori aucune raison pour que cette classe ne dispose pas également des données permettant aux fonction de travailler, ce qui nous permettrait de ne pas devoir les déclarer virtuelles (ou de ne pas devoir les implémenter dans les classes dérivées).

    "Tout ce qu'il y aurait à faire, c'est d'en empêcher l'instanciation, par exemple, en plaçant le constructeur dans l'accessibilité protégée, de manière à ce que seules les instances dérivées puissent y avoir accè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

  6. #6
    Nouveau membre du Club
    Inscrit en
    Mars 2007
    Messages
    53
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 53
    Points : 38
    Points
    38
    Par défaut
    Merci beaucoup pour toutes vos réponses.

    dalfab, ta solution provoque une erreur de compilation :
    error C2039: 'B1' : is not a member of 'B1C2'
    En effet, bacelar a raison, j'ai trop simplifié mon problème qui est je pense finalement plus compliqué que ça car les fonctions s'appellent mutuellement à travers les différentes classes. Désolé de ne pas avoir mis ça dès le début. Pas toujours simple de faire des versions simplifiées

    Voici une version plus détaillée de mon problème (et de mon architecture actuelle) en espérant que cela soit plus explicite. Si vous avez des idées pour améliorer ça

    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
    #include <stdio.h>
    #include <stdlib.h> 
     
    //#pragma warning(disable : 4250)
     
    class Mere{
    public:
    	Mere(){};
    	void operation1(){/*Do operation1*/; printf("op1"); };
    	void operation2(){/*Do operation2*/; printf("op2"); };
    	void operation3(){/*Do operation3*/; printf("op3"); };
    	virtual void enchainer_operations() = 0;
     
    	virtual void petite_fonction1() = 0;
    	virtual void petite_fonction2() = 0;
    	virtual void petite_fonction3() = 0;
     
    	void grosse_fonction(){
    		petite_fonction1();
    		petite_fonction2();
    		petite_fonction3();
    	};
    };
     
    class Type1 : public virtual Mere{
    public:
    	Type1() :Mere(){};
     
    	void petite_fonction1(){/* du code + */ enchainer_operations(); };
    	void petite_fonction2(){/* du code + */ enchainer_operations(); };
    	void petite_fonction3(){/* du code + */ enchainer_operations(); };
    };
     
    class Type2 : public virtual Mere{
    public:
    	Type2() :Mere(){};
     
    	void petite_fonction1(){/* du code + */ enchainer_operations(); };
    	void petite_fonction2(){/* du code + */ enchainer_operations(); };
    	void petite_fonction3(){/* du code + */ enchainer_operations(); };
     
    };
     
    class Mode1 : public virtual Mere{
    public:
    	Mode1() :Mere(){};
    	void enchainer_operations(){ operation1(); operation2(); };
    };
     
    class Mode2 : public virtual Mere{
    public:
    	Mode2() :Mere(){};
    	void enchainer_operations(){ operation1(); operation2(); operation3(); };
    };
     
    class Fille : public Type1, public Mode2{
    public:
    	Fille() : Mere(), Type1(), Mode2(){};
    };
     
    void main(int argc, char* argv[])
    {
    	Fille* fille = new Fille();
    	fille->grosse_fonction();
    	system("pause");
    }

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Et tu ne peux pas renommer et revoir ton architecture ?
    Le simple fait que tu appelles ça Type et Mode laissent penser que tu pourrais, voire devrais, et que ce ne sont finalement pas des relations d'héritage qu'elles devraient partager. Mais que l'héritage tombe ici par hasard par simplification de nommage.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  8. #8
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Visiblement, même, les deux types ne définissent que les "petites_fonctions", et les modes l'enchainement.
    Ca ressemble furieusement à "strategy" et un peu de template.

    Sinon, il semble que les "petites fonctions" appelle aveuglement "enchainer_operations" quoi que cette dernière fasse.
    Cela ressemble à deux niveaux responsabilités distinctes.
    Cela mérite probablement de déléguer l'une des responsabilité à une autre classe, dont un objet serait contenu dans Mère.

    D'un point de vue utilisateur de Mère&, fille1 et fille..., les classes Type et Mode ont elle un intérêt par elle même.
    Voudras-t-il créer une classe en héritant?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Tu nous montre du code tellement générique qu'il est très difficile de se faire un idée précise de ta situation conceptuelle.

    Car, lorsque l'on lit class Mere{};, classe Fille: public Mere{};, la seule information dont nous disposions est : tu as décidé de recourir à l'héritage public.

    Or, toute première question que les concepteurs ont tendance à se poser est : est-ce que l'héritage public est cohérent dans le contexte actuel "; la deuxième question étant "est ce que l'héritage public respecte le LSP dans le contexte actuel "

    Et le problème au final, c'est que ce que tu nous montre ne nous permet de répondre à aucune de ces questions .

    Dés lors, je voudrais bien te croire "sur parole" et estimer que, oui, l'héritage public est cohérent dans ce contexte, et que oui, le LSP est pleinement respecté, malheureusement, je n'y arrive absolument pas. Pour une raison toute simple : tu as un problème d'héritage, et donc, c'est très certainement qu'il y en a au moins un qui n'est pas cohérent ou qui ne respecte pas le LSP. Autrement, tu n'aurais pas de problème et tu ne serais pas venu chercher notre aide

    Dés lors, je n'entrevois que deux conseils à te donner pour t'aider, qui partent du prérequis qu'il faudra changer l'architecture de ton code (chose que tu ne souhaites malheureusement pas envisager, de toute évidence):

    1. L'héritage public est la relation la plus forte qui puisse exister entre deux classes, c'est donc la dernière qui doive être envisagées lorsque toutes les autres (agrégation en tête) ont démontré leur inefficacités
    2. L'héritage public ne peut être considéré comme une alternative valable que si le principe de substitution de Liskov est pleinement respecté. Si ce n'est pas le cas, il faut chercher une autre solution.



    Ou bien tu es plus précis dans tes exemples (montre nous à quoi ressemblent réellement tes classes, avec leurs noms réels, qu'on sache à quoi elles servent réellement) et on réfléchira ensemble à la manière d'appliquer ces deux conseil, ou bien tu cogites dessus dans ton coin en vue de les appliquer, et tu viens nous montrer le résultat de tes cogitations plus tard. C'est comme tu veux

    Mais une chose est sure, il va falloir cogiter... Et comme tu nous demande de l'aide (qu'on est au demeurant tout à fait disposés à te fournir), tu devrais sans doute choisir la première solution
    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

  10. #10
    Nouveau membre du Club
    Inscrit en
    Mars 2007
    Messages
    53
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 53
    Points : 38
    Points
    38
    Par défaut
    Zut ! Ça reste trop simplifié ! Désolé

    Le souci c'est que chacune de mes classes fait autour de 1000 lignes et qu'il y en a plus que ça. Je vais donner plus de détails :

    D'un point de vue historique, j'ai récupéré un code dégueulasse (architecture objet pour ainsi dire inexistante, pointeurs à gogo avec des memcmp et memcpy partout, tableaux statiques initialisés à 256 ou alors 10000 si ça suffit pas, création et manipulation de variables qui ne servent à rien, hardcoding un peu partout avec des bouts de code à commenter/dé-commenter, etc). Bref, tout ça pour dire que j'essaye de mettre de l'ordre dans tout ce merdier.

    D'un point de vue contextuel, mon programme est sensé testé différentes applications (que j'ai pu classer par types). Chaque application à plusieurs modes de fonctionnements. Ces modes sont apparus par la suite, je les ai incorporés dans une classe feuille (application spécifique) dans un premier temps, pour voir si mes tests sur ces modes fonctionnaient correctement. Evidemment je n'ai pas envie de recopier ce code dans chaque classe feuille. C'est là que je les ai incorporés dans cette architecture en diamant actuelle. Je suis tout à fait prêt à changer mon architecture ! C'est d'ailleurs pour ça que je suis ici

    D'un point de vue architectural:
    - J'ai tout en haut une classe Application qui définit toutes les opérations de base que peut effectuer une application. Elle contient aussi quelques opérations un peu plus compliquées qui implémentent un enchaînement d'opérations (enchaînement qui dépend du mode de fonctionnement).
    - Juste en dessous j'ai une classe TestableApplication qui définit les tests génériques à toutes mes applications (utilise les opérations de base de la classe application et aussi les enchaînements contenus dans le mode. Cette classe est séparée de la classe Application principalement par souci de sémantique, pour séparer ce qui relève du test et ce qui n'en relève pas.
    - Mes classes "type" définissent des types d'applications particuliers qui fonctionnent de la même manière. Elles héritent de la classe TestableApplication. Elles implémentent des tests spécifiques contenus à l'intérieur des test génériques de la classe TestableApplication. Elles implémentent aussi des opérations spécifiques appelés dans des opérations complexes de la classe application.
    - Mes classes "mode" définissent un enchaînement d'opérations et aussi quelques tests spécifiques qui dépendent du mode de fonctionnement de l'application. Elles héritent de la classe TestableApplication (d'où le diamant). Certains de ces tests spécifiques sont appelés depuis une classe type, d'autre depuis la classe TestableApplication.
    - Enfin, mes classes feuilles qui définissent l'application finale qui est testée héritent d'une classe "type" et d'une classe "mode". Elles définissent des tests spécifiques à l'application précise.

    Du coup je cherche à améliorer mon architecture car je vois bien qu'elle est bancale. Par améliorer, j'inclus aussi l'exploser et en changer complètement

    La seule solution alternative que je vois pour l'instant est de définir un type Mode dans Application et de faire des switch/if à chaque fois que je dois prendre en compte le mode mais ça ne me parait pas plus propre, au contraire. Peut-être le strategy est une solution mais je m'emmêle un peu les pinceaux avec l'architecture actuelle où des fonctions de chaque classe (appellent des fonctions des classes mères/filles).

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 074
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 074
    Points : 12 120
    Points
    12 120
    Par défaut
    Evidemment je n'ai pas envie de recopier ce code dans chaque classe feuille. C'est là que je les ai incorporés dans cette architecture en diamant actuelle.
    Ne pas recopier le code, c'est la plus mauvaise des justifications pour faire de l'héritage.
    Ça ne sert pas à cela, ce n'est qu'un effet de bord.

    Vous devez avoir une vue claire du RÔLE de chaque classe.

    Avant de chercher à factoriser le code, cherchez la liste des classes nécessaires et leur rôle à chacun.
    Les sous-classes et autre tralala, c'est après.

    Il y a énormément à redire sur votre démarche (pas de classe "Test" ? "TestSuite"? ...), mais je pense que le plus efficace, c'est de ne pas réinventer la roue.
    Des Framework de test, il y en a pléthore, autant s'en servir.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Bloodista Voir le message
    Zut ! Ça reste trop simplifié ! Désolé
    Y a pas de mal
    Le souci c'est que chacune de mes classes fait autour de 1000 lignes et qu'il y en a plus que ça. Je vais donner plus de détails :
    Rassures moi: c'est implémentation comprise

    Car si la seule définition de tes classes prend autant de ligne, il est plus que temps de réfléchir un tout petit peu à respecter le SRP... Je serais particulièrement surpris qu'il soit respecté même si tu me disais qu'il n'y a que 200 lignes représentant des services fournis par ces classes (comprend : des fonctions exposée par l'interface publique)

    Ceci dit, même 1000 lignes de code implémentation comprise, ca me semble clairement excessif : soit les fonctions en font beaucoup trop, vu que la "moyenne acceptable" du nombre d'instruction par fonction varie entre 25 et 50 (avec "une certaine marge"), soit il y a beaucoup trop de foncions . Quoi qu'il en soit, on retombe toujours sur ce satané SRP qui vient nous cassez les pieds et qui ne semble clairement pas respecté

    Maintenant, tu n'est certainement pas obligé de nous donner l'implémentation de toutes les fonctions, ni même de nous donner la définition complète de toutes les classes...

    Mais, disons qu'il serait au moins sympa d'avoir leurs noms réels (c'est fou ce qu'un nom de classe peut nous apprendre à son sujet, même si on ne sait pas à quoi elle ressemble ) et quelques un des noms de fonctions virtuelles; cela nous permettrait surement de cerner d'avantage le problème
    D'un point de vue historique, j'ai récupéré un code dégueulasse (architecture objet pour ainsi dire inexistante, pointeurs à gogo avec des memcmp et memcpy partout, tableaux statiques initialisés à 256 ou alors 10000 si ça suffit pas, création et manipulation de variables qui ne servent à rien, hardcoding un peu partout avec des bouts de code à commenter/dé-commenter, etc). Bref, tout ça pour dire que j'essaye de mettre de l'ordre dans tout ce merdier.
    Mouaip... Je comprends ton désarroi, et je compatis de tout coeur

    D'un point de vue contextuel, mon programme est sensé testé différentes applications (que j'ai pu classer par types). Chaque application à plusieurs modes de fonctionnements. Ces modes sont apparus par la suite, je les ai incorporés dans une classe feuille (application spécifique) dans un premier temps, pour voir si mes tests sur ces modes fonctionnaient correctement. Evidemment je n'ai pas envie de recopier ce code dans chaque classe feuille. C'est là que je les ai incorporés dans cette architecture en diamant actuelle. Je suis tout à fait prêt à changer mon architecture ! C'est d'ailleurs pour ça que je suis ici
    C'est déjà une bonne chose (excuses ma mauvaise interprétation de tes écrits lorsque tu as posé la question )
    D'un point de vue architectural:
    - J'ai tout en haut une classe Application qui définit toutes les opérations de base que peut effectuer une application. Elle contient aussi quelques opérations un peu plus compliquées qui implémentent un enchaînement d'opérations (enchaînement qui dépend du mode de fonctionnement).
    Hummm... De prime abord, une classe Application n'aura qu'une opération de base à effectuer : s'exécuter. Appelles la run() ou execute(), je m'en fous pas mal, mais ce devrait être le seul point d'entrée vers le reste

    Pour ce qui est des enchainements, ils n'ont sans doute rien à faire dans cette hiérarchie de classes.Un patron de conception de type "Command" serait sans doute le bienvenu. Chaque application aurait donc une "liste de commande à effectuer", et agirait sans doute toujours de la même manière : en exécutant ces commandes, les unes après les autres, dans l'ordre exact dans lequel elles apparaissent .

    Et, pour ce qui est des mode de fonctionnement, ce n'est pas non plus du ressort de la classe Application, et le patron de conception strategy (proposé avec tant d'insistance par ternel) semble effectivement adapté à cette situaiton

    - Juste en dessous j'ai une classe TestableApplication qui définit les tests génériques à toutes mes applications (utilise les opérations de base de la classe application et aussi les enchaînements contenus dans le mode. Cette classe est séparée de la classe Application principalement par souci de sémantique, pour séparer ce qui relève du test et ce qui n'en relève pas.
    Les séparation sémantique, c'est souvent plus error prone qu'autre chose.

    Tu m'aurais dit que cette classe mofifiait le comportement associé à l'exécution pour logger le résultat, j'aurais pu comprendre, mais là, je ne vois pas ce qu'elle apporte

    - Mes classes "type" définissent des types d'applications particuliers qui fonctionnent de la même manière. Elles héritent de la classe TestableApplication. Elles implémentent des tests spécifiques contenus à l'intérieur des test génériques de la classe TestableApplication. Elles implémentent aussi des opérations spécifiques appelés dans des opérations complexes de la classe application.
    Entre les stratégies (pour le mode d'exécution) et les commandes (pour savoir ce qui doit être fait), je ne suis pas sur que tu aies encore besoin de ces classes

    - Mes classes "mode" définissent un enchaînement d'opérations et aussi quelques tests spécifiques qui dépendent du mode de fonctionnement de l'application.
    Ces classes entrent dans la stratégie d'exécution
    Elles héritent de la classe TestableApplication
    Et c'est justement cela qu'il ne fallait pas faire...
    Que les différents modes interviennent dans une hiérarchie de classe, soit : il y a des chances pour qu'ils exposent également une fonction run() ou execute() (sans doute ne plus d'une fonction "configure(), qui sait ).

    Mais ce n'est pas parce que deux classes (Application et Mode, dans le cas qui nous intéresse) expose une fonction portant le même nom (run() ou execute(), selon le cas) qu'il faut forcément envisager de faire hériter l'une de l'autre.

    D'ailleurs, on ne peut pas d'avantage dire qu'une application EST un mode d'exécution que dire qu'un mode d'exécution EST une applicaton

    On peut dire d'une application qu'elle dispose d'un (ou de plusieurs) mode(s) d'exécution; on peut dire qu'un mode d'exécution est associé à une (ou à plusieurs) applicaiton(s). Mais ca s'arrête là

    Moralité : on est d'avantage dans une relation d'agrégation ou de composition (selon le sens d'où l'on regarde) que dans une relation d'héritage

    Certains de ces tests spécifiques sont appelés depuis une classe type, d'autre depuis la classe TestableApplication.
    Rien n'empêche les différents mode d'exécution (strategy) de contenir un certain nombre de commandes qui leur sont propre (voir qui sont propre à une application spécifique bien déterminée)

    - Enfin, mes classes feuilles qui définissent l'application finale qui est testée héritent d'une classe "type" et d'une classe "mode". Elles définissent des tests spécifiques à l'application précise.
    A partir du moment où l'on peut considérer que toute application utilise déjà une stratégie, à laquelle est déjà associé un ensemble de commandes (en plus de ses commandes spécifiques), tu n'a même plus besoin de cette dernière classe non plus

    Du coup je cherche à améliorer mon architecture car je vois bien qu'elle est bancale. Par améliorer, j'inclus aussi l'exploser et en changer complètement
    Ben je viens, en gros, de te la donner
    Rajoute une classe Factory, pour générer les différentes commandes, rajoute un ou deux utilitaires à gauche et à droite, et tu auras tout ce qu'il te faut

    Et la cerise sur le gâteau serait de faire une sorte d'interpéteur : un truc qui, sur base d'un fichier qui pourrait prendre 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
    test{
        nom : Un nom pour savoir ce qui est testé
        application{
            nom : nom de l'application à lancer
            mode {
                nom : le mode utilisé
                paramètres{ #ce seraient les paramètres propres au mode d'exécution ici
                   nom1 : valeur1
                   nom2 : valeur2
                   ...
               }
               commandes{
                   nom commande1{ # ici, ce sont les paramètres à fournir aux différentes commandes
                       nom parametre 1 : valeur
                       nom paramètre 2 : valeur
                   }
                   nom commande1{ # ici, ce sont les paramètres à fournir aux différentes commandes
                       nom parametre 1 : valeur
                       nom paramètre 2 : valeur
                   }
                   nom commande1{ # ici, ce sont les paramètres à fournir aux différentes commandes
                       nom parametre 1 : valeur
                       nom paramètre 2 : valeur
                   }
              }
        }
     
    }
    (syntaxe et organisation à concevoir... ce n'est qu'un exemple vite fait )
    pourrait automatiquement créer les différents modes des différentes applications ainsi que les commandes y afférant et lancer le tout... Mais bon, là, on s'écarte vraiment de ton problème de base
    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
    Nouveau membre du Club
    Inscrit en
    Mars 2007
    Messages
    53
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 53
    Points : 38
    Points
    38
    Par défaut
    Merci pour vos réponses.

    En effet, le strategy est je pense ce qu'il me faut. J'avais un peu peur de me lancer dedans et de me rendre compte au milieu que ça ne le faisait pas Avoir une référence de Application dans Mode devrait finalement suffire.

    Je ne suis pas encore sûr que le patron command ait du sens dans ma situation.

    Je vais commencer par le strategy, peut être avec du template. Je verrai ensuite pour le reste.

    Encore merci.

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

Discussions similaires

  1. [2008R2] Clause SELECT avec héritage par partition
    Par Jah73 dans le forum Développement
    Réponses: 4
    Dernier message: 29/10/2013, 15h00
  2. Héritage par prototype
    Par Invité dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 11/02/2013, 16h37
  3. Unités de persistance et héritage par jointure
    Par le2tbomi dans le forum Java EE
    Réponses: 0
    Dernier message: 04/10/2010, 15h04
  4. [MPD] alternatives à un héritage
    Par o.deb dans le forum Schéma
    Réponses: 4
    Dernier message: 12/07/2010, 23h08
  5. cherche alternative à l'héritage multiple
    Par X-dark dans le forum Langage
    Réponses: 4
    Dernier message: 22/04/2009, 17h04

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