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 :

Design Patterns & ((im)Poly)morphisme


Sujet :

C#

  1. #1
    Membre régulier
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    116
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Orne (Basse Normandie)

    Informations professionnelles :
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 116
    Points : 100
    Points
    100
    Par défaut Design Patterns & ((im)Poly)morphisme
    Glop, glop.
    (désolé pour le jeu de mot dans l'objet, je n'ai pas pu m'empêcher ^^).

    J'ai le cahier des charges suivant :
    - 2 structures de fichiers HTML différentes (amdb et scc) contenant des données d'animaux. Chaque fichier HTML contient des données communes comme le nom de l'animal mais pas avec le même XPath.
    - les données extraites ont une partie commune stockée dans ExternalAnimal. Les données ont aussi des champs spécifiques uniques à chacune des 2 sources HTML utilisées.
    - une fois les données extraites, elles sont exportées en CSV (via CsvHelper).
    - le CSV sera ensuite utilisé avec EntityFramework pour importer les données dans une BDD.

    - J'ai restructuré le code pour être plus générique dans la façon d'importer ou d'exporter les données pour ExternalAnimal.
    En effet, on pourrait vouloir exporter en XML au lieu du CSV. Mais on peut aussi charger le CSV ou le XML pour exporter vers la BDD.

    Donc, je me dis que ExternalAnimal et ses classes dérivées n'ont pas lieu de connaitre qui leur envoie des données (CSV, XML, HTML...)

    1er problème dans HtmlAnimalLoader :
    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
    public void Load([Required] AnimalConfiguration config, [Required] ExternalAnimal animal)
            {
                AnimalFileConfiguration fileConfig = config as AnimalFileConfiguration;
                Debug.Assert(fileConfig != null, "config is not a AnimalFileConfiguration type.");
     
                // Load HTMl source file.
                string fullPath = Path.Combine(fileConfig.Path, fileConfig.Filename);
                Debug.Assert(File.Exists(fullPath), $"File NOT found {fullPath}");
                string htmlContent = FileUtility.ReadFile(fullPath, fileConfig.Encoding);
     
                // Manage HTML DOM
                var doc = new HtmlAgilityPack.HtmlDocument();
                doc.LoadHtml(htmlContent);
     
                this.ExtractHtmlDatas(fileConfig, doc, htmlContent, animal);
            }
    J'ai du caster vers un type dérivé. Je ne vois pas trop comment utiliser le polymorphisme dans ce cas précis. Alors Est ce que c'est mal d'avoir faire le cast là ? (très probablement je dirais).

    2ème problème lors de l'extraction des données dans ExtractHtmlDatas, je suis aussi obligé de caster ExternalAnimal dans la classe dérivée pour pouvoir remplir les champs (communs ou spécifiques).

    Est ce que je suis fatigué à ce point que j'ai pondu une architecture boiteuse ?

    Voici le diagramme de classes :
    Nom : Design Patterns et polymorphisme.PNG
Affichages : 186
Taille : 67,3 Ko

    Merci,
    Vincent

  2. #2
    Membre régulier
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    116
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Orne (Basse Normandie)

    Informations professionnelles :
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 116
    Points : 100
    Points
    100
    Par défaut
    Pour le problème n°1 :

    On pourrait contourner le soucis en créant un LoadFromFile() et un LoadFromDb().
    Le premier implémenterait l'interface IAnimalFileReader et l'autre IAnimalDbReader.
    Comme ça, plus besoin de caster mais on a 2x plus d'interfaces.

    Pour le problème n°2 :

    On pourrait se dire que ExternalAnimal étant un objet de transition, est ce que l'utilisation de l'héritage pour quelques champs spécifiques à chaque classe ne va pas entrainer par la suite une complexité disproportionnée ?
    Donc il faudrait tout remonter des classes dérivées vers ExternalAnimal et supprimer l'héritage.

    Verdict ?

  3. #3
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 757
    Points : 10 697
    Points
    10 697
    Billets dans le blog
    21
    Par défaut
    Je pense que la difficulté vient du mélange de deux choses (enfin si j'ai bien compris) :
    • d'un côté, on a un import de données, à partir d'une source HTML
    • d'un autre côté, on un un chargement/sauvegarde à partir d'une source Csv/Xml/BD/etc...


    Si un processus d'import est proche d'un chargement, il n'en reste pas moins plus compliqué, avec des sources de données hétérogènes, dans des formats différents, avec des conversions à réaliser, du nettoyage, de la normalisation, etc... tandis que les chargement/sauvegarde ne sont ni plus ni moins qu'un problème de sérialisation.

    Avec un import, on charge en général des données en provenance de l'extérieur du logiciel (=pas généré par lui).

    Pourquoi tout ce petit laïus ? Tout simplement pour pouvoir discuter plus proprement du modèle de données. Au sein de l'application, y a-t-il besoin de savoir que la source initiale est un animal provenant d'un fichier HTML AMDB ou SCC ? Ou est-ce que cela n'a aucune importance et fera juste que dans certains cas, certains champs seront remplis et d'autres non ?

    Si cela n'a pas d'importance, alors je pense que l'héritage n'est pas utile issu au niveau de la classe ExternalAnimal. C'est un objet métier, et une fois qu'il est dans l'application, qu'importe sa provenance. Ce n'est pas parce qu'il y a 2 provenances différentes qu'il doit y avoir 2 formats différents. La gestion des formats se fait uniquement au niveau de l'importation.

    Au niveau de la configuration, et du chargement depuis un fichier ou depuis la BD, tout dépend de comment sont stockés les données en BD. Est-ce que c'est une forme sérialisé (CSV, XML, autre) qui est stockée en tant que BLOB, ou est-ce que c'est un animal stocké dans une table avec des colonnes pour le nom, le nombre de pattes, fourrure/écaille, herbivore/carnivore ou que sais-je encore ?

    Si c'est un BLOB, alors effectivement, qu'importe la source, que cela soit un fichier ou la BD, le type de données sera le même et le schéma actuel est bon. Si c'est différent, alors je changerais un peu les choses. AnimalLoader resterait toujours en haut de la hiérarchie, et ensuite :
    • Une classe AnimalStreamLoader pour charger depuis un stream (fichier, réseau, BLOB, autre)
    • Une classe AnimalTableLoader pour charger depuis une représentation tabulaire (une BD, mais aussi, pourquoi pas un CSV !)
    • La classe XmlAnimalLoader hériteraient de AnimalStreamLoader
    • La classe CsvAnimalLoader pourrait hériter soit de AnimalStreamLoader, soit de AnimalTableLoader, en fonction de l'approche souhaitée
    • La classe DbAnimalLoader hériterait de AnimalTableLoader
    • Il faudrait aussi séparer en deux la classe AnimalConfiguration en AnimalStreamConfiguration et AnimalTableConfiguration, ce qui permettrait d'éviter d'avoir, par l'organisation hiérarchique même des classes, un AnimalTableLoader avec un AnimalFileConfiguration par exemple
    • La classe AnimalFileConfiguration hériterait de AnimalStreamConfiguration
    • La classe AnimalDbConfiguration hériterait de AnimalTableConfiguration


    Avec ce découpage, je pense qu'il n'y aurait pas besoin de cast explicite.

    A noter qu'il est aussi possible de passer uniquement par des interfaces plutôt des que classes abstraites.

    Et pour le point 1), avec le découpage si dessus, le HtmlAnimalLoader pourrait prendre en entrée AnimalStreamReader rendant le cast inutile aussi.
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  4. #4
    Membre régulier
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    116
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Orne (Basse Normandie)

    Informations professionnelles :
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 116
    Points : 100
    Points
    100
    Par défaut
    bonjour,

    Merci pour cette longue réponse.
    Pour répondre aux différents points :
    - OSEF de savoir si ça vient d'ADMB ou de SCC. Effectivement, certains champs sont remplis, d'autres non. J'ai donc supprimé l'héritage de ExternalAnimal. Plus de cast à faire. Tout de suite, ça me rend la vie bien plus simple.
    - Le stockage dans la BDD est du SQL Serveur 2K19 afin d'utiliser les graph.
    - J'ai modifié aussi du coté du loader ce qui fait que le chargement de la config se fait bien avec un AnimalFileConfiguration et donc le cast est supprimé là aussi.

    Le code ressemble donc à ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    ExternalAnimal animal = new ExternalAnimal();
    AnimalFileConfiguration config = new AnimalFileConfiguration
    {
      Path = pathToScan,
      Filename = filename,
      Encoding = encoding,
    };
     
    HtmlAnimalLoader loader = new AmdbHtmlAnimalLoader();
    loader.Load(config, animal);
    Donc je dirais qu'on est bon maintenant, c'est propre Merci infiniment.

    Allez question bonus : si l'on veut traiter plusieurs fichiers.
    - A partir de Directory.GetDirectories() et Directory.GetFiles("*.csv"), je peux parcourir en récursif un dossier pour traiter tous les fichiers (XML, CSV, HTML)
    - Je pense modifier le prototype de l'interface IAnimalStreamReader.Load () et .Save() pour ne plus prendre un ExternalAnimal en param 2 mais plutôt en retour de fonction.
    - Au niveau de AnimalStreamLoader, j'implémente une fonction protected Load() et Save() qui prend en paramètre AnimalFileConfiguration ainsi qu'un filtre d'extension de fichier et qui retourne une Collection<ExternalAnimal>.
    - Dans les 3 classes dérivées HtmlAnimalLoader, CsvAnimalLoader et XmlAnimalLoader, on crée une méthode public Load(AnimalConfiguration) qui renvoie Collection<ExternalAnimal> dont le rôle est d'appeler AnimalStreamLoader(AnimalConfiguration, "*.csv").
    Je pense qu'il n'y a pas de code dupliqué (pour le scan récursif), on n'ajoute pas de complexité en créant d'autres interfaces.

    Voici le nouveau diagramme :
    Nom : Capture_diagram2.PNG
Affichages : 124
Taille : 85,1 Ko

    Merci

  5. #5
    Membre éprouvé
    Homme Profil pro
    Administrateur Systèmes, Clouds et Réseaux /CAO/DAO/Ingénierie Electrotechnique
    Inscrit en
    Décembre 2014
    Messages
    449
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Administrateur Systèmes, Clouds et Réseaux /CAO/DAO/Ingénierie Electrotechnique

    Informations forums :
    Inscription : Décembre 2014
    Messages : 449
    Points : 991
    Points
    991
    Par défaut
    salut, truc sans rapport avec quoi tu fais ta modélisation ? Ca me sauverait la mise pour me souvenir ou j'en suis quand je reviens sur un programme.

    Sinon j'ai récemment eu à faire quelque chose dans un genre assez proche du tien, un module de trad, un de config et le dernier c'est en fait de lire/écrire le xml d'une autre application qui repose sa "bdd" en fait sur du xml pur. Au final j'ai près de 8 classes avec des interfaces pour charger une partie si nécessaire ou pas, j'ai d'une part rendu mes fonctions écritures lectures génériques, avec plusieurs interfaces du coup. Certaines rajoutent des fonctions, c'était pas vital mais j'ai dédoublé en faisant des classes raw liées au programme duquel j'utilise les xml et à côté des classes à moi qui rajoute des fonctions et propriétés (ils ont des centaines de balises, et c'est pas évident de les suivre à chaque mise à jour). Bref... je me suis simplement rendu maitre de la sérialization pour lire et écrire (donc ta classe reste ta classe, et le module s'occupe de charger/sauver au format souhaité), au début j'avais fait d'ailleurs en xmldoc et puis je suis passé sur linq, qui une fois bien maitrisé donne un code un peu plus propre et compréhensible. J'ai abandonné d'ailleurs les méthodes de parse que j'avais fait c'était trop de boulot. Mais bref c'est faisable et c'est pas bien compliqué dès que tu gères par toi même cette étape qui va pas mal te libérer (si c'est pas encore fait) même si ça passe par écrire ton module écriture/lecture... au final ta classe à ses propriétés, l'extension aussi. Chaque fois que tu lis ou que tu écris tu délègues à un module qui va lire/écrire sa partie (sachant que du coup ça va permettre de créer des fonctions en plus de load/save, ou ce n'est même pas spécialement utile d'utiliser les classes), ce qui fait qu'en fait tu n'as pas à revenir modifier X manière de lire/écrire puisque c'est à chaque fois basé sur un tronc commun. Du coup avec ce système modulaire c'est franchement facile et je me retrouve sans problème dans mes méthodes, plutôt que de faire une lecture/écriture par classe. Après bien sûr encore une fois c'est relatif à toutes les données à entrer, si c'est juste un tp ça devrait être assez peu, si c'est un logiciel de taxi... mince j'ai oublié le nom, bref où il faut répertorier et classifier tous les animaux, il va y avoir des balises vides en pagaille, d'autres qui vont bouger etc etc... (comme pour mon cas) et là je pense que c'est préférable... disons que se trimballer avec la propriété rémiges pour un singe, ça peut être un peu useless . Par contre comme le dit François Dorin il est certain que savoir la provenance du fichier n'est pas fondamental au point de créer une classe en plus pour ça. Donc voilà tout va dépendre si tu dois spliter avec les reptiles, les mammifères.. si un ornithorynque fou croise ta route (t'es pas dans la mouise... enfin au moins ça ne vole pas ce qui est toujours ça de pris).

    Dans ton cas c'est clair que s'il n'y a que quelques variables/propriétés qui sont laissées vides ça sera probablement pas très grave en effet. donc si ça te simplifie la vie une seule classe, c'est le mieux. Moi ça me posait soucis car avec les classes monstres que ça me donnait, sachant que dès le départ je récupère toutes ls entrées je ne voulais pas me trimballer avec un mammouth alors que je vais traiter très souvent qu'une seule entrée. Mais bon voilà c'est faisable. Par contre en ce qui concerne ce qui va lire écrire, c'est forcément extérieur, enfin moi c'est comme ça que je vois les choses, comme ça tu peux à tout moment changer le module qui va lire/écrire sans que ça n'affecte la classe en question. C'est comme ça qu'à un moment du coup je suis passé du xml en json.

    Voilà si ça a pu te donner des idées pour plus tard...

  6. #6
    Membre régulier
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    116
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Orne (Basse Normandie)

    Informations professionnelles :
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 116
    Points : 100
    Points
    100
    Par défaut
    @daerlnaxe : le diagramme est fait avec le Class Diagram de Visual Studio 2019. J'utilise une version Pro mais elle est disponible sur la version Community !

    C'est un composant qui n'est pas forcément installé par défaut :
    Visual Studio Installer -> Modifier -> Composants individuels -> Rechercher 'classe' et cocher 'Concepteur de classes'.

    J'essaie de repenser mon code de façon un peu plus DesignPatterns. J'avais acheté le libre Tete la première - Design Patterns. Il a malheureusement brûlé en 2008 avec le reste de la maison. C'est un sujet livre très ludique. Et Je t'assure que l'application SuperCanard, je ne suis pas le seul à avoir pensé trop vite et tombé dans le panneau ^^.
    Il n'est plus imprimé à ce jour, en occaz' en français il existe à un tarif pas raisonnable. Mais heureusement, PDF et Google sont nos amis ^^.

    Effectivement, avec ta méthode de sérialisation, ça fonctionne aussi. Je me suis dit qu'avec le recul, augmenter mon nombre de classe risquait de me faire payer ce choix très cher ensuite lorsqu'il faudra faire le mapping avec EntityFramework.
    Et donc, plutôt que de faire une usine à gaz, je me dit que je ne devrais pas croiser d'ornithologue.

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

Discussions similaires

  1. Réponses: 4
    Dernier message: 24/02/2009, 12h06
  2. [VS.NET] Les design pattern et DOTNET
    Par Nycos62 dans le forum Visual Studio
    Réponses: 4
    Dernier message: 22/10/2004, 14h44
  3. [Observateur] Précisions sur le design pattern Observer [UML]
    Par joquetino dans le forum Design Patterns
    Réponses: 2
    Dernier message: 07/10/2004, 22h35
  4. Les Designs Patterns Entreprise
    Par boulon dans le forum Design Patterns
    Réponses: 4
    Dernier message: 01/09/2004, 19h16
  5. [Design Patterns] Architecture 3 tiers
    Par HPJ dans le forum Design Patterns
    Réponses: 1
    Dernier message: 29/07/2003, 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