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


15

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 afin de 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 sources, 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?


La Personclasse est-elle un DTO? contient-il un comportement?
Yacoub Massad du

Pour autant que je sache, il s'agit techniquement d'un DTO. Il contient une logique "injectée" comme méthodes d'extension, mais cette logique est limitée aux méthodes de type "ConvertToThatClass". Ce sont toutes des méthodes héritées. Mon travail consiste à implémenter de nouvelles fonctionnalités qui utilisent certaines de ces classes, mais on m'a dit que je ne devrais pas m'en tenir à l'approche actuelle si elle est mauvaise juste pour la garder cohérente. Je me demande donc si cette approche existante avec la conversion à l'aide de méthodes d'extension est bonne et si je dois m'en tenir à cela, ou essayer autre chose
emsi

Réponses:


11

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 sources, 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 devrait ê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 l' injection de dépendances pour injecter un convertisseur (par exemple IConverter<Person,Customer>) dans n'importe quelle classe qui nécessite la capacité de convertir entre Personet Customer.


Si je ne me trompe pas, il y IConverteren a déjà un dans le cadre, qui ne demande qu'à être implémenté.
RubberDuck

@RubberDuck, où existe-t-il?
Yacoub Massad

Je pensais à IConvertablece qui n'est pas ce que nous recherchons ici. Mon erreur.
RubberDuck

Ah ha! J'ai trouvé ce que je pensais de @YacoubMassad. Converterest utilisé par Listlors de l'appel ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Je ne sais cependant pas à quel point cela est utile pour OP.
RubberDuck

Également pertinent. Quelqu'un d'autre a adopté l'approche que vous proposez ici. codereview.stackexchange.com/q/51889/41243
RubberDuck

5

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

Oui. Une Consumerclasse est responsable de la tenue des données relatives à un consommateur (et peut - être effectuer certaines actions) et ne devrait pas être responsable de la conversion elle - même dans 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 sources, 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 puissent ê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 entièrement é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 quelque chose de mal en utilisant des méthodes d'extension.


2

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

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.