web-dev-qa-db-fra.com

Meilleures pratiques concernant le mappage de types et les méthodes d'extension

Je souhaite poser quelques questions sur les meilleures pratiques concernant les types de mappage et l'utilisation des méthodes d'extension en C #. Je sais que ce sujet a été discuté plusieurs fois au cours des dernières années, mais j'ai lu beaucoup de messages et j'ai encore des doutes.

Le problème que j'ai rencontré était d'étendre la classe que je possède avec la fonctionnalité "convertir". Disons que j'ai la classe "Person" qui représente un objet qui sera utilisé par une logique. J'ai également une classe "Client" qui représente une réponse de l'API externe (en fait, il y aura plus d'une API, j'ai donc besoin de mapper la réponse de chaque API au type commun: Personne). J'ai accès au code source des deux classes et je peux théoriquement y implémenter mes propres méthodes. J'ai besoin de convertir le client en personne pour pouvoir l'enregistrer dans la base de données. Le projet n'utilise aucun mappeur automatique.

J'ai 4 solutions possibles en tête:

  1. Méthode .ToPerson () dans la classe Consumer. C'est simple, mais cela me semble rompre le modèle de responsabilité unique, en particulier que la classe Consumer est mappée à d'autres classes (certaines requises par une autre API externe), elle devrait donc contenir plusieurs méthodes de mappage.

  2. Constructeur de mappage dans la classe Person prenant Consumer comme argument. Aussi facile et semble aussi briser le modèle de responsabilité unique. J'aurais besoin de plusieurs constructeurs de mappage (car il y aura une classe d'une autre API, fournissant les mêmes données que Consumer mais dans un format légèrement différent)

  3. Classe de convertisseurs avec méthodes d'extension. De cette façon, je peux écrire la méthode .ToPerson () pour la classe Consumer et lorsqu'une autre API est introduite avec sa propre classe NewConsumer, je peux simplement écrire une autre méthode d'extension et la conserver dans le même fichier. J'ai entendu dire que les méthodes d'extension sont mauvaises en général et ne devraient être utilisées qu'en cas de nécessité absolue, c'est ce qui me retient. Sinon j'aime cette solution

  4. Classe Converter/Mapper. Je crée une classe distincte qui gérera les conversions et implémentera des méthodes qui prendront l'instance de classe source comme argument et retourneront l'instance de classe de destination.

Pour résumer, mon problème peut être réduit à un certain nombre de questions (toutes dans le contexte de ce que j'ai décrit ci-dessus):

  1. Mettre la méthode de conversion à l'intérieur d'un objet (POCO?) (Comme la méthode .ToPerson () dans la classe Consumer) est-il considéré comme une rupture du modèle de responsabilité unique?

  2. L'utilisation de constructeurs de conversion dans une classe (de type DTO) est-elle considérée comme une rupture du modèle de responsabilité unique? Surtout si une telle classe peut être convertie à partir de plusieurs types de source, donc plusieurs constructeurs de conversion seraient nécessaires?

  3. L'utilisation de méthodes d'extension tout en ayant accès au code source de la classe d'origine est-elle considérée comme une mauvaise pratique? Un tel comportement peut-il être utilisé comme modèle viable pour séparer la logique ou est-ce un anti-modèle?

15
emsi

Mettre la méthode de conversion à l'intérieur d'un objet (POCO?) (Comme la méthode .ToPerson () dans la classe Consumer) est-il considéré comme une rupture du modèle de responsabilité unique?

Oui, car la conversion est une autre responsabilité.

L'utilisation de constructeurs de conversion dans une classe (de type DTO) est-elle considérée comme une rupture du modèle de responsabilité unique? Surtout si une telle classe peut être convertie à partir de plusieurs types de source, donc plusieurs constructeurs de conversion seraient nécessaires?

Oui, la conversion est une autre responsabilité. Cela ne fait aucune différence si vous le faites via des constructeurs ou via des méthodes de conversion (par exemple ToPerson).

L'utilisation de méthodes d'extension tout en ayant accès au code source de la classe d'origine est-elle considérée comme une mauvaise pratique?

Pas nécessairement. Vous pouvez créer des méthodes d'extension même si vous disposez du code source de la classe que vous souhaitez étendre. Je pense que si vous créez une méthode d'extension ou non doit être déterminé par la nature de cette méthode. Par exemple, contient-il beaucoup de logique? Cela dépend-il d'autre chose que des membres de l'objet lui-même? Je dirais que vous ne devriez pas avoir une méthode d'extension qui nécessite des dépendances pour fonctionner ou qui contient une logique complexe. Seule la plus simple des logiques doit être contenue dans une méthode d'extension.

Un tel comportement peut-il être utilisé comme modèle viable pour séparer la logique ou est-ce un anti-modèle?

Si la logique est complexe, je pense que vous ne devriez pas utiliser une méthode d'extension. Comme je l'ai noté précédemment, vous ne devez utiliser les méthodes d'extension que pour les choses les plus simples. Je ne considérerais pas la conversion comme simple.

Je vous suggère de créer des services de conversion. Vous pouvez avoir une seule interface générique pour cela comme ceci:

public interface IConverter<TSource,TDestination>
{
    TDestination Convert(TSource source_object);
}

Et vous pouvez avoir des convertisseurs comme celui-ci:

public class PersonToCustomerConverter : IConverter<Person,Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

Et vous pouvez utiliser Injection de dépendances pour injecter un convertisseur (par exemple IConverter<Person,Customer>) vers n'importe quelle classe nécessitant la possibilité de convertir entre Person et Customer.

11
Yacoub Massad

Est-ce que placer la méthode de conversion à l'intérieur d'un objet (POCO?) (Comme la méthode .ToPerson() dans la classe Consumer) est considéré comme une rupture du modèle de responsabilité unique?

Oui. Une classe Consumer est responsable de la conservation des données relatives à un consommateur (et éventuellement de l'exécution de certaines actions) et ne devrait pas être responsable de la conversion de elle-même en un autre type sans rapport.

L'utilisation de constructeurs de conversion dans une classe (de type DTO) est-elle considérée comme une rupture du modèle de responsabilité unique? Surtout si une telle classe peut être convertie à partir de plusieurs types de source, donc plusieurs constructeurs de conversion seraient nécessaires?

Probablement. J'ai généralement des méthodes en dehors du domaine et des objets DTO pour effectuer la conversion. J'utilise souvent un référentiel qui accepte les objets du domaine et les (dé) sérialise, soit dans une base de données, une mémoire, un fichier, etc. Si je mets cette logique dans mes classes de domaine, je les ai maintenant liées à une forme particulière de sérialisation, ce qui est mauvais pour les tests (et d'autres choses). Si je mets la logique dans mes classes DTO, je les ai liées à mon domaine, limitant à nouveau les tests.

L'utilisation de méthodes d'extension tout en ayant accès au code source de la classe d'origine est-elle considérée comme une mauvaise pratique?

Pas en général, bien qu'ils peuvent être surutilisés. Avec les méthodes d'extension, vous créez des extensions facultatives qui facilitent la lecture du code. Ils ont des inconvénients - il est plus facile de créer des ambiguïtés que le compilateur doit résoudre (parfois silencieusement) et peut rendre le code plus difficile à déboguer car il n'est pas tout à fait évident d'où vient la méthode d'extension.

Dans votre cas, une classe de convertisseur ou de mappeur semble être l'approche la plus simple et la plus directe, bien que je ne pense pas que vous fassiez quoi que ce soit mauvais en utilisant des méthodes d'extension.

5
D Stanley

Que diriez-vous d'utiliser AutoMapper ou un mappeur personnalisé pourrait être utilisé

MyMapper
   .CreateMap<Person>()
   .To<PersonViewModel>()
   .Map(p => p.Name, vm => vm.FirstName)
   .To<SomeDTO>()
   .Map(...);

du domaine

 db.Persons
   .ToListAsync()
   .Map<PersonViewModel>();

Sous le capot, vous pouvez résumer AutoMapper ou lancer votre propre mappeur

2
Anders

Je sais que c'est vieux, mais c'est encore révélateur. Cela vous permettra de convertir les deux façons. Je trouve cela utile lorsque je travaille avec Entity Framework et que je crée des modèles de vue (DTO).

public interface IConverter<TSource, TDestination>
{
    TDestination Convert(TSource source_object);
    TSource Convert(TDestination source_object);
}

public class PersonCustomerConverter : IConverter<Person, Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
    public Person Convert(Customer source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

Je trouve que AutoMapper est un peu suffisant pour effectuer des opérations de mappage vraiment simples.

2
KC.