Problème de cast ou de design

Version imprimable

Voir 40 message(s) de cette discussion en une page
Page 1 sur 2 12 DernièreDernière
Je vais me documenter à ce sujet plutôt.
Citation:

Envoyé par poukill Voir le message
Iil y a déjà eu pas mal de question là dessus, une petite recherche sur le forum avec le mot clé downcasting devrait de donner pas mal d'infos (je me souviens avoir posé moi-même des questions là dessus il fut un temps).

Ok, merci beaucoup.
  • 20/02/2013, 15h31
    poukill
    Citation:

    Envoyé par LinuxUser Voir le message
    Je me suis pas encore penché sur les templates, peut être est-ce le moment?

    Attention, le template method est un pattern, le nom est trompeur... Rien à avoir avec les template C++.
  • 20/02/2013, 15h35
    jblecanard
    Citation:

    Envoyé par LinuxUser Voir le message
    Je me suis pas encore penché sur les templates,

    Je vote pour le visiteur comme te l'as conseillé Poukill, c'est un pattern utile à connaître.
  • 20/02/2013, 16h24
    Bousk
    Citation:

    Envoyé par LinuxUser Voir le message
    Je vois pas trop l'intérêt, j'ai l'impression que le problème est juste déplacé.

    L'intérêt c'est de passer non pas un Mere& mais un Trucoidal& en paramètre.
    L'héritage revient à factoriser des comportements et services. Devoir faire appel à une méthode spécifique à un type fille n'est pas du tout normal et montre au moins un gros manque architectural.
    Si ta méthode doit faire appel à une méthode de fille
    - elle doit prendre un fille en paramètre
    --> sinon elle traite comment les cas où c'est pas une fille mais fille2 ?!?
    - la méthode de fille n'est pas spécifique à fille mais spécialisée
    --> on factorise alors le comportement et service à travers cette méthode
  • 20/02/2013, 16h45
    r0d
    En fait, le problème qui se pose ici est assez courant, et il vient du fait que le protocole (ensemble des fonctions et variables publiques de la classe) de la classe mère est différent de celui de la classe fille. Ça viole le LSP, et donc, ça pose potentiellement tout un tas de problèmes (celui évoqué ici n'en est qu'un parmi bien d'autres).

    Donc effectivement, comme toujours, la solution consiste à ajouter un niveau d'indirection. Visiteur est généralement approprié.

    Parfois il n'est pas nécessaire de respecter LSP, mais c'est assez rare. Il s'agit de cas où on est sûr que le code qui utilise ces classes ne déclarera jamais un objet de type Mere, et donc qu'il n'utilisera que les classes héritées. Mais en dehors de ce cas rare, respecter LSP permet un confort non négligeable dans le cycle de développement. Confort dont on ne prend pas forcément conscience, puisque les problèmes ne posent pas... mais je m'égare...
  • 20/02/2013, 20h06
    LinuxUser
    Dans mon cas, je ne cherche pas à respecter LSP, car les classe Filles sont des implémentations différentes de la classe mère (qui n'est jamais instanciée et ne le sera jamais).

    En fait, mon idée c'est plutôt:
    * j'ai une classe mère abstraite : Mere
    * j'ai des classes filles qui implémentent Mere: Fille1, Fille2, Fille3
    * j'ai des fonctions qui utilisent Fille1 ou Fille2 ou Fille3 quasiment de la même manière, et donc pour factoriser je fait:
    Code:

    1
    2
    3
    void function(Mere& m)
    {...// code polymorphe qui fonctionne que ce soit Fille1, 2 ou 3
    }

    Sauf que dans cette fonction je voudrait faire un seul appel de méthode spécifique à Fille2 par exemple:
    Code:

    1
    2
    3
    4
    void function(Mere& m)
    {...// tout le code idem que précédemment
    m.methodeDeFille2();
    }

    Je trouverais dommage de devoir dupliquer du code à cause d'une seule méthode:
    Code:

    1
    2
    3
    void function(Fille1& f);
    void function(Fille2& f);
    void function(Fille3& f);

    Et donc avoir 3 fonctions avec 99% de code identique.
  • 20/02/2013, 20h41
    Bousk
    Ce que je trouve dommage c'est de faire de l'héritage pour quelque chose qui n'en est clairement pas. :cry:
    Et vouloir factoriser des lignes de code, bien que je sois le premier à dire que les programmeurs sont fénéants, est une très mauvaise idée.
  • 20/02/2013, 21h21
    LinuxUser
    Citation:

    Envoyé par Bousk Voir le message
    Ce que je trouve dommage c'est de faire de l'héritage pour quelque chose qui n'en est clairement pas. :cry:

    Même si ce n'est pas LSP, les classes filles partagent certaines caractéristiques, donc l'héritage ne me semble pas absurde de ce cas.
    Citation:

    Envoyé par Bousk Voir le message
    Et vouloir factoriser des lignes de code, bien que je sois le premier à dire que les programmeurs sont fénéants, est une très mauvaise idée.

    Je devrais dupliquer les méthodes pour chaque implémentations?
  • 20/02/2013, 21h31
    Flob90
    Bonsoir,

    J'ai l'impression que la discussion tourne un peu dans le vide. Si arriver à ce problème est classique, les moyens de le résoudre sont nombreuses et dépendent énormément de l'objectif du code.

    Tu pourrais concrétiser un peu le contexte de ton code ? Ça serait plus simple pour t'indiquer une solution réellement adaptée. Idéalement avec un mini exemple de la façon dont doit être utilisé le code (ça donne de nombreuse indications dans les choix techniques).
  • 21/02/2013, 11h45
    Médinoc
    Si Mere est une classe abstraite, pourquoi ne pas tout simplement lui donner également une méthode abstraite?

    Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    struct Mere {
    	virtual void TraitementPourFonction() = 0;
    };
    struct Fille1 : public Mere {
    	void TraitementPourFonction();
    };
    struct Fille2 : public Mere {
    	void TraitementPourFonction();
    };
     
     
    void function(Mere& m)
    {
    	//Code pour les 3
    	//...
    	m.TraitementPourFonction();
    	//Code pour les 3
    	//...
    }

  • 21/02/2013, 15h10
    koala01
    Salut,
    Citation:

    Envoyé par LinuxUser Voir le message
    Dans mon cas, je ne cherche pas à respecter LSP, car les classe Filles sont des implémentations différentes de la classe mère (qui n'est jamais instanciée et ne le sera jamais).

    C'est, en gros, ce que les piliers de la programmation orientée objets conseillent de manière très appuyée avec le I de SOLID (qui est mis pour ISP: Interface Segregation Principle) ;)

    Mais cela ne veut en aucun cas dire qu'il faille accepter de ne pas respecter LSP pour la cause :aie:

    Que ta classe de base soit une classe "tout ce qu'il y a de plus classique", une classe abstraite ou même qu'elle ne présente qu'un partie d'interface, dés le moment où tu envisages l'héritage, le LSP te donne un GO/ NO GO qui t'indique si, oui ou non, tu peux effectivement y recourir.

    Si tu acceptes de ne pas respecter ce principe de base, tu peux te dire que tu dénature totalement la philosophie même de la programmation orientée objets parce que tu acceptes l'idée de ne pas t'intéresser à la substituabilité de tes classes :aie:

    Bref, s'il est vrai que l'intérêt d'un respect stricte de LSP ne saute pas forcément aux yeux au début, il faut être conscient du fait que son non respect se paye cash très rapidement ;)
    Citation:

    En fait, mon idée c'est plutôt:
    * j'ai une classe mère abstraite : Mere
    * j'ai des classes filles qui implémentent Mere: Fille1, Fille2, Fille3
    * j'ai des fonctions qui utilisent Fille1 ou Fille2 ou Fille3 quasiment de la même manière, et donc pour factoriser je fait:
    Code:

    1
    2
    3
    void function(Mere& m)
    {...// code polymorphe qui fonctionne que ce soit Fille1, 2 ou 3
    }


    C'est ce à quoi servent les fonctions virtuelles et leur pendant: les comportements polymorphes ;)
    Citation:

    Sauf que dans cette fonction je voudrait faire un seul appel de méthode spécifique à Fille2 par exemple:
    Code:

    1
    2
    3
    4
    void function(Mere& m)
    {...// tout le code idem que précédemment
    m.methodeDeFille2();
    }

    Je trouverais dommage de devoir dupliquer du code à cause d'une seule méthode:
    Code:

    1
    2
    3
    void function(Fille1& f);
    void function(Fille2& f);
    void function(Fille3& f);

    Et donc avoir 3 fonctions avec 99% de code identique.
    Sauf que, si tu envisages la possibilité de devoir appeler une fonction particulière de Fille2 dans une situation particulière, tu peux partir du principe sans risquer de te tromper que, tôt ou tard, tu voudras aussi pouvoir utiliser une fonction particulière de Fille3 ou de Fille5 (qui est encore à créer :D) dans certaines circonstances particulières, auxquelles tu ne penseras qu'au moment où tu envisageras des évolutions.

    La question n'est pas ici de savoir SI cela arrivera, car je peux t'assurer que tu y viendras, mais bien de savoir QUAND cela arrivera ;)


    De plus, l'idée n'est pas de dupliquer du code, mais de déléguer les responsabilités afin, justement, d'éviter les duplications de code inutiles ;)

    Or, si tu veux éviter d'avoir sans arrêt à vérifier le type réel de la variable pointée par un pointeur "passant pour être" du type de base afin de savoir comment la manipuler, la meilleure solution passe par le double dispatch.

    Il s'agit de se baser sur le principe que la variable qui est pointée par un pointeur "passant pour être du type de base" connait parfaitement son type réel et que c'est ce type réel qui sera transmis en paramètre si on décide d'appeler n'importe quelle fonction en passant this (ou *this).

    L'idée est donc de prévoir un comportement polymorphe (autrement dit, une fonction virtuelle (pure si besoin) ;)) dans la classe de base dont le comportement adapté dans les classes dérivées aura pour résultat d'appeler une fonction particulière prenant une référence (ou un pointeur si besoin) sur un objet du type réel.

    Le pattern visiteur est une des manières régulièrement utilisées pour mettre le double dispatch en oeuvre ;)
    Citation:

    Envoyé par LinuxUser Voir le message
    Même si ce n'est pas LSP, les classes filles partagent certaines caractéristiques, donc l'héritage ne me semble pas absurde de ce cas.

    Je devrais dupliquer les méthodes pour chaque implémentations?

    NON!!!

    Soit LSP est respecté et l'héritage est cohérent, soit LSP n'est pas respecté et tu NE PEUX PAS envisager l'héritage.

    Il faut bien se rappeler ici que LSP est un principe de conception, c'est à dire qu'il s'agit d'un principe qui doit être utilisé pour décider s'il est opportun ou non de recourir à l'héritage.

    Soit LSP est respecté, et l'on peut alors envisager de recourir à l'héritage sans problème (ce qui ne veut pas du tout dire qu'il faudra d'office y recourir ;)), soit LSP n'est pas respecté, et, dans ce cas, il ne faut absolument pas recourir à l'héritage ;)

    Il s'agit donc, comme je l'ai indiqué plus haut d'un GO / NO GO: On peut le faire (sans y voir la moindre obligation cependant) si on a le GO, on ne peut pas le faire (sans y voir la moindre exception) si on a le NO GO ;)
  • 21/02/2013, 15h38
    white_tentacle
    Citation:

    Envoyé par koala01 Voir le message
    Soit LSP est respecté et l'héritage est cohérent, soit LSP n'est pas respecté et tu NE PEUX PAS envisager l'héritage.

    J’appuie tout ce qu’a dit Koala, et me permets de suggérer un héritage privé / protégé dans le cas présent. Si tu hérites afin de factoriser du code, mais que tu ne souhaites pas de polymorphisme, c’est une solution envisageable.
  • 21/02/2013, 15h49
    LinuxUser
    OK, donc je viens de comprendre que je n'aurais pas dû utiliser l'héritage dans mon cas.

    Mais justement, dans mon cas où les classes ont un comportement très proches (à quelques exeptions près), la seule solution est le pattern visiteur?

    Et donc si je recommencais le design des classes (sans les faire hériter d'une classe mère), je les traiterai indépendemment sans rapports les unes avec les autres?
  • 21/02/2013, 15h57
    Flob90
    Tu ne veux vraiment pas détailler un peu ton problème de manière concrète ?

    Parce que personnellement quand tu dis que tu as factorisé des comportements dans des classes mères qui ne seront jamais instancié seules, je pense aux classes de politique (qui sont bien réalisées via des héritages publique en général, mais avec une nuance au niveau du destructeur).
  • 21/02/2013, 16h05
    LinuxUser
    OK, je rédige proprement et je post.
  • 21/02/2013, 17h10
    koala01
    Pour ce que j'entrevois, dans la brume de ton imprécision actuelle, de ton problème, je dirais que la solution passe, décidément, par une délégation correcte des responsabilités.

    Quitte à "tout recommencer", je te conseillerais vivement de veiller en permanence à ce que chaque classe n'ait jamais qu'une et une seule responsabilité clairement définie (si possible autre que de "gérer les machins" :D)

    De cette manière, si deux classes distinctes (et n'ayant aucun lien entre elles) doivent avoir un comportement plus ou moins similaire, il te sera facile d'utiliser, justement, une troisième classe dont la responsabilité serait de présenter le comportement en question ;)
  • Voir 40 message(s) de cette discussion en une page
    Page 1 sur 2 12 DernièreDernière