it-swarm.com.de

Der beste Ort, um ein Objekt in ein anderes Objekt zu konvertieren

Ich schreibe eine Schnittstelle, um zwei zugrunde liegende APIs in einer neuen zu bündeln. Die APIs liefern Daten zu archivierten Rechnungen.

Alle drei APIs (die beiden alten und meine neue) haben unterschiedliche Datenstrukturen.
Beispielsweise: <INVOICE_NO> (altes XML), "number" (alter JSON), "formatted_number" (neuer JSON) bedeuten alle dasselbe Feld.

Ich habe für jede API eine Klasse erstellt und Spring's RestTemplate das Parsen/Formatieren der Antworten überlassen.

Nachdem ich nun ein ClassA Objekt von der ersten API und ein ClassB Objekt von der zweiten API erhalten habe, muss ich sie in ClassC Objekte konvertieren und in ihrem Format zurückgeben.

Mein erster Ansatz bestand darin, zwei Konstruktoren für ClassC zu erstellen, die entweder ein Objekt vom Typ ClassA oder ein Objekt vom Typ ClassB als Argument verwenden.

Aber jetzt bin ich mir nicht sicher, ob das das Richtige ist, weil ich zwei Datenübertragungsobjekte kopple.

Wäre es besser, eine InvoiceConverter Klasse oder sogar etwas anderes zu erstellen?

3
das Keks

Dies scheint ein perfektes Beispiel für die Verwendung von Adapter Pattern zu sein

Grundsätzlich würden Sie eine Schnittstelle erstellen, die eine Methode hätte:

public interface Adapter
{
   ClassC ConvertObject();
}

Und dann würden Sie zwei Klassen erstellen und diese Schnittstelle wie folgt implementieren:

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
   }
}

Auf diese Weise entkoppeln Sie die Logik der Typkonvertierung in verschiedene Klassen und überlassen der Benutzerklasse dieselbe Schnittstelle.

Jetzt kann es zu Problemen beim unnötigen Erstellen einer Schnittstelle kommen, wenn nur ClassC den Konstruktor von Adapterklassen aufruft. Die Schnittstelle ist nicht nur da, weil sie den Konstruktor aufruft. Zum Beispiel ist es einfacher, Komponententests zu schreiben, wenn Sie so etwas haben.

Als Nächstes möchten Sie möglicherweise die Klassen vollständig entkoppeln und den Aufruf des Konstruktors in eine Factory-Klasse verschieben, in der ClassC nur die Adapter-Schnittstelle sieht und dann in keiner Weise von ClassA oder ClassB abhängt. Natürlich flirtet dies mit der Verletzung von KISS-Prinzip , aber ich würde es einen Urteilsruf nennen.

Fazit: Wenn Sie ClassC von ClassA und ClassB entkoppeln möchten, muss es etwas geben, das Adapterklassen bindet. Dies kann eine abstrakte übergeordnete Klasse oder eine reguläre übergeordnete Klasse oder eine Schnittstelle sein. Da die übergeordnete Entität keine Informationen enthält, ist es logisch, die Schnittstelle zu verwenden.

3
Vladimir Stokic

Aber jetzt bin ich mir nicht sicher, ob das eine nette Sache ist, weil ich zwei Datenübertragungsobjekte kopple.

Ja, das sieht nach einem Codegeruch aus.

Wäre es besser, eine InvoiceConverter-Klasse oder etwas anderes zu erstellen?

Wahrscheinlich ja. Wenn ich Sie richtig verstanden habe, haben Sie einen Datenfluss wie

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

und Converter ist die einzige Stelle in Ihrem Code, die direkt von den APIs A und B abhängig ist, während C-Objekte sowie der Code, der C-Objekte verwendet, nicht mehr von A oder B abhängen (was wahrscheinlich Ihre ist Ziel hier, die verschiedenen Teile Ihres Systems zu entkoppeln).

Wenn Sie jedoch C-Objekte direkt von A und B abhängig machen und versuchen, eine separate Konverterklasse zu vermeiden, hängt der gesamte Code, der C-Objekte verwendet, weiterhin von A und B ab, was Sie wahrscheinlich vermeiden möchten. Überlegen Sie, was dies bedeutet, wenn Sie den Code mit C in einer separaten Bibliothek (in einer kompilierten Sprache) platzieren möchten. Im zweiten Fall muss diese Bibliothek mit den APIs von A und B verknüpft werden, während im ersten Fall die Bibliothek keine Verknüpfung mit A oder B benötigt.

3
Doc Brown

Sie haben Recht, Sie brauchen einen Konverter. Und natürlich möchten Sie nicht für jede Konvertierung eine bestimmte Schnittstelle erstellen, dafür sind Generika gedacht. Ich bin mir der Syntax in Java nicht sicher, aber ich bin mir ziemlich sicher, dass dies wie in C # möglich ist:

Zuerst erstelle ich eine Schnittstelle für die Konvertierung von einem Typ in einen anderen:

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

Sie möchten dies jedoch nicht für jede Konvertierung referenzieren, die Sie durchführen möchten. Aus diesem Grund erstellen wir eine weitere Schnittstelle wie die folgende:

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

       IObjectConverterFactory Factory;
}

Beachten Sie, dass wir hier eine andere Schnittstelle haben, die IObjectConverterFactory. Sie werden dies bei der Implementierung von IConverter.Convert verwenden, um das IObjectConverter-Objekt abzurufen. Die Fabrik ist wie folgt:

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

Wir haben also 3 Schnittstellen, was sehr viel zu sein scheint, aber die Idee hier ist, die Verwendung zu erleichtern (und die Kopplung nicht zu erhöhen). Sie müssen nur auf IConverter verweisen und der Convert-Methode den Quelltyp und den Ergebnistyp mitteilen.

Sie erklären nur:

IConverter _converter;

Und benutze es so:

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

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

Von dort aus müssen Sie sich nur noch um die spezifischen Implementierungen der Konverter kümmern.