Klassenduplikationsmuster?

11

Ich arbeite derzeit als Solo-Entwickler an meinem aktuellen Projekt. Ich habe das Projekt von einem anderen Entwickler geerbt, der das Unternehmen inzwischen verlassen hat. Es ist eine Webanwendung im Model-View-Controller-Stil in C #. Es verwendet Entity Framework für die objektrelationale Zuordnung. Und es gibt zwei verschiedene Klassen für die Typen im Domänenmodell. Ein Satz wird für die Interaktion mit dem ORM verwendet, der andere als Modell im MVC-System. Beispielsweise kann es zwei Klassen geben:

public class Order{
    int ID{get;set;}
    String Customer{get;set;}
    DateTime DeliveryDate{get;set;}
    String Description{get;set;}
}

und

public class OrderModel{
    String Customer{get;set;}
    DateTime DeliveryDate{get;set;}
    String Description{get;set;}
    public OrderModel( Order from){
        this.Customer= from.Customer;
        // copy all the properties over individually
    }
    public Order ToOrder(){
        Order result =new Order();
        result.Customer = this.Customer;
        // copy all the properties over individually
    }

}

Ich kann mir einige Nachteile dieses Ansatzes vorstellen (mehr Orte, an denen Code geändert werden kann, wenn sich etwas ändert, mehr Objekte im Speicher, mehr Zeit für das Kopieren von Daten), aber ich bin mir nicht sicher, welche Vorteile hier liegen. Mehr Flexibilität für die Modellklassen, nehme ich an? Aber ich könnte das erreichen, indem ich auch die Entitätsklassen unterklassifiziere. Meine Neigung wäre, diese beiden Klassengruppen zusammenzuführen oder möglicherweise die Modellklassen Unterklassen der Entitätsklassen zu sein. Vermisse ich hier etwas Wichtiges? Ist dies ein allgemeines Designmuster, das mir nicht bekannt ist? Gibt es gute Gründe, den Refactor, über den ich nachdenke, nicht durchzugehen?

AKTUALISIEREN

Einige der Antworten hier lassen mich erkennen, dass meiner anfänglichen Beschreibung des Projekts einige wichtige Details fehlten. Es gibt auch eine dritte Gruppe von Klassen im Projekt: die Seitenmodellklassen. Sie werden tatsächlich als Modell für die Seite verwendet. Sie enthalten auch Informationen, die für die Benutzeroberfläche spezifisch sind und nicht mit einer Bestellung in der Datenbank gespeichert werden. Eine beispielhafte Seitenmodellklasse könnte sein:

public class EditOrderPagelModel
{
    public OrderModel Order{get;set;}
    public DateTime EarliestDeliveryDate{get;set;}
    public DateTime LatestAllowedDeliveryDate{get;set;}
}

Ich sehe den Nutzen dieser dritten Gruppe hier völlig anders und habe nicht vor, sie mit etwas anderem zusammenzuführen (obwohl ich sie vielleicht umbenennen könnte).

Die Klassen in der Modellgruppe werden derzeit auch von der API der Anwendung verwendet. Ich würde mich auch für Eingaben interessieren, ob dies eine gute Idee ist.

Ich sollte auch erwähnen, dass der Kunde, der hier als Zeichenfolge dargestellt wird, das Beispiel vereinfachen sollte, nicht weil es tatsächlich so im System dargestellt wird. Das tatsächliche System hat den Kunden als einen bestimmten Typ im Domänenmodell mit seinen eigenen Eigenschaften

Robert Ricketts
quelle
1
"Ich habe das Projekt von einem anderen Entwickler geerbt, der das Unternehmen inzwischen verlassen hat." Es gibt eine Website , die Geschichten gewidmet ist, die auf diese Weise beginnen .
In Bezug auf Ihr Update: Das scheint zu viel Indirektion für zu wenig Nutzen zu sein.
Robert Harvey
@RobertHarvey Was meinst du als zu viel Indirektion?
Robert Ricketts
1
Das OrderModel. OrderViewModel (was Sie wahrscheinlich als EditOrderPageModel bezeichnen) ist eine ausreichende Indirektion. siehe meine Antwort unten.
Robert Harvey
@ RobertHarvey Dies klingt wie die Antwort auf die Frage, die ich eigentlich zu stellen versuchte
Robert Ricketts

Antworten:

16

Vermisse ich hier etwas Wichtiges?

JA

Während diese wie dasselbe aussehen und dasselbe in der Domäne darstellen, sind sie nicht dieselben (OOP) Objekte.

Einer ist der Orderdurch den Datenspeicher bekannte Teil des Codes. Die andere ist die Orderder Benutzeroberfläche bekannte. Es ist zwar ein angenehmer Zufall, dass diese Klassen die gleichen Eigenschaften haben, dies ist jedoch nicht garantiert .

Oder betrachten Sie das Prinzip der Einzelverantwortung, um es aus einer anderen Perspektive zu betrachten. Der Hauptmotivator hier ist, dass "eine Klasse einen Grund hat, sich zu ändern". Insbesondere beim Umgang mit allgegenwärtigen Frameworks wie einem ORM- und / oder UI-Framework müssen Ihre Objekte gut isoliert sein, da Änderungen am Framework häufig dazu führen, dass sich Ihre Entitäten ändern.

Indem Sie Ihre Entitäten an beide Frameworks binden , machen Sie es noch schlimmer.

Telastyn
quelle
7

Der Zweck des Ansichtsmodells besteht darin, die Entkopplung auf zwei Arten bereitzustellen: durch Bereitstellen von Daten in der für die Ansicht erforderlichen Form (unabhängig vom Modell) und (insbesondere in MVVM) durch Zurückschieben eines Teils oder der gesamten Ansichtslogik aus der Ansicht zum Ansichtsmodell.

In einem vollständig normalisierten Modell würde der Kunde im Modell nicht durch eine Zeichenfolge, sondern durch eine ID dargestellt. Wenn das Modell den Namen des Kunden benötigt, sucht es den Namen in der Tabelle "Kunden" unter Verwendung der Kunden-ID in der Tabelle "Bestellungen".

Ein Ansichtsmodell hingegen interessiert sich möglicherweise mehr für Nameden Kunden, nicht für die ID. Die ID ist nur erforderlich, wenn der Kunde im Modell aktualisiert wird.

Darüber hinaus kann und wird das Ansichtsmodell normalerweise eine andere Form annehmen (es sei denn, es handelt sich um reines CRUD ). Ein Rechnungsansichtsmodellobjekt verfügt möglicherweise über Kundennamen, Adressen und Werbebuchungen, das Modell enthält jedoch Kunden und Beschreibungen.

Mit anderen Worten, Rechnungen werden normalerweise nicht so gespeichert, wie sie angezeigt werden.

Robert Harvey
quelle
3

In gewisser Hinsicht haben Sie Recht: Beide beziehen sich auf dasselbe Domainobjekt, das aufgerufen wird order. Aber Sie liegen insofern falsch, es ist keine echte Vervielfältigung als eine andere Darstellung - die aus unterschiedlichen Zwecken stammt. Wenn Sie Ihre Bestellung beibehalten möchten, möchten Sie z. B. Zugriff auf jede einzelne Spalte. Aber das wollen Sie nicht nach außen lecken. Außerdem ist es nicht das, was Sie wirklich wollen , ganze Entitäten durch den Draht zu schieben . Ich kenne Fälle, in denen eine Sammlung von MBs ordersan den Kunden gesendet wurde, in denen ein paar KB ausreichen würden.

Um es kurz zu machen - hier sind die Hauptgründe, warum Sie das tun möchten:

1) Kapselung - Informationen, die Ihre Anwendung verbergen

2) Kleine Modelle sind schnell zu serialisieren und einfach zu übertragen

3) Sie dienen unterschiedlichen Zwecken, obwohl sie sich überlappen, und daher sollten unterschiedliche Objekte vorhanden sein.

4) Manchmal ist es sinnvoll, für verschiedene Abfragen unterschiedliche Wertobjekte zu haben . Ich weiß nicht, wie es in C # ist, aber in Java ist es möglich, ein POJO zu verwenden, um Ergebnisse zu sammeln. Wenn ich eine einzelne benötige, verwende orderich die Order -Entity. Wenn ich jedoch nur einige Spalten mit Datenmengen benötige, ist die Verwendung kleinerer Objekte effizienter.

Thomas Junk
quelle
Die Art und Weise, wie das Entity-Framework strukturiert ist, Entitäten sind einfache alte C # -Objekte, sodass das Senden über das Netzwerk kein wirkliches Problem darstellt. Ich sehe den Nutzen, unter bestimmten Umständen nur einen Teil des Inhalts einer Bestellung versenden zu können.
Robert Ricketts
Einer oder ein Haufen ist nicht das Problem, wie ich sagte. Es gibt jedoch Fälle, in denen Sie über MB Daten verfügen, was Ihre Ladezeit verlangsamt. Denken Sie an mobile Ladezeiten
Thomas Junk
0

(Haftungsausschluss: Ich habe nur gesehen, dass es auf diese Weise verwendet wird. Ich habe möglicherweise den eigentlichen Zweck falsch verstanden. Behandeln Sie diese Antwort mit Argwohn.)

Hier ist ein weiteres fehlendes Bit: die Konvertierung zwischen Orderund OrderModel.

Die OrderKlasse ist an Ihr ORM gebunden, während die OrderModelan das Design Ihres Ansichtsmodells gebunden ist.

In der Regel werden die beiden Methoden "auf magische Weise" bereitgestellt (manchmal als DI, IoC, MVVM oder noch etwas anderes bezeichnet). Das heißt, anstatt diese beiden Konvertierungsmethoden als Teil von zu implementieren OrderModel, gehören sie stattdessen zu einer Klasse, die sich der gegenseitigen Konvertierung dieser Dinge widmet. Diese Klasse wird irgendwie beim Framework registriert, so dass das Framework es leicht nachschlagen kann.

public class OrderModel {
    ...
    public OrderModel(Order from) { ... }
    public Order ToOrder() { ... }
}

Der Grund dafür ist, dass Sie bei jedem Übergang von der OrderModelnach Orderoder umgekehrt wissen, dass einige Daten aus ORM geladen oder in ORM gespeichert wurden.

rwong
quelle
Wenn ich beide behalten möchte, möchte ich sie definitiv so einrichten, dass die Konvertierung automatisierter wird
Robert Ricketts
@RobertRicketts Das, was ich gesehen habe, sieht so aus: IMvxValueConverter . Obwohl ich seinen Zweck vielleicht falsch verstanden habe.
Rwong