Ist es schlecht, Klassen zu erstellen, deren einziger Zweck darin besteht, implizit in eine andere Klasse konvertiert zu werden?

10

Stellen Sie sich eine Situation vor, in der wir eine Bibliothek verwenden, mit der Sie CircleObjekte erstellen können, in der Sie den Radius und den Mittelpunkt des Kreises angeben können, um ihn zu definieren. Aus irgendeinem Grund wird jedoch auch ein erforderlicher flavourParameter benötigt . Nehmen wir jetzt an, ich muss es wirklich Circlein meiner eigenen App verwenden, aber für die Zwecke meiner App kann ich den Geschmack Flavours.Cardboardjedes Mal so einstellen.

Um dies zu "lösen", erstelle ich meine eigene CircleKlasse in einem anderen Namespace, der nur radiusund centerals Parameter verwendet, aber einen impliziten Konverter zur CircleKlasse der externen Bibliothek hat , der nur ein Circle(this.radius, this.center, Flavours.Cardboard)Objekt erstellt. Überall, wo ich den anderen Typ brauche Circle, lasse ich die automatische Konvertierung stattfinden.

Was sind die Konsequenzen einer solchen Klasse? Gibt es bessere Lösungen? Würde es einen Unterschied machen, wenn meine Anwendung eine API wäre, die auf dieser externen Bibliothek basiert und von anderen Programmierern verwendet werden soll?

user3002473
quelle
Dies scheint eine Variation des Adaptermusters zu sein , obwohl es hier von zweifelhaftem Wert zu sein scheint (es ist lediglich eine Annehmlichkeit, um das Einstellen eines Parameters zu vermeiden).
Robert Harvey
5
Warum nicht eine MakeCircle Funktion erstellen ?
user253751
1
@immibis Thay war mein erster Gedanke. Wenn ich Spiele mache, erstelle ich oft eine Funktion in dieser Richtung, die makePlayernur Koordinaten akzeptiert, an denen der Spieler platziert werden soll, aber an einen viel komplexeren Konstruktor delegiert.
Carcigenicate

Antworten:

13

Es ist zwar nicht immer schlecht, aber es ist ziemlich selten, dass implizite Conversions Ihre beste Option sind.

Es gibt Probleme.

  1. Implizite Konvertierungen sind nicht sehr gut lesbar.
  2. Da Sie ein neues Objekt erstellen, werden Änderungen am ursprünglichen Objekt von dem Objekt, in das Sie konvertieren, nicht gesehen, was zu Fehlern führt.
  3. Wenn Sie das Objekt wegwerfen, ist das zusätzlicher Müll zum Aufräumen.
  4. Wenn es ein Problem gibt, tritt es auf, wenn die Konvertierung erfolgt, nicht wenn das Objekt erstellt wird, wodurch Fehler entstehen, die schwerer zu finden sind.

Im Allgemeinen gibt es bessere Lösungen.

  1. Erstellen Sie Ihren eigenen Thin Wrapper, der die andere Klasse / das andere Framework intern verwendet.
  2. Erstellen Sie eine Hilfsmethode, die die gewünschten Konstruktorargumente verwendet und das festgelegte liefert und das reale Objekt zurückgibt, ohne das Argument angeben zu müssen, das Ihnen egal ist.
  3. Erben Sie von der Problemklasse und liefern Sie Ihren eigenen, schöneren Konstruktor.
  4. Erkenne, dass es keine große Sache ist, das zusätzliche Argument weiterzugeben.

Persönlich finde ich, dass # 2 am einfachsten zu implementieren und am wenigsten belastend für das Design ist. Die anderen können in Ordnung sein, angesichts der Situation und was Sie sonst noch mit diesen Klassen versuchen.

Die implizite Konvertierung ist ein letzter Ausweg und scheint mir nur dann wirklich wert zu sein, wenn ich C ++ - Funktoren habe, die ich erstellen möchte - Strategieobjekte, die ich implizit in delegierte Typen konvertiere.

Telastyn
quelle
1

In Anbetracht des von Ihnen beschriebenen Szenarios können Sie sich dies als Teilfunktionsanwendung vorstellen.

Ein Konstruktor ist eine Funktion (zumindest theoretisch; in C # können Sie eine "Factory-Funktion" erstellen, die den Konstruktor aufruft):

Func<double, Point, Flavour, Circle> MakeCircle = (r, p, f) => new Circle(r, p, f);

Für eine teilweise Anwendung reicht Folgendes aus:

public static Func<T1, T2, R> Partial<T1, T2, T3, R>(this Func<T1, T2, T3, R> func, T3 t3)
   => (t1, t2) => func(t1, t2, t3);

Sie können jetzt Ihren Konstruktor erhalten, der nur 2 Parameter benötigt:

Func<double, Point, Circle> MakeCardboardCircle = Circle.Partial(Flavours.Cardboard)

Jetzt haben Sie eine Werksfunktion mit den gewünschten Parametern

Circle c = MakeCardboardCircle(1.0, new Point(0, 0)))

Übrigens entspricht dies eindeutig der obigen Option 2, nur aus einer funktionaleren Perspektive.

la-yumba
quelle