web-dev-qa-db-fra.com

Meilleur endroit pour convertir un objet en un autre objet

J'écris une interface pour regrouper deux API sous-jacentes en une nouvelle. Les API fournissent des données sur les factures archivées.

Les trois API (les deux anciennes et la nouvelle) ont des structures de données différentes.
Par exemple: <INVOICE_NO> (ancien XML), "number" (ancien JSON), "formatted_number" (nouveau JSON) signifie tous le même champ.

J'ai créé une classe pour chaque API et j'ai laissé RestTemplate de Spring gérer l'analyse/le formatage des réponses.

Maintenant, après avoir reçu un objet ClassA de la première API et un objet ClassB de la deuxième API, je dois les convertir en objets ClassC et les renvoyer dans son format.

Ma première approche a été de créer deux constructeurs pour ClassC qui prennent soit un objet de type ClassA soit un objet de type ClassB comme argument.

Mais maintenant, je ne sais pas si c'est la bonne chose à faire, car je couple deux objets de transfert de données.

Serait-il préférable de créer une classe InvoiceConverter ou même autre chose?

3
das Keks

Cela semble être un très bon exemple d'utilisation de Pattern d'adaptateur

Fondamentalement, vous créeriez une interface qui aurait une méthode:

public interface Adapter
{
   ClassC ConvertObject();
}

Et puis vous créez deux classes, implémentant cette interface, comme ceci:

class AdapterClass : Adapter
{
   private ClassA adaptee;
   public AdapterClassA(ClassA pAdaptee)
   {
        adaptee = pAdaptee;
   }

   public ClassC ConvertObject()
   {
      //Code that converts ClassA to ClassC
   }
}

class AdapterClassB : Adapter
{
   private ClassB adaptee;
   public AdapterClassB(ClassB pAdaptee)
   {
        adaptee = pAdaptee;
   }

   public ClassC ConvertObject()
   {
      //Code that converts ClassB to ClassC
   }
}

De cette façon, vous découplez la logique de conversion de type en différentes classes, laissant la même interface à la classe utilisateur.

Maintenant, on peut soulever un problème avec la création inutile d'une interface lorsque personne sauf ClassC n'appellera le constructeur des classes d'adaptateurs. L'interface n'est pas là simplement à cause de qui invoquera le constructeur. Par exemple, il sera plus facile d'écrire des tests unitaires si vous avez quelque chose comme ça.

Ensuite, vous souhaiterez peut-être découpler complètement les classes et déplacer l'invocation du constructeur vers une classe d'usine, où ClassC ne verrait que l'interface de l'adaptateur et ne dépendrait en aucune façon de ClassA ou ClassB. Bien sûr, cela flirte avec la violation de principe KISS , mais je l'appellerais un jugement.

En résumé, si vous souhaitez dissocier ClassC de ClassA et ClassB, il doit y avoir quelque chose qui lie les classes d'adaptateur. Il peut s'agir d'une classe parent abstraite, ou d'une classe parent régulière ou d'une interface. Étant donné que l'entité parent ne porte aucune information, il est logique d'utiliser l'interface.

3
Vladimir Stokic

Mais maintenant, je ne sais pas si c'est une bonne chose à faire, car je couple deux objets de transfert de données.

Oui, cela ressemble à une odeur de code.

Serait-il préférable de créer une classe InvoiceConverter ou même autre chose?

Probablement oui. Si je vous ai bien compris, vous aurez alors un flux de données comme

     [class A or B object] -> [Converter] -> [C object] -> [code using C]

et Converter est le seul endroit dans votre code qui dépend directement des API A et B, tandis que les objets C ainsi que le code utilisant les objets C ne dépendront plus de A ou B (ce qui est probablement votre objectif ici, pour découpler les différentes parties de votre système).

Cependant, si vous rendez les objets C directement dépendants de A et B et essayez d'éviter une classe de convertisseur distincte, tout le code utilisant des objets C dépendra toujours de A et B, ce qui est probablement quelque chose que vous voulez éviter. Réfléchissez à ce que cela signifie lorsque vous souhaitez placer le code à l'aide de C dans une bibliothèque distincte (à l'aide d'un langage compilé). Dans le second cas, cette bibliothèque doit être liée aux API de A et B, tandis que dans le premier cas, la lib n'a pas besoin de liaison avec A ou B.

3
Doc Brown

Vous avez raison, vous avez besoin d'un convertisseur. Et bien sûr, vous ne voulez pas créer une interface spécifique pour chaque conversion, c'est à cela que servent les génériques. Je ne suis pas sûr de la syntaxe en Java, mais je suis sûr que c'est faisable comme en C #:

Je vais d'abord créer une interface pour gérer les conversions d'un type à un autre:

public interface IObjectConverter<TSource, TResult>{
       TResult Convert(TSource source);
}

Mais vous ne voulez pas faire référence à cela pour chaque conversion que vous souhaitez effectuer, c'est pourquoi nous allons créer une autre interface, comme ceci:

public interface IConverter(){
       TResult Convert<TSource,TResult>(TSource source);

       IObjectConverterFactory Factory;
}

Notez que nous avons ici une autre interface, IObjectConverterFactory. Vous l'utiliserez sur l'implémentation de IConverter.Convert pour obtenir l'objet IObjectConverter. L'usine est comme ça:

public interface IConverterFactory
{
    IObjectConverter<TSource, TResult> Create<TSource, TResult>();
}

Nous avons donc 3 interfaces, ce qui peut sembler beaucoup, mais l'idée ici est de faciliter l'utilisation (et non d'augmenter le couplage). Vous aurez seulement besoin de référencer IConverter et d'indiquer à la méthode Convert le type de source et le type de résultat.

Vous déclarerez uniquement:

IConverter _converter;

Et utilisez-le comme ceci:

var classC1 = _converter.Convert<ClassA,ClassC>(classA);

var classC2 = _converter.Convert<ClassB,ClassC>(classB);

De là, vous n'avez qu'à vous soucier des implémentations spécifiques des convertisseurs.