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 :

Colle de généricité


Sujet :

C#

  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut Colle de généricité
    Bonjour,

    Sur cette page se trouve un didacticiel sur les fabriques à base de délégués.

    À la fin de la 3ème zone de code (orange), nous avons ce code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public static AObject ObjectCreator(params object[] list)
    {
        return new Class1();
    }
    N'aurait-il pas été possible d'utiliser la généricité pour ce code ?
    Car là, on doit répéter ce code pour chaque classe dérivée (ici, Class1 & Class2).

    Ou alors, y a-t-il un moyen pour indiquer "le type de la classe étant définie" ?
    Avec la réflexion, peut-être...

    Merci.

  2. #2
    Membre éclairé
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2005
    Messages
    700
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Juin 2005
    Messages : 700
    Par défaut
    Biensur tu peux, mais je pense que ca n'est pas trop le but de l'exercice.

    Je devinne qu'ils veulent t'apprendre le parterne, et donc tentent de rester simple.

  3. #3
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Ce n'est pas une question de "simplicité" ou autre, utiliser la généricité ici n'aurait eu aucun intérêt, c'est évident quand on se rappelle quel est le but du pattern "Factory".

    Citons Wikipedia : Fabrique (patron de conception) :
    La fabrique (factory) est un patron de conception créationnel utilisé en programmation orientée objet. Comme les autres modèles créationnels, la fabrique a pour rôle l'instanciation d'objets divers dont le type n'est pas prédéfini : les objets sont créés dynamiquement en fonction des paramètres passés à la fabrique.
    Autrement dit, AObject est une classe abstraite, l'implémentation est réalisée par les héritiers. Le consommateur de la Factory veut simplement un objet de type AObject, il se fiche de savoir si c'est un object "ClassA", "BObject" ou que sais-je : autrement dit il se fiche de savoir quelle classe réalise l'implémentation, tout ce qu'il veut c'est une classe qui respecte "le contrat AObject" (qui hérite/implémente AObject) et qui corresponde aux paramètres fournis à la Factory. D'ailleurs ces sous-classes ne lui sont peut-être même pas visibles. C'est le rôle de la Fabrique de déterminer le type particulier en fonction des arguments qui ont été fournis.

    Exemple classique du pattern Factory :
    * Véhicule est une classe abstraite, qui a pour héritiers les classes Moto, Voiture, Camion.
    * Fabrique possède une méthode "Véhicule Fabrique.Créer(string modèle)". Si le modèle spécifié est "Clio" on renverra une classe Voiture. Si le modèle est "33 tonnes", la fabrique renverra un objet de type Camion. Etc... Le consommateur de la Factory se fiche de savoir si l'objet est un camion ou autre, tout ce dont il a besoin ce sont des méthodes déclarées par Véhicule et implémentées par les héritiers.

  4. #4
    Membre éclairé Avatar de cs_ntd
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Décembre 2006
    Messages
    598
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Décembre 2006
    Messages : 598
    Par défaut
    Tu veux dire mettre de la généricité pour Class1 ? oui c'est possible.

    On peut faire ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    public static AObject ObjectCreator < AObjectType >(params object[] list)
        where AObjectType : AObject, //Pour dire que le type doit etre ou dériver de AObject
        new() //pour dire que le type implémente le constructeur par défaut
    {
        return new AObjectType();
    }
    Cet exemple marche tres bien. Le seul souci, c'est quand tu veux utiliser autre chose que le constructeur par défaut. Tu ne peux tout simplement pas. Tu ne peux pas faire new AObjectType(/*param*/).
    Ou alors il faut utiliser des astuces (reflexion...). Regarde ici :
    http://stackoverflow.com/questions/8...ructor-problem

    EDIT: Apres est-ce que c'est valable pour respecter la pure 'Abstract Factory j'en sais rien, mais c'est techniquement possible d'utiliser la généricité.

  5. #5
    Membre éclairé
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2005
    Messages
    700
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Juin 2005
    Messages : 700
    Par défaut
    Ce n'est pas une question de "simplicité" ou autre, utiliser la généricité ici n'aurait eu aucun intérêt, c'est évident quand on se rappelle quel est le but du pattern "Factory".
    Oui c'est ce que je voulais dire entre autre par :
    ca n'est pas trop le but de l'exercice.

    Je devinne qu'ils veulent t'apprendre le partern

  6. #6
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Oups, la lecture en diagonale, saylemal.

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Autrement dit, AObject est une classe abstraite, l'implémentation est réalisée par les héritiers. Le consommateur de la Factory veut simplement un objet de type AObject, il se fiche de savoir si c'est un object "ClassA", "BObject" ou que sais-je
    Je sais bien. Mais le morceau de code ci-dessus est dans l'implémentation de Class1, et sera à répéter pour chaque implémentation (Class2, Class3...). Cela me semble être un cas de redondance. La table de hachage nous informe déjà sur quel type d'objet il faut appeler la fonction de création, et donc du type d'objet qui va nous être retourné.

    Si on avait un truc du genre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return new curentObject.type()
    on resterait dans les concepts de la fabrique.

  8. #8
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Ouhlà, je n'avais pas lu la fin de l'article. C'est assez... tordu... et sale. Visiblement, l'intérêt du design est de pouvoir spécifier en amont la factory à utiliser pour instancier une classe donnée, on est plus proche d'une abstract factory.

    Effectivement, on aurait pu utiliser la généricité dans ce cas. Ca n'aurait pas permis d'écrire moins de code cela dit. Il faut toujours un objectcreator différent par classe. La seule chose que l'on aurait pu supprimer aurait été le champ "ClassType" (argh, que c'est laid).

    Le code que tu donnes ne colle pas : outre que ça ne pourrait jamais marcher (même si type() renvoyait un type, tu ne pourrait pas l'instancier avec new), ça viole la philosophie du pattern, qui est de ne pas savoir quel objectcreator se chargera d'instancier ton objet, même si dans l'exemple c'est à chaque fois l'objectcreator par défaut.

  9. #9
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Ouhlà, je n'avais pas lu la fin de l'article. C'est assez... tordu... et sale. Visiblement, l'intérêt du design est de pouvoir spécifier en amont la factory à utiliser pour instancier une classe donnée, on est plus proche d'une abstract factory.
    Pour moi, l'intérêt est de pouvoir associer dans un map une chaîne, par exemple, à un type une fabrique d'un type donné.
    Pourquoi trouves-tu ça sale ?

    Citation Envoyé par DonQuiche Voir le message
    Effectivement, on aurait pu utiliser la généricité dans ce cas. Ca n'aurait pas permis d'écrire moins de code cela dit. Il faut toujours un objectcreator différent par classe. La seule chose que l'on aurait pu supprimer aurait été le champ "ClassType" (argh, que c'est laid).
    Et pourquoi ne pas mettre le template dans la classe parente ? Pas possible de récupérer le type dynamique de la classe courante ?
    Sinon, quel intérêt de templatiser dans la classe fille ?

    Citation Envoyé par DonQuiche Voir le message
    Le code que tu donnes ne colle pas : outre que ça ne pourrait jamais marcher (même si type() renvoyait un type, tu ne pourrait pas l'instancier avec new),
    Pourquoi ?

    Citation Envoyé par DonQuiche Voir le message
    ça viole la philosophie du pattern, qui est de ne pas savoir quel objectcreator se chargera d'instancier ton objet, même si dans l'exemple c'est à chaque fois l'objectcreator par défaut.
    Moi, je ne cherche pas absolument à coller à une philosophie. Je veux répondre à mon besoin, c'est tout : on me file un nom de fichier, et en fonction de l'extension, créer l'objet qui va bien.

  10. #10
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Moi, je ne cherche pas absolument à coller à une philosophie. Je veux répondre à mon besoin, c'est tout : on me file un nom de fichier, et en fonction de l'extension, créer l'objet qui va bien.
    4. Tu as besoin d'une factory si tu veux ensuite manipuler ces objets sans connaître leur vrai type. Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MonFicher fichier = FileFactory.CreateFichier("c:\\truc.jpg");
    Fichier sera en fait du type MonImage mais, toi, tu t'en fiches : MonImage ne fournit qu'une implémentation et pas de nouvelles méthodes ou propriétés dont tu as besoin dans l'immédiat.

    En revanche, si tu as besoin de connaître le vrai type juste après l'instanciation alors il te faut... des constructeurs !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MonImage image = new MonImage("c:\\truc.jpg");
    En effet, à quoi bon créer une Factory si de toute façon, en-dehors de la factory, tu vas à nouveau avoir besoin de regarder l'extension pour savoir quel type tu dois récupérer ?

    Ou alors tu peux avoir besoin de différentes méthodes de création selon le contexte, une Factory abstraite pourrait alors se justifier. Un lecteur multimédia pourrait, à certains stades, vouloir charger toutes les données mais, à d'autres vouloir simplement les méta données. Dans ce cas, ok, une Factory pourrait être nécessaire : à un point A du code on donne à la Factory abstraite les Factory à utiliser pour chaque type de fichier (soit les instanciations pleines, soit les partielles) et, plus tard, à un point B du code, la Factory abstraite est utilisée pour instancier les fichiers, et ces fichiers sont consommés sans savoir si toutes les données ont été lues ou simplement les métadonnées.

    Enfin, si c'est simplement parce que tu as besoin d'enregistrer ton fichier dans différentes collections et autres, c'est un pattern builder qu'il te faut, pas une factory.



    1. C'est sale parce qu'en donnant un exemple dans lequel les codes numériques des types et les ObjectCreator sont tous deux des membres statiques des types, ça rend la Factory inutile, on aurait aussi bien pu utiliser des constructeurs. La Factory abstraite proposée peut néanmoins avoir son intérêt, il aurait fallu un meilleur exemple.

    2. J'ai du mal à saisir ce que tu veux dire. Si tu te contentes d'une méthode de création surchargée dans chaque type, ça s'appelle un constructeur. Si tu veux dire qu'on pourrait partir d'un ObjectCreator générique (se contentant de constructeurs sans arguments) avec une méthode Init() sur AObject que l'on surchargerait dans chaque classe fille, encore une fois pourquoi ne pas simplement utiliser un constructeur ?

    3. Le code suivant ne marche pas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    var type = typeof(AObject)
    new type();
    En effet, "type" n'est pas connu à la compilation donc, à l'exécution, il faudrait utiliser la reflection pour obtenir le constructeur à appeler. En revanche, tu peux faire Activator.Create(typeof(AObject)) ou Activator.Create((new AObject).GetType()), Activator se charge justement d'utiliser la reflection pour obtenir le constructeur et ensuite l'invoquer.

  11. #11
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Je dois créer une bibliothèque qui doit entre autre faire de la conversion entre formats. Mais l'utilisateur de cette bibliothèque doit être capable de l'étendre, en fournissant de quoi lire et écrire ce format, et de convertir ses données en un format pivot.

    Dès lors, je ne connais pas à l'avance tous les formats et classes. Je prévois donc que l'utilisateur enregistre sa classe dans la fabrique.

  12. #12
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Mettons pour la discussion que tu crées une biblio qui manipules des images. Je présume que tu as une classe racine Image, des héritiers ImageJpeg. Je présume qu'en sus d'enrichir la factory les plugins vont aussi définir de nouvelles classes filles : ImageTiff, etc. Gagné ?

    Dans ce cas, la généricité serait un boulet : tu vas manipuler des types que tu ne connais pas, tu peux pas les écrire dans ton code. Tout ce dont tu as besoin, c'est d'instancier une classe Image et tu te fiches du type exact.

    Voilà ce qui correspondrait à ce scénario, une bête fabrique extensible :
    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
     
    // La Factory ne peut pas être statique si tes plugins sont chargés dans des sandboxes puisque les membres statiques sont liés à un appdomain, gaffe.
    public static class Factory
    {
        public static void Register(string extension, Func<string, Image>);
        public static Image Create(string filename);
    }
     
    public namespace Plugin
    {
       public class ImageTga : Image
       {
            public ImageTga(string filename);
       }
     
       public static class PluginEntryPoint
       {
          // Méthode appelée au chargement du plugin
          public static void RegisterPlugin() 
          {
              Facotry.Register(".tga", (filename) => new ImageTga(filename));
          }
       }
    Si derrière tu dois tester un type particulier (par exemple, si tiff est votre pivot, tu peux vouloir faire un traitement particulier pour les classes ImageTiff), tu feras simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (image is ImageTiff)

  13. #13
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Mettons pour la discussion que tu crées une biblio qui manipules des images. Je présume que tu as une classe racine Image, des héritiers ImageJpeg. Je présume qu'en sus d'enrichir la factory les plugins vont aussi définir de nouvelles classes filles : ImageTiff, etc. Gagné ?
    Oui.

    Citation Envoyé par DonQuiche Voir le message
    Voilà ce qui correspondrait à ce scénario, une bête fabrique extensible :
    Je suis content de connaître le nom officiel : ça m'aidera, pour Google. :-)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    // La Factory ne peut pas être statique si tes plugins sont chargés dans
    // des sandboxes puisque les membres statiques sont liés à un appdomain, 
    // gaffe.
    Comprend pas trop... :-/
    Je pense que le terme sandbox est trop lié aux environnements C#/Java, que je ne connais guère.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public static void Register(string extension, Func<string, Image>);
    ...
    Factory.Register(".tga", (filename) => new ImageTga(filename));
    C'est quoi cette syntaxe (=>) ??

    edit : j'ai trouvé; mais apparu dans 2008 alors que j'utilise 2005... :-)

    Citation Envoyé par DonQuiche Voir le message
    Si derrière tu dois tester un type particulier (par exemple, si tiff est votre pivot, tu peux vouloir faire un traitement particulier pour les classes ImageTiff), tu feras simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (image is ImageTiff)
    En fait, je compte plutôt faire des convertisseurs génériques :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Converter<T1,T2>      // TGA & PNG, par exemple
    {
     
    }
    Pour le pivot, je ne sais pas encore si je fais dans chaque classe une fonction qui va remplir une instance de pivot passée en paramètre, ou si je fais deux Converter spécialisé pour chaque format. Exemple (ce n'est pas la bonne syntaxe, mais les types entres crochets sont les vrais types; on se comprend...) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Converter<TGA, pivot>
    Converter<pivot, TGA>

  14. #14
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Deux ou trois retours sur ce que tu as écrit.

    * "Fabrique" (factory) est le nom officiel, extensible est juste un adjectif que je lui ai accolé.

    * Une sandbox (bac à sable) est un environnement isolé dans lequel un code est exécuté afin qu'ils n'aient que des privilèges restreints et ne puisse pas corrompre l'application ou la machine. Ce n'est pas propre à dotnet (Firefox 4 et Chrome les utilisent par exemple pour les plugins et, pour Chrome, chaque page web) mais, pour dotnet, ils ont modélisés par des AppDomain. Un membre statique a une valeur différente dans chaque domaine afin de garantir l'isolation des données. Si tu ne comptes pas isoler tes plugins, pas de souci.

    * L'idée était d'utiliser un délégué plutôt que de te forcer à créer un nouvel ObjectCreator pour chaque classe. Tu peux utiliser la syntaxe traditionnelle en créant un type délégué "void ImageCreatorDelegate(string filename)" Les fonctions de créations pourraient alors être des méthodes statiques placées sur chaque type.

    * Encore une fois, la généricité est une mauvaise idée. Rappelle-toi : si ImageJpeg est défini dans un plugin, qui n'est pas référencé par ton assembly principale, le token "ImageJpeg" ne sera pas reconnu dans ton code et il t'est donc impossible d'instancier aisément un "Converter<ImageJpeg>". Même si tu en instanciais un par réflexion, à quoi bon ? Tu ne pourras le manipuler que comme un vulgaire "object" (ou tout autre type de base ou interface non-générique qui serait un token valable dans ton code).

    4. Concernant l'usage de convertisseurs, ça me semble effectivement une mauvaise idée. Voici les questions que tu devrais te poser.
    * Auras-tu besoin d'autres conversions que celles "vers le pivot" et "depuis le pivot" ?
    * Tes classes images exposeront-elles toutes les données nécessaires aux convertisseurs ou ces données sont-elles typiquement privées ?
    * Est-ce qu'une conversion "vers le pivot" ou "depuis le pivot" n'est pas typiquement de la responsabilité des images elles-mêmes (avec la possibilité d'ajouter des méthodes protégées dans la classe de base pour le code commun à toutes les conversions) ?



    Pour ma part, voilà ce que j'aurais fait :
    ImageData serait une classe neutre concernant toutes les données nécessaires à la création d'une autre image (tableau de pixels ou classe image gdi/windowdforms/wpf si tu passes par elles, format de couleurs, etc). J'utiliserais ça plutôt que le pivot directement, d'une part parce que ImageData pourrait exposer des données que tu préfèrerais masquer dans ImagePivot (tu ne veux sans doute pas exposer le tableau de pixels, encore moins un IntPtr), ensuite parce du coup cela permet de garder dans ImagePivot les détails de la création d'une image pivot à partir d'un tableau de pixels et autres, enfin parce que ImageData pourrait alors contenir tous les trucs "sales" (conversion d'un format de couleurs vers un autre)

    Délégués :
    * Image CreationFromFileDelegate(string filename)
    * Image CreationFromDataDelegate(Image data)

    Factory
    * static void Register(string extension, CreationFromFileDelegate a, CreationFromDataDelegate b)
    * static Image Convert(Image filename, string targetExtension)
    * static Image Create(string filename)

    ImageJpeg
    * static Image Create(string filename)
    * static Image Create(ImageData data)
    * override ImageData GetData()

Discussions similaires

  1. [architecture] pour de la généricité, vous feriez quoi ?
    Par Alec6 dans le forum Débats sur le développement - Le Best Of
    Réponses: 39
    Dernier message: 03/07/2006, 15h39
  2. [D7] généricité / portabilité d'une application
    Par Magnus dans le forum Bases de données
    Réponses: 7
    Dernier message: 01/09/2005, 09h39
  3. [Ada 95] Généricité de type/package
    Par kindool dans le forum Ada
    Réponses: 5
    Dernier message: 19/05/2005, 12h54
  4. Une colle CSS
    Par gael.mases dans le forum Mise en page CSS
    Réponses: 6
    Dernier message: 21/10/2004, 11h49

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