Bewährte Methoden für die Typzuordnung und Erweiterungsmethoden


15

Ich möchte einige Fragen zu bewährten Methoden in Bezug auf Zuordnungstypen und die Verwendung von Erweiterungsmethoden in C # stellen. Ich weiß, dass dieses Thema in den letzten Jahren mehrmals diskutiert wurde, aber ich habe viele Beiträge gelesen und habe immer noch Zweifel.

Das Problem, auf das ich gestoßen bin, war die Erweiterung der Klasse, die ich besitze, um die Konvertierungsfunktionalität. Angenommen, ich habe die Klasse "Person", die ein Objekt darstellt, das von einer Logik verwendet wird. Ich habe auch eine Klasse "Kunde", die eine Antwort von einer externen API darstellt (tatsächlich wird es mehr als eine API geben, daher muss ich die Antwort jeder API einem allgemeinen Typ zuordnen: Person). Ich habe Zugriff auf den Quellcode beider Klassen und kann dort theoretisch meine eigenen Methoden implementieren. Ich muss Customer in Person konvertieren, damit ich es in der Datenbank speichern kann. Das Projekt verwendet keine automatischen Mapper.

Ich habe 4 mögliche Lösungen im Auge:

  1. .ToPerson () -Methode in der Consumer-Klasse. Es ist einfach, aber es scheint mir, als würde das Muster der Einzelverantwortung durchbrochen, insbesondere, dass die Consumer-Klasse auch anderen Klassen zugeordnet ist (einige werden von einer anderen externen API benötigt), sodass sie mehrere Zuordnungsmethoden enthalten müsste.

  2. Zuordnungskonstruktor in der Personenklasse unter Verwendung von Consumer als Argument. Auch einfach und scheint auch Single-Responsibility-Muster zu brechen. Ich brauche mehrere Mapping-Konstruktoren (da es Klassen von einer anderen API gibt, die die gleichen Daten wie Consumer liefern, aber in einem etwas anderen Format)

  3. Converters-Klasse mit Erweiterungsmethoden. Auf diese Weise kann ich die .ToPerson () -Methode für die Consumer-Klasse schreiben. Wenn eine andere API mit einer eigenen NewConsumer-Klasse eingeführt wird, kann ich einfach eine andere Erweiterungsmethode schreiben und alles in derselben Datei belassen. Ich habe eine Meinung gehört, dass Erweiterungsmethoden im Allgemeinen böse sind und nur verwendet werden sollten, wenn dies unbedingt erforderlich ist, und das ist es, was mich zurückhält. Ansonsten mag ich diese Lösung

  4. Konverter / Mapper-Klasse. Ich erstelle eine separate Klasse, die Konvertierungen verarbeitet und Methoden implementiert, die die Quellklasseninstanz als Argument verwenden und die Zielklasseninstanz zurückgeben.

Zusammenfassend kann mein Problem auf eine Reihe von Fragen reduziert werden (alles im Zusammenhang mit dem, was ich oben beschrieben habe):

  1. Wird das Platzieren der Konvertierungsmethode in einem (POCO?) - Objekt (wie die .ToPerson () - Methode in der Consumer-Klasse) als Verstoß gegen das Muster einer einzelnen Verantwortung betrachtet?

  2. Wird die Verwendung von Konvertierungskonstruktoren in einer (DTO-ähnlichen) Klasse als Verstoß gegen das Muster einer einzelnen Verantwortung angesehen? Insbesondere wenn eine solche Klasse aus mehreren Quelltypen konvertiert werden kann, wären dann mehrere Konvertierungskonstruktoren erforderlich?

  3. Wird die Verwendung von Erweiterungsmethoden beim Zugriff auf den ursprünglichen Klassenquellcode als schlechte Praxis angesehen? Kann ein solches Verhalten als tragfähiges Muster für die Trennung von Logik verwendet werden oder ist es ein Anti-Muster?


Ist die PersonKlasse ein DTO? Enthält es irgendein Verhalten?
Yacoub Massad

Soweit ich weiß ist es technisch ein DTO. Es enthält einige als Erweiterungsmethoden "injizierte" Logik, diese Logik ist jedoch auf den Methodentyp "ConvertToThatClass" beschränkt. Dies sind alles traditionelle Methoden. Meine Aufgabe ist es, neue Funktionen zu implementieren, die einige dieser Klassen verwenden, aber mir wurde gesagt, dass ich mich nicht an den aktuellen Ansatz halten sollte, wenn es schlecht ist, nur um ihn konsistent zu halten. Ich frage mich also, ob dieser bestehende Ansatz beim Konvertieren mit Erweiterungsmethoden gut ist und ob ich dabei bleiben oder etwas anderes ausprobieren sollte
emsi

Antworten:


11

Wird das Platzieren der Konvertierungsmethode in einem (POCO?) - Objekt (wie die .ToPerson () - Methode in der Consumer-Klasse) als Verstoß gegen das Muster einer einzelnen Verantwortung betrachtet?

Ja, weil die Bekehrung eine andere Verantwortung ist.

Wird die Verwendung von Konvertierungskonstruktoren in einer (DTO-ähnlichen) Klasse als Verstoß gegen das Muster einer einzelnen Verantwortung angesehen? Insbesondere wenn eine solche Klasse aus mehreren Quelltypen konvertiert werden kann, wären dann mehrere Konvertierungskonstruktoren erforderlich?

Ja, die Konvertierung ist eine andere Verantwortung. Es macht keinen Unterschied, ob Sie dies über Konstruktoren oder über Konvertierungsmethoden (zB ToPerson) tun .

Wird die Verwendung von Erweiterungsmethoden beim Zugriff auf den ursprünglichen Klassenquellcode als schlechte Praxis angesehen?

Nicht unbedingt. Sie können Erweiterungsmethoden auch dann erstellen, wenn Sie den Quellcode der Klasse haben, die Sie erweitern möchten. Ich denke, ob Sie eine Erweiterungsmethode erstellen oder nicht, sollte durch die Art dieser Methode bestimmt werden. Enthält es zum Beispiel viel Logik? Kommt es auf etwas anderes an, als die Mitglieder des Objekts selbst? Ich würde sagen, dass Sie keine Erweiterungsmethode haben sollten, die Abhängigkeiten erfordert oder komplexe Logik enthält. In einer Erweiterungsmethode sollte nur die einfachste Logik enthalten sein.

Kann ein solches Verhalten als tragfähiges Muster für die Trennung von Logik verwendet werden oder ist es ein Anti-Muster?

Wenn die Logik komplex ist, sollten Sie meiner Meinung nach keine Erweiterungsmethode verwenden. Wie ich bereits erwähnt habe, sollten Sie Erweiterungsmethoden nur für die einfachsten Dinge verwenden. Ich würde die Konvertierung nicht einfach finden.

Ich schlage vor, dass Sie Konvertierungsdienste erstellen. Sie können eine einzige generische Schnittstelle dafür haben:

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

Und Sie können Konverter wie folgt haben:

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

Und Sie können Dependency Injection verwenden , um einen Konverter (z. B. IConverter<Person,Customer>) in jede Klasse zu injizieren , für die die Konvertierung zwischen Personund erforderlich ist Customer.


Wenn ich mich nicht irre, gibt es bereits ein IConverterFramework, das nur darauf wartet, implementiert zu werden.
RubberDuck

@RubberDuck, wo gibt es das?
Yacoub Massad

Ich habe darüber nachgedacht IConvertable, wonach wir hier nicht suchen. Mein Fehler.
RubberDuck

Ah ha! Ich fand, was ich von @YacoubMassad dachte. Converterwird Listbeim Aufruf von verwendet ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Ich weiß jedoch nicht, wie nützlich dies für OP ist.
RubberDuck

Auch relevant. Jemand anderes hat den Ansatz gewählt, den Sie hier vorschlagen. codereview.stackexchange.com/q/51889/41243
Rubberduck

5

Wird das Einfügen der Konvertierungsmethode in ein (POCO?) - Objekt (wie die .ToPerson()Methode in der Consumer-Klasse) als Verstoß gegen das Muster einer einzelnen Verantwortung betrachtet?

Ja. Eine ConsumerKlasse ist dafür verantwortlich, die Daten zu einem Verbraucher zu speichern (und möglicherweise einige Aktionen auszuführen), und sollte nicht dafür verantwortlich sein, sich in einen anderen, nicht verwandten Typ umzuwandeln .

Wird die Verwendung von Konvertierungskonstruktoren in einer (DTO-ähnlichen) Klasse als Verstoß gegen das Muster einer einzelnen Verantwortung angesehen? Insbesondere wenn eine solche Klasse aus mehreren Quelltypen konvertiert werden kann, wären dann mehrere Konvertierungskonstruktoren erforderlich?

Wahrscheinlich. Ich habe in der Regel Methoden außerhalb der Domäne und DTO-Objekte, um die Konvertierung durchzuführen. Ich verwende oft ein Repository, das Domänenobjekte aufnimmt und sie (de) serialisiert, entweder in eine Datenbank, einen Speicher, eine Datei oder was auch immer. Wenn ich diese Logik in meine Domänenklassen einbaue, habe ich sie jetzt an eine bestimmte Form der Serialisierung gebunden, die für Tests (und andere Dinge) schlecht ist. Wenn ich die Logik in meine DTO-Klassen einbaue, habe ich sie an meine Domäne gebunden, was wiederum das Testen einschränkt.

Wird die Verwendung von Erweiterungsmethoden beim Zugriff auf den ursprünglichen Klassenquellcode als schlechte Praxis angesehen?

Nicht im Allgemeinen, obwohl sie können überstrapaziert werden. Mit Erweiterungsmethoden erstellen Sie optionale Erweiterungen, die das Lesen von Code erleichtern. Sie haben Nachteile - es ist einfacher, Mehrdeutigkeiten zu erzeugen, die der Compiler auflösen muss (manchmal unbemerkt), und es kann schwieriger sein, Code zu debuggen, da nicht ganz klar ist, woher die Erweiterungsmethode stammt.

In Ihrem Fall scheint eine Konverter- oder Mapper-Klasse der einfachste und direkteste Ansatz zu sein, obwohl ich nicht glaube, dass Sie mit Erweiterungsmethoden etwas falsch machen .


2

Wie wäre es mit AutoMapper oder einem benutzerdefinierten Mapper?

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

von der Domain

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

Unter der Haube können Sie AutoMapper abstrahieren oder Ihren eigenen Mapper rollen


2

Ich weiß, dass dies alt ist, aber immer noch offen. Auf diese Weise können Sie in beide Richtungen konvertieren. Ich finde dies hilfreich, wenn ich mit Entity Framework arbeite und Viewmodels (DTOs) erstelle.

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

Ich finde, dass AutoMapper ein bisschen zu viel ist, um wirklich einfache Mapping-Operationen durchzuführen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.