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 :

Interfaces, dérivés et appelants : problème de cast implicite


Sujet :

C#

  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut Interfaces, dérivés et appelants : problème de cast implicite
    Bonjour à tous,

    Je suis face à un souci concernant l'archi sur laquelle je travaille.
    Souhaitant utiliser au mieux les interfaces et le polymorphisme, je me retrouve dans une situation où j'ai une interface de base, et des interfaces dérivées.
    J'ai une fonction par interface dérivée attendant en paramètre un modele de type Iquelquechose mais toutes les fonctions portent le même nom (admettons "Save" pour l'exemple).
    Jusque là, tout va bien. Si je fais appel à ma fonction Save en utilisant n'importe quel model à partir du moment où ceux ci implémentent bien une des interfaces, je suis routé vers le bon traitement.

    Maintenant, j'ai un souci si jamais je créé une fonction qui va venir ajouter une couche au dessus.
    Exemple : Je créé une fonction TraitementMetier qui va commencer par faire un traitement X puis fait appel à la fonction Save puis fait un traitement Y.
    Donc ma fonction traitementMetier attend un model de type IBase qui est l'interface mère dont héritent toutes les interfaces citées plus haut.

    Si j'execute cette fonction, cela appellera toujours la fonction Save dont le paramètre attend un IBase et ne routera jamais vers les traitements spécifiques des autres interfaces.
    Je pense que cela vient du fait que TraitementMetier attendant un paramètre de type IBase, force l'appel à Save(IBase m) mais comment contourner ce problème du coup ?
    C'est dommage car cela m'évitais de devoir tester si le type de mon model implémente telle interface alors je force le cast dans ce type sinon si c'est telle autre interface etc...

    Auriez vous une idée ?

  2. #2
    Membre Expert Avatar de Guulh
    Homme Profil pro
    Inscrit en
    Septembre 2007
    Messages
    2 160
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2007
    Messages : 2 160
    Par défaut
    Hello,

    problématqiue fréquente Formulé autrement : comment avoir un comportement polymorphique en dehors d'une hiérarchie de classe ?

    Ce que tu recherches, ça s'appelle le double dispatch. Certains langages ont cette fonctionnalité de base, d'autres pas (dont C++, C# et Java).
    Il existe un Design Pattern pour cela: le visitor. Je te laisse googler.

    Depuis C# 4, le mot clé dynamic permet de se passer de visitor (au prix d'une type-safety un peu moindre). Tu trouveras plus de détails ici, ou les deux solutions sont abordées.

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut
    Salut et merci pour ta réponse.
    Alors pour tout te dire, j'ai triché un peu ^^ je suis en VB.Net
    J'ai posté dans le forum C# parce qu'en général c'est plus réactif au vu de la communauté plus grande.
    Je n'ai à priori pas bien capté le pattern mais bon je vais essayer de plancher là dessus et voir si j'arrive à m'en sortir avec.
    Merci du tuyau en tout cas.

  4. #4
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 204
    Par défaut
    moi en voyant le topic j'ai regardé un peu et j'ai cru comprendre qu'on avait pas ce problème en vb.net, et que nous n'avions pas le mot clé dynamic pour cette raison

    j'aimerais bien tester pour voir ce que ca donne mais je n'ai pas trop compris le schéma de tes interfaces et classes ...
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  5. #5
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut
    Ah là tu m'interesses (et au passage : j'ai voulu chercher sur le net mais honnêtement je ne savais pas comment poser le probleme sur google... Par quels genre de mots clés t'es passé pour avoir des résultats parlant de cette problématique ?)

    Sinon, concernant les tests, voilà comment tu peux procéder (c'est très rapide à faire) :
    Tu créés un projet Windows Forms classique avec 3 répertoires :
    - Interfaces
    - Objects
    - Logic

    Dans Interfaces, tu créés 2 interfaces :
    - IBase avec Nom as string et prenom as string
    - IDerivee qui hérite de IBase et tu ajoutes une property DateNaissance as datetime

    Dans Objects, tu créés 2 objets :
    - PersonBase qui implémente IBase
    - PersonDerivee qui implémente IDerivee

    Dans Logic, tu créés 2 classes :
    - PersonLogic dans laquelle tu créés deux méthodes save
    --- save(p as IBase) : cette sub fait juste messagebox.show("Base")
    --- save(p as IDerivee) : cette sub fait juste messagebox.show("derivee")
    - BusinessLogic dans laquelle tu créés la méthode suivante
    --- DoBusinessWork(p as IBase) dans laquelle tu executes les points suivants :
    -------+ messagebox.show("pré traitement")
    -------+ tu fais appel à PersonLogic.save(p)
    -------+ messagebox.show("post traitement")

    Enfin, sur ta form, tu ajoutes deux boutons dont les événements font le traitement suivant :
    - l'un va créer une instance de PersonBase et faire appel à businessLogic.DoBusinessWork en l'envoyant en paramètre
    - l'autre va créer une instance de PersonDerivee et faire appel à businessLogic.DoBusinessWork en l'envoyant en paramètre

    Tu verras que dans les deux cas, le messagebox.show qui sera appelé sera celui qui affiche "Base" et non l'un base et l'autre derivée.

  6. #6
    Membre Expert Avatar de Guulh
    Homme Profil pro
    Inscrit en
    Septembre 2007
    Messages
    2 160
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2007
    Messages : 2 160
    Par défaut
    Citation Envoyé par zax-tfh Voir le message
    Ah là tu m'interesses (et au passage : j'ai voulu chercher sur le net mais honnêtement je ne savais pas comment poser le probleme sur google... Par quels genre de mots clés t'es passé pour avoir des résultats parlant de cette problématique ?)
    Cherche "visitor pattern", tout simplement. C'est l'un des principaux design patterns. Ma remarque ne sous-entendait rien de péjoratif, juste que tu trouveras pas mal de littérature sur le net sans que je doive tout décrire ici
    En deux mots, disons que t'as une classe mère M, et deux filles F1 et F2. Si tu veux mettre ton comportement polymorphique directement dans les classes, ça donne ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class M { public abstract string F(); }
     
    class F1 : M { public override string F() { return "je suis F1"; } }
    class F2 : M { public override string F() { return "je suis F2"; } }
    Basique donc. Par contre, si tu veux que les methodes F soient en dehors, le pattern visiteur consiste à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class M { public abstract Accept(Visitor v); }
    class F1 { public override Accept(Visitor v) { v.Visit1(this); }
    class F2 { public override Accept(Visitor v) { v.Visit2(this); }
    public class Visitor
    {
      public void Visit1(F1 obj) { ... }
      public void Visit2(F2 obj) { ... }
    }
     
    M obj = new F1();
    var v = new Visitor();
    obj.Accept(v); // va exécuter Visit1
    Tu vois ? Ca permet de mettre dans une seule classe (ici Visitor) les méthodes spécifiques a chaque sous-type, et c'est la méthode accept qui va rediriger (dispatch en anglais) vers la bonne en fonction du type réel de l'objet.

  7. #7
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 204
    Par défaut
    je n'ai jamais rencontré ce cas de figure car je ne code pas ainsi, et puis j'aurais senti venir l’ambiguïté je pense ^^
    moi j'aurais mis la méthode Save sur l'interface de base, ou un autre truc plus simple, mais qui je l'avoue ne permet pas de décomposer autant le code
    j'ai essayé de bricolé un truc avec les generics mais ca n'arrange rien
    les méthodes anonymes et un dictionary devraient pouvoir pallier aussi, ca reste du code explicite à écrire mais peut etre moins que le visitor
    il y a aussi la reflection en truc moyen ^^
    ou encore l'attribut qui reste plus pratique que les méthodes anonymes et moins freestyle que la reflection pure
    je vais détailler un peu ca vu que ca me semble être une bonne solution, créer une classe d'atrribut qui servira à trouver la méthode save, dans la méthode save générique demander .gettype et trouver via l'attribut la méthode à appeler par reflection (à stocker dans un dictionary of type, action par exemple)

    en bref il doit y avoir plein de solutions
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  8. #8
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut
    Citation Envoyé par Guulh Voir le message
    Cherche "visitor pattern", tout simplement. C'est l'un des principaux design patterns. Ma remarque ne sous-entendait rien de péjoratif, juste que tu trouveras pas mal de littérature sur le net sans que je doive tout décrire ici
    Je ne l'avais pas mal pris t'inquietes pas c'etait juste que je me demandais comment on pouvait avoir un resultat probant sous google (sans connaitre l'existance du pattern visitor) parce que du coup je me suis retrouvé devant une feuille blanche à pas savoir quoi mettre en recherche

    @Pol63 : il y a beaucoup plus efficace et plus propre que les bidouilles avec reflection et compagnie : l'utilisation des interfaces, des generics et du mot clé new. exemple (désolé c'est en vb pour le coup, je ne connais pas bien la syntaxe c# pour ce cas précis) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public function GetMyElements(Of T as {new, IDerivee})() as T
    Au pire, dans mon cas, si je devais "bidouiller" je ferais juste un gros test dans ma méthode DoBusinessWork (désolé, toujours en VB...) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    If typeOf p is IDerivee Then
        Save(DirectCast(p, IDerivee))
    ElseIf TypeOf p is IBase Then
        Save(p)
    Else
        Throw new notimplementedException()
    End If
    C'est moins sexy mais ça a le mérite de fonctionner sans trop de contournement. Ah et ce qui est clair c'est que je ne peux pas me permettre de mettre la logique dans le model lui même.
    Toujours est-il que je vais tester ce matin le fonctionnement du pattern et voir comment l'adapter à mon cas et je reviendrais vous tenir au courant
    Merci pour votrre interet en tout cas

  9. #9
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut
    D'ailleurs, je reviens sur le pattern Visitor : pour moi c'est une aberation par rapport à l'archi de l'appli sur laquelle je suis.
    Pour faire simple : on a des briques métier étanches composées d'un projet de type class library dans lequel on a les traitements métier et un projet d'interfaces.
    N'importe quelle appli, n'importe quel framework peut utiliser ces interfaces et venir se brancher sur la brique métier : tout est injécté de l'exterieur en terme d'objets (utilisation des génériques, interfaces et mot clé new quand il s'agit de récupérer des données provenant de la brique et utilisation de simples interfaces quand il s'agit d'envoyer des données dans la brique métier).
    Du coup, dans cette optique, demander à chaque personne implémentant les interfaces qu'ils fassent en sorte que leur méthode accept soit correctement codée, c'est moyen je trouve. Ca implique que tout le monde prenne conscience de son utilité, sache comment ça fonctionne et surtout qu'ils se préoccupent d'un truc qui fonctionne à l'interieur d'une brique dont ils n'ont pas et ne doivent pas forcement avoir conscience.

  10. #10
    Membre Expert Avatar de Guulh
    Homme Profil pro
    Inscrit en
    Septembre 2007
    Messages
    2 160
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2007
    Messages : 2 160
    Par défaut
    Citation Envoyé par zax-tfh Voir le message
    Du coup, dans cette optique, demander à chaque personne implémentant les interfaces qu'ils fassent en sorte que leur méthode accept soit correctement codée, c'est moyen je trouve.
    C'est là que tu te trompes Comme tu peux le voir, Accept consiste juste à appeler v.Visit(this). Accept est juste un intermédiaire, qui permet "d'exporter" le polymorphisme. Aucune intelligence particulière. Par exemple , une classe Bidule peut ne dépendre que d'un IBiduleVisitor, qui pourraient avoir plusieurs implémentation concrètes (SaveVisitor, DisplayVisitor, ...)

    Ceci dit, je ne connais pas exactement ton contexte métier, cette solution est peut être overkill. mais elle a l'avantage d'être un pattern standard et reconnu.

  11. #11
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut
    Le but est vraiment de faire que ce fonctionnement reste interne à la brique métier et qu'il n'y ait rien d'autre à faire que d'injecter des données et en récupérer. Là est tout ma problématique :/ sinon je l'aurais volontiers implémenté ce pattern ^^
    Le contexte métier ? imagines qu'on te demande de faire une brique métier et de fournir une DLL qui doit fonctionner le plus simplement du monde et sans que la personne qui s'en sert n'ait à comprendre quoi que ce soit

  12. #12
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 204
    Par défaut
    Citation Envoyé par zax-tfh Voir le message
    @Pol63 : il y a beaucoup plus efficace et plus propre que les bidouilles avec reflection et compagnie
    c'est sur que la reflection peut faire peur, car elle est souvent utilisée pour appeler un membre à partir de son nom, mais ce n'est pas la seule utilisation possible

    Citation Envoyé par zax-tfh Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    If typeOf p is IDerivee Then
        Save(DirectCast(p, IDerivee))
    ElseIf TypeOf p is IBase Then
        Save(p)
    Else
        Throw new notimplementedException()
    End If
    pour info pour l'utilisation avec un select case (ou switch vu qu'on squatte le forum c# ^^)
    Code VB.NET : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    select case true
      case typeof p is IDerivee
      case machin
      case else
    end select

    et donc ce que je disais pour ma reflection c'est en gros de remplacer ce select case par du code générique via la reflection et un attribut, niveau performance tu perd au moins 100ms au démarrage de l'appli mais après c'est relativement transparent
    et ca fonctionne aussi si quelqu'un hérite dans une autre dll sans modifier le code de base, alors qu'avec un select case ce n'est pas le cas vu qu'il est codé en dur



    Citation Envoyé par zax-tfh Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public function GetMyElements(Of T as {new, IDerivee})() as T
    tu peux m'expliquer ce que fait le new ici ? je ne connais pas cette syntaxe pour les generics
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  13. #13
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2003
    Messages
    837
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Février 2003
    Messages : 837
    Par défaut
    Citation Envoyé par Pol63 Voir le message
    tu peux m'expliquer ce que fait le new ici ? je ne connais pas cette syntaxe pour les generics
    Ahahhhhhh c'est le truc ultime, le saint graal du saint graal
    Que je t'explique :

    Imagines que tu sois dans le cas que je citais plus haut : tu as deux projets :
    - une librairie (j'utilise la PCL dans mon cas) avec uniquement tes interfaces
    - une librairie (class library classique) avec toutes tes classes métier / accès aux données

    Maintenant, imaginons que tu aies plusieurs applicatifs qui s'appuient sur ce même code métier :
    - une appli WPF
    - une appli ASP MVC
    - un webservice WCF couplé à une appli Silverlight

    Dans le cas où tu fais de l'injection de données en base, tu fais transiter tes données dans le sens Appli -> code métier. Là, rien de bien méchant, ton code métier attend un objet, quel qu'il soit, tant qu'il implémente l'interface que tu as mis dans ton argument.
    Exemple :

    public void Save(IPerson person)

    Maintenant, voyons l'interet du new. Dans le cas où tu fais de la récupération de données, donc, dans le sens code métier -> appli, tu vas devoir retourner, à partir de ton code métier, des objets remplis avec tes données. Sauf que moi, utilisant ce code métier à partir de différentes applis dans des objets aussi différents les uns que les autres, je ne veux pas recevoir d'objet provenant de la couche métier. Je veux que la couche métier remplisse un objet provenant de l'appli. Tu comprendras que le fait de vouloir ça est impossible car il faudrait que l'appli reference la couche métier pour pouvoir y faire appel et que la couche métier référence l'appli pour qu'elle connaisse les objets de l'appli et les remplisse => boum, reference circulaire et en plus on ajoute une couche de spécifique dans une couche métier qui ne doit pas comporter de liens spécifiques avec l'appli.

    La déclaration public function GetMyElements(Of T as {new, IDerivee})() as T permet de dire :
    Je vais te retourner n'importe quel objet qui provient de l'exterieur, du moment qu'il implémente cette interface, je me débrouillerai.
    Maintenant, si tu fais ça sans le mot clé new, je te mets au défi d'instancier un objet de ce type à l'intérieur de la fonction, tu ne pourras pas. Avec le mot clé new, tu peux faire un new T, il fonctionnera sans problème. Donc voila, à l'intérieur d'une couche métier, grace à cette déclaration, tu n'as pas à avoir de référence vers l'objet que tu as besoin d'instancier.

    Grace à ca, ma couche métier peut me retourner n'importe quelle enveloppe provenant de mes applications (ex : un model ASP MVC, un DTO pour mes webservices, whatever mais à chaque fois ils auront des particularités propres à chaque frameworks, on évite ainsi de mélanger des annotations de validation ASP MVC avec des annotations WCF avec des raisepropertychanged silverlight...)

    Au final j'ai, d'un coté, ma librairie avec code métier et interfaces, de l'autre coté, mes applis et les models respectifs à chaque appli. la séparation est propre et nette, pas d'interdépendance pourrav, généricité absolue, bref, un truc qui saura vivre impeccablement sans avoir à se casser la tete à transferer des données d'un objet à un autre (comme avec automapper et consorts) pour traverser les couches

  14. #14
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 204
    Par défaut
    oui syntaxe inintéressante, mais c'était déjà possible d'instancier des types de plus haut niveau sans référence
    Code VB.NET : 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
     
    Public Class Form1
     
        Public Sub New()
            InitializeComponent()
     
            Dim a1 = CreateAndReturn(Of A)()
            Dim a2 = CreateAndReturn(Of A)(GetType(A))
        End Sub
     
        Public Shared Function CreateAndReturn(Of T As {New, Ia})() As T
            Dim a As New T
            Return a
        End Function
     
        Public Shared Function CreateAndReturn(Of T)(ty As System.Type) As T
            Dim a = DirectCast(System.Activator.CreateInstance(ty), T)
            Return a
        End Function
     
    End Class
     
     
     
    Public Interface Ia
        Sub a()
    End Interface
     
    Public Class A
        Implements Ia
     
        Public Sub a() Implements Ia.a
        End Sub
    End Class
    et il y a d'autres méthodes parce que ce code est tout de même moins sécure
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

Discussions similaires

  1. [CASTS]problème de cast de Time
    Par DeVoN dans le forum Langage
    Réponses: 7
    Dernier message: 22/02/2006, 18h24
  2. [JDBC Driver][JSTL] Problème de cast de données
    Par GyLes dans le forum PostgreSQL
    Réponses: 1
    Dernier message: 27/09/2005, 11h00
  3. problème de cast!
    Par LaseLiep dans le forum Langage
    Réponses: 3
    Dernier message: 03/06/2005, 10h30
  4. Problème de cast/serialization/externalization ?
    Par Linlin dans le forum CORBA
    Réponses: 1
    Dernier message: 06/12/2004, 17h46
  5. [C#] Problème de casting de @IDENTITY
    Par bilb0t dans le forum Accès aux données
    Réponses: 7
    Dernier message: 03/09/2004, 10h42

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