Ist meine Verwendung eines expliziten Casting-Operators sinnvoll oder ein schlechter Hack?

24

Ich habe ein großes Objekt:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

und ein spezialisiertes, DTO-ähnliches Objekt:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

Ich persönlich finde ein Konzept zum expliziten Umwandeln von BigObject in SmallObject - in dem Wissen, dass es sich um eine Einwegoperation handelt, bei der Daten verloren gehen - sehr intuitiv und lesbar:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

Es wird mit dem expliziten Operator implementiert:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

Jetzt könnte ich eine SmallObjectFactoryKlasse mit einer FromBigObject(BigObject big)Methode erstellen , die dasselbe macht, sie zur Abhängigkeitsinjektion hinzufügt und sie bei Bedarf aufruft ... aber für mich scheint es noch komplizierter und unnötiger zu sein.

PS: Ich bin mir nicht sicher, ob dies relevant ist, aber es wird möglich sein OtherBigObject, das auch in eine SmallObjectandere Einstellung umzuwandeln EnumType.

Gerino
quelle
4
Warum kein Konstrukteur?
edc65
2
Oder eine statische Fabrikmethode?
Brian Gordon
Warum brauchen Sie eine Factory-Klasse oder eine Abhängigkeitsinjektion? Sie haben dort eine falsche Dichotomie gemacht.
user253751
1
@immibis - weil ich irgendwie nicht darüber nachgedacht habe, was @Telastyn vorgeschlagen hat: .ToSmallObject()Methode (oder GetSmallObject()). Ein kurzer Moment der Vernunft - Ich wusste, dass etwas mit meinem Denken nicht stimmt, also habe ich euch gefragt :)
Gerino
3
Dies klingt nach einem perfekten Anwendungsfall für eine ISmallObject-Schnittstelle, die nur von BigObject implementiert wird, um den Zugriff auf einen begrenzten Satz ihrer umfangreichen Daten / Verhaltensweisen zu ermöglichen. Vor allem in Kombination mit @ Telastyns Idee einer ToSmallObjectMethode.
Marjan Venema

Antworten:

0

Keine der anderen Antworten hat meiner bescheidenen Meinung nach Recht. In dieser Stackoverflow-Frage argumentiert die am höchsten bewertete Antwort, dass der Zuordnungscode nicht in die Domäne aufgenommen werden soll. Um Ihre Frage zu beantworten, nein - Ihre Verwendung des Cast-Operators ist nicht besonders gut. Ich würde empfehlen, einen Zuordnungsdienst zu erstellen, der zwischen Ihrem DTO und Ihrem Domänenobjekt liegt, oder Sie könnten dafür Automapper verwenden.

Esben Skov Pedersen
quelle
Das ist eine tolle Idee. Ich habe bereits Automapper installiert, so dass es ein Kinderspiel wird. Einziges Problem, das ich damit habe: Sollte es nicht eine Spur geben, dass BigObject und SmallObject irgendwie zusammenhängen?
Gerino
1
Nein, ich sehe keinen Vorteil darin, BigObject und SmallObject weiter zusammen zu koppeln, abgesehen vom Mapping-Service.
Esben Skov Pedersen
7
"Ja wirklich?" Ein Automapper ist Ihre Lösung für Designprobleme?
Telastyn
1
Das BigObject kann auf SmallObject abgebildet werden, sie sind im klassischen OOP-Sinne nicht wirklich miteinander verwandt, und der Code spiegelt dies wider (beide Objekte existieren in der Domäne, die Zuordnungsfähigkeit wird in der Zuordnungskonfiguration zusammen mit vielen anderen festgelegt). Es entfernt zweifelhaften Code (mein unglücklicher Operator-Override), lässt Modelle sauber (keine Methoden in ihnen), also scheint es eine Lösung zu sein.
Gerino
2
@EsbenSkovPedersen Bei dieser Lösung wird wie bei einem Bulldozer ein Loch für die Installation Ihres Postfachs gegraben. Zum Glück wollte das OP den Hof sowieso ausgraben, daher funktioniert in diesem Fall eine Planierraupe. Im Allgemeinen würde ich diese Lösung jedoch nicht empfehlen .
Neil
81

Es ist ... nicht großartig. Ich habe mit Code gearbeitet, der diesen cleveren Trick ausgeführt hat und zu Verwirrung geführt hat. Schließlich würden Sie erwarten, dass Sie das einfach BigObjectin eine SmallObjectVariable umwandeln können, wenn die Objekte so miteinander verknüpft sind, dass sie umgewandelt werden können. Es funktioniert jedoch nicht - Sie erhalten Compiler-Fehler, wenn Sie es versuchen, da sie, was das Typsystem betrifft, nichts miteinander zu tun haben. Es ist für den Gießer auch leicht unangenehm, neue Objekte herzustellen.

Ich würde stattdessen eine .ToSmallObject()Methode empfehlen . Es ist klarer darüber, was tatsächlich vor sich geht und wie ausführlich.

Telastyn
quelle
18
Doh ... ToSmallObject () scheint die naheliegendste Wahl zu sein. Manchmal am offensichtlichsten ist die schwer fassbare;)
Gerino
6
mildly distastefulist eine Untertreibung. Es ist bedauerlich, dass die Sprache zulässt, dass so etwas wie eine Typografie aussieht. Niemand würde vermuten, dass es sich um eine tatsächliche Objekttransformation handelt, wenn sie es nicht selbst geschrieben hätten. In einem Ein-Personen-Team, okay. Wenn Sie mit jemandem zusammenarbeiten, ist dies im besten Fall eine Zeitverschwendung, da Sie anhalten und herausfinden müssen, ob es sich wirklich um eine Besetzung handelt oder um eine dieser verrückten Transformationen.
Kent A.
3
@Telastyn Einverstanden, dass es nicht der ungeheuerlichste Codegeruch ist. Die versteckte Erstellung eines neuen Objekts aus einer Operation heraus, die die meisten Programmierer als Anweisung an den Compiler verstehen , dasselbe Objekt als einen anderen Typ zu behandeln , ist jedoch für jeden unfreundlich, der nach Ihnen mit Ihrem Code arbeiten muss. :)
Kent A.
4
+1 für .ToSmallObject(). Sie sollten Operatoren kaum überschreiben.
Ytoledano
6
@dorus - zumindest in .NET bedeutet dies, Getdass ein vorhandenes Objekt zurückgegeben wird. Wenn Sie die Operationen für das kleine Objekt nicht überschrieben haben, würden zwei GetAufrufe ungleiche Objekte zurückgeben, was zu Verwirrung / Fehlern / wtfs führen würde.
Telastyn
11

Ich kann zwar nachvollziehen, warum Sie eine benötigen, aber SmallObjectich würde das Problem anders angehen. Mein Ansatz für diese Art von Problem ist die Verwendung einer Fassade . Ihr einziger Zweck ist BigObjectes, bestimmte Mitglieder zusammenzufassen und nur zur Verfügung zu stellen. Auf diese Weise handelt es sich um eine neue Schnittstelle in derselben Instanz und nicht um eine Kopie. Natürlich möchten Sie vielleicht auch eine Kopie erstellen, aber ich würde Ihnen empfehlen, dies mithilfe einer Methode zu tun, die zu diesem Zweck (zum Beispiel return new SmallObject(instance.Clone())) in Kombination mit der Fassade erstellt wurde .

Facade bietet eine Reihe weiterer Vorteile: Es muss sichergestellt werden, dass bestimmte Abschnitte Ihres Programms nur die Mitglieder verwenden können, die über Ihre Fassade verfügbar sind, und dass das, was Sie nicht wissen sollten, nicht genutzt werden kann. Darüber hinaus bietet es den enormen Vorteil, dass Sie BigObjectbei zukünftigen Wartungsarbeiten flexibler vorgehen können, ohne sich über die Verwendung im gesamten Programm Gedanken machen zu müssen. Solange Sie das alte Verhalten in irgendeiner Form emulieren können, können Sie die SmallObjectArbeit so machen, wie sie vorher war, ohne dass Sie Ihr Programm überall ändern BigObjectmüssten.

Beachten Sie, dass BigObjectdies nicht davon abhängt, SmallObjectsondern umgekehrt (wie es meiner bescheidenen Meinung nach sein sollte).

Neil
quelle
Der einzige Vorteil, den Sie erwähnt haben, dass eine Fassade gegenüber dem Kopieren von Feldern in eine neue Klasse besteht, ist das Vermeiden des Kopierens (was wahrscheinlich kein Problem ist, es sei denn, die Objekte haben eine absurde Menge von Feldern). Andererseits hat es den Nachteil, dass Sie die ursprüngliche Klasse jedes Mal ändern müssen, wenn Sie in eine neue Klasse konvertieren müssen, im Gegensatz zu einer statischen Konvertierungsmethode.
Doval
@Doval Ich denke, das ist der Punkt. Sie würden es nicht in eine neue Klasse konvertieren. Sie würden eine weitere Fassade erstellen, wenn Sie dies wünschen. Änderungen an BigObject müssen nur auf die Facade-Klasse angewendet werden und nicht überall, wo sie verwendet werden.
Neil,
Ein interessanter Unterschied zwischen diesem Ansatz und Telastyns Antwort ist, ob die Verantwortung für die Generierung SmallObjectbei SmallObjectoder liegt BigObject. Dieser Ansatz erzwingt standardmäßig SmallObjectdie Vermeidung von Abhängigkeiten von privaten / geschützten Mitgliedern von BigObject. Wir können einen Schritt weiter gehen und Abhängigkeiten von privaten / geschützten Mitgliedern vermeiden, SmallObjectindem wir eine ToSmallObjectErweiterungsmethode verwenden.
Brian
@Brian Du riskierst das Durcheinander BigObject. Wenn Sie etwas Ähnliches tun wollten, würden Sie dafür bürgen, eine ToAnotherObjectErweiterungsmethode innerhalb von zu erstellen BigObject? Dies sollte nicht die Sorge sein, BigObjectda es vermutlich bereits groß genug ist, wie es ist. Sie können sich auch BigObjectvon der Erstellung der Abhängigkeiten trennen , dh Sie können Fabriken und dergleichen verwenden. Der andere Ansatz ist stark paart BigObjectund SmallObject. Das mag in diesem speziellen Fall in Ordnung sein, aber meiner bescheidenen Meinung nach ist es keine bewährte Vorgehensweise.
Neil,
1
@Neil Eigentlich hat Brian es falsch erklärt, aber er hat Recht - Erweiterungsmethoden beseitigen die Kopplung. Es ist keine BigObjectKopplung mehr SmallObject, es ist nur eine statische Methode, die ein Argument von annimmt BigObjectund zurückgibt SmallObject. Erweiterungsmethoden sind eigentlich nur syntaktischer Zucker, um statische Methoden besser aufzurufen. Die Erweiterungsmethode ist nicht Teil von BigObject, sondern eine völlig separate statische Methode. Es ist eigentlich eine ziemlich gute Anwendung von Erweiterungsmethoden und insbesondere für DTO-Konvertierungen sehr praktisch.
Luaan
6

Es gibt eine sehr starke Konvention, die besagt, dass veränderbare Referenztypen identitätserhaltend sind. Da das System in Situationen, in denen ein Objekt des Quelltyps einer Referenz des Zieltyps zugewiesen werden könnte, im Allgemeinen keine benutzerdefinierten Umwandlungsoperationen zulässt, gibt es nur wenige Fälle, in denen benutzerdefinierte Umwandlungsoperationen für eine veränderbare Referenz sinnvoll wären Typen.

Ich würde als Voraussetzung vorschlagen, dass, wenn beide Darstellungen auf dasselbe Objekt angewendet werden, sie einige Zeit x=(SomeType)foo;später gegeben werden y=(SomeType)foo;, sie x.Equals(y)immer und für immer zutreffen sollten, auch wenn das betreffende Objekt zwischen den beiden Darstellungen geändert wurde. Eine solche Situation könnte eintreten, wenn z. B. eines ein Paar von Objekten unterschiedlichen Typs hätte, von denen jedes einen unveränderlichen Verweis auf das andere enthielt, und das Umsetzen eines Objekts auf den anderen Typ seine gepaarte Instanz zurückgeben würde. Dies kann auch für Typen gelten, die als Wrapper für veränderbare Objekte dienen, vorausgesetzt, die Identitäten der zu umhüllenden Objekte sind unveränderlich, und zwei Wrapper desselben Typs melden sich als gleich, wenn sie dieselbe Auflistung umhüllen.

Ihr spezielles Beispiel verwendet veränderbare Klassen, behält jedoch keine Form der Identität bei. Als solches würde ich vorschlagen, dass es keine angemessene Verwendung eines Casting-Operators ist.

Superkatze
quelle
1

Es könnte in Ordnung sein.

Ein Problem mit Ihrem Beispiel ist, dass Sie solche beispielhaften Namen verwenden. Erwägen:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

Wenn Sie eine gute Vorstellung davon haben, was ein Long und Int bedeutet, sind sowohl die implizite Umwandlung von intin longals auch die explizite Umwandlung von longin intdurchaus verständlich. Es ist auch verständlich , wie 3wird 3und ist nur ein weiterer Weg zur Arbeit mit 3. Es ist verständlich, wie dies int.MaxValue + 1in einem überprüften Kontext fehlschlagen wird . Sogar wie es int.MaxValue + 1in einem ungeprüften Kontext funktionieren wird, int.MinValueist nicht die schwierigste Sache, die es gibt.

Wenn Sie implizit in einen Basistyp oder explizit in einen abgeleiteten Typ umwandeln, ist dies für jeden verständlich, der weiß, wie die Vererbung funktioniert, was geschieht und was das Ergebnis sein wird (oder wie es fehlschlagen könnte).

Bei BigObject und SmallObject habe ich keine Ahnung, wie diese Beziehung funktioniert. Wenn Ihre realen Typen so beschaffen sind, dass die Casting-Beziehung offensichtlich ist, dann ist Casting in der Tat eine gute Idee, auch wenn dies häufig der Fall ist normales vererbungsbasiertes Casting wird ausreichen.

Jon Hanna
quelle
Tatsächlich sind sie nicht viel mehr als das, was in Frage gestellt wird - aber sie können zum Beispiel BigObjecteine beschreiben Employee {Name, Vacation Days, Bank details, Access to different building floors etc.}und SmallObjecteine sein MoneyTransferRecepient {Name, Bank details}. Es gibt eine einfache Übersetzung von Employeenach MoneyTransferRecepient, und es gibt keinen Grund, mehr Daten als erforderlich an die Bankenanwendung zu senden.
Gerino