Ist es ein schlechter Entwurf, langwierige Operationen in einem Konstruktor auszuführen?

8

Ich implementiere eine Klasse zum Vergleichen von Verzeichnisbäumen (in C #). Zuerst habe ich den tatsächlichen Vergleich im Konstruktor der Klasse implementiert. So:

%Vor%

Aber es fühlt sich nicht "richtig" an, eine mögliche langwierige Operation im Konstruktor auszuführen. Eine andere Möglichkeit besteht darin, den Konstruktor privat zu machen und eine statische Methode wie folgt hinzuzufügen:

%Vor%

Was denkst du? Erwarten Sie, dass ein Konstruktor "schnell" ist? Ist das zweite Beispiel besser oder verkompliziert es die Verwendung der Klasse?

BTW:

Ich werde keine Antwort als akzeptiert markieren, weil ich nicht glaube, dass es eine richtige Antwort gibt, nur Vorliebe und Geschmack.

Bearbeiten:

Nur um mein Beispiel ein wenig zu verdeutlichen. Ich bin nicht nur interessiert, wenn die Verzeichnisse unterschiedlich sind, ich bin auch daran interessiert, wie sie sich unterscheiden (welche Dateien). Ein einfacher int-Rückgabewert wird nicht ausreichen. Die Antwort von cdragon76.myopenid.com ist eigentlich ziemlich nah an dem, was ich will (+1 für dich).

    
Mikael Sundberg 14.02.2010, 09:49
quelle

14 Antworten

12

Ich würde denken, dass eine Kombination der beiden die "richtige" Wahl ist, da ich erwarten würde, dass die Compare-Methode das Vergleichsergebnis liefert, nicht den Vergleich selbst.

%Vor%

... und wie Dana erwähnt, gibt es eine IComparer Schnittstelle in .Net spiegelt dieses Muster wider.

Die IComparer.Compare -Methode gibt ein int zurück, da die Verwendung von IComparer-Klassen hauptsächlich mit ist Sortierung. Das allgemeine Muster passt jedoch zu dem Problem der Frage:

  1. Der Konstruktor initialisiert eine Instanz mit (optional) "konfigurierenden" Parametern
  2. Compare-Methode nimmt zwei "Daten" Parameter, vergleicht sie und gibt ein "Ergebnis"
  3. zurück

Nun kann das Ergebnis ein int, ein bool, eine Sammlung von Diffs sein. Was auch immer den Bedürfnissen entspricht.

    
Peter Lillevold 06.11.2008 20:01
quelle
10

Ich bevorzuge die zweite.

Ich erwarte, dass der Konstruktor die Klasse instanziiert. Der Methodenvergleich macht das, wozu er gedacht ist.

    
Burkhard 06.11.2008 19:56
quelle
5

Ich denke, eine Schnittstelle könnte das sein, wonach Sie suchen. Ich würde eine Klasse erstellen, um ein Verzeichnis zu repräsentieren, und die DirectoryComparer-Schnittstelle implementieren. Diese Schnittstelle würde die Vergleichs-Methode beinhalten. Wenn C # bereits über eine vergleichbare Schnittstelle verfügt, können Sie dies auch einfach implementieren.

Im Code wäre Ihr Anruf:

%Vor%     
Dana the Sane 06.11.2008 19:58
quelle
3

Sie sollten niemals etwas tun, was in einem Konstruktor fehlschlagen könnte. Sie möchten niemals ungültige Objekte erstellen. Während Sie einen "Zombie" -Zustand implementieren können, in dem das Objekt nicht viel tut, ist es viel besser, komplexe Logik in separaten Methoden durchzuführen.

    
Douglas Mayle 06.11.2008 19:56
quelle
3

Ich stimme dem generellen Gefühl zu, keine langwierigen Operationen innerhalb von Konstruktoren durchzuführen.

Außerdem würde ich, während ich mich mit Design beschäftige, erwägen, das zweite Beispiel so zu ändern, dass die Methode DirectoryComparer.Compare etwas anderes als ein Objekt DirectoryComparer zurückgibt. (Vielleicht eine neue Klasse namens DirectoryDifferences oder DirectoryComparisonResult .) Ein Objekt vom Typ DirectoryComparer hört sich an wie ein Objekt, das Sie zum Vergleichen von Verzeichnissen verwenden würden, im Gegensatz zu einem Objekt, das die Unterschiede zwischen einem Verzeichnispaar darstellt.

Wenn Sie verschiedene Arten des Vergleichens von Verzeichnissen definieren möchten (z. B. das Ignorieren von Zeitstempeln, schreibgeschützten Attributen, leeren Verzeichnissen usw.), können Sie diese Parameter an den DirectoryComparer -Klassenkonstruktor übergeben. Oder, wenn Sie immer DirectoryComparer die gleichen Regeln zum Vergleichen von Verzeichnissen haben möchten, können Sie DirectoryComparer einfach zu einer statischen Klasse machen.

Zum Beispiel:

%Vor%     
C. Dragon 76 06.11.2008 20:57
quelle
2

Ja, in der Regel ist ein Konstruktor etwas Schnelles, er dient dazu, das Objekt für die Verwendung vorzubereiten, nicht um Operationen auszuführen. Ich mag Ihre zweite Option, da es eine einzeilige Operation hält.

Sie können es auch etwas einfacher machen, indem Sie dem Konstruktor erlauben, die zwei Pfade zu übergeben, und dann eine Compare () -Methode verwenden, die die Verarbeitung tatsächlich durchführt.

    
Mitchel Sellers 06.11.2008 19:56
quelle
1

Ich mag das zweite Beispiel, weil es erklärt, was genau passiert, wenn Sie das Objekt instanziieren. Außerdem benutze ich immer den Konstruktor, um alle globalen Einstellungen für die Klasse zu initialisieren.

    
Michael Kniskern 06.11.2008 20:01
quelle
1

Ich denke, für einen allgemeinen Vergleich können Sie bei der Konstruktion nur die Dateien angeben, die Sie vergleichen und später vergleichen - auf diese Weise können Sie auch erweiterte Logik implementieren:

  • Noch einmal vergleichen - was ist, wenn sich die Verzeichnisse geändert haben?
  • Ändern Sie die Dateien, die Sie vergleichen, indem Sie die Mitglieder aktualisieren.

Sie sollten außerdem in Ihrer Implementierung erwägen, Nachrichten von Ihrem Betriebssystem zu empfangen, wenn Dateien in den Zielverzeichnissen geändert wurden - und optional erneut zu kompilieren.

Der Punkt ist, dass Sie Grenzen auferlegen, indem Sie davon ausgehen, dass diese Klasse nur einmal für eine einzelne Instanz dieser Dateien verwendet wird.

Daher bevorzuge ich:

DirectoryComparer = new DirectoryComparer(&Dir1,&Dir2);

DirectoryComparer- & gt; Compare ();

Oder

DirectoryComparer = new DirectoryComparer();

DirectoryComparer- & gt; Vergleichen (& amp; Dir1, & amp; Dir2);

    
Klathzazt 06.11.2008 20:04
quelle
1

Ich denke, es ist nicht nur in Ordnung, wenn ein Konstruktor so viel Zeit benötigt, um ein gültiges Objekt zu konstruieren, aber der Konstruktor muss dies tun. Das Verschieben von Objekten ist sehr schlecht, da Sie möglicherweise ungültige Objekte erhalten. Sie müssen also jedes Mal ein Objekt überprüfen, bevor Sie es berühren (so wird es im MFC gemacht, Sie haben bool IsValid() Methoden überall).

Ich sehe nur einen kleinen Unterschied zwischen den beiden Möglichkeiten, das Objekt zu erstellen. Man kann den neuen Operator sowieso als statische Funktion der Klasse sehen. Also, das alles läuft auf syntaktischen Zucker hinaus.

Was macht die Klasse DirectoryComparer ? Was ist die Verantwortung? Aus meiner Sicht (was eine C ++ - Programmierersicht ist) sieht es so aus, als ob Sie mit einer kostenlosen Funktion besser dran wären, aber ich denke nicht, dass Sie in C # freie Funktionen haben können, oder? Ich nehme an, Sie werden die Dateien sammeln, die im DirectoryComparer -Objekt unterschiedlich sind. Wenn dies der Fall ist, können Sie etwas wie ein Array von Dateien oder eine entsprechende Klasse erstellen, die entsprechend benannt ist.

    
Andre 07.11.2008 14:30
quelle
0

Wenn Sie mit C # arbeiten, können Sie Erweiterungsmethoden verwenden, um eine Methode zum Vergleichen von zwei Verzeichnissen zu erstellen, die Sie an den Build in DirectoryClass anhängen würden. Es sieht dann so aus:

%Vor%

Dies wäre eine viel klarere Implementierung. Mehr über Erweiterungsmethoden hier .

    
Alex Shnayder 06.11.2008 20:28
quelle
0

Wenn eine Operation eine unbekannte Zeit braucht, ist es eine Operation, die Sie vielleicht in einen anderen Thread exportieren möchten (so dass Ihr Haupt-Thread nicht blockiert und andere Dinge tun kann, wie zum Beispiel einen Dreh-Fortschrittsanzeiger) ). Andere Anwendungen möchten dies möglicherweise nicht, sie möchten möglicherweise alles innerhalb eines einzelnen Threads (z. B. solche, die keine Benutzeroberfläche haben). Die Erstellung von Objekten in einen separaten Thread zu verschieben ist ein wenig umständlich IMHO. Ich würde es vorziehen, das Objekt (schnell) in meinem aktuellen Thread zu erstellen und dann eine Methode innerhalb eines anderen Threads laufen zu lassen und sobald die Methode fertig ist, kann der andere Thread absterben und ich kann das Ergebnis dieser Methode in meinem Aktueller Thread durch Verwendung einer anderen Methode des Objekts vor dem Dumping des Objekts, da ich glücklich bin, sobald ich das Ergebnis weiß (oder eine Kopie behalten, wenn das Ergebnis mehr Details enthält, die ich möglicherweise einzeln verbrauchen muss).

    
Mecki 06.11.2008 20:34
quelle
0

Wenn die Argumente nur einmal verarbeitet werden, dann glaube ich nicht, dass sie entweder als Konstruktorargumente oder als Instanzzustand gehören.

Wenn der Vergleichsdienst jedoch eine Art aussetzbarer Algorithmus unterstützt oder Listener benachrichtigen soll, wenn sich der Gleichheitszustand von zwei Verzeichnissen aufgrund von Dateisystemereignissen oder ähnlichem ändert. Dann sind weitere Verzeichnisse Teil des Instanzstatus.

In beiden Fällen führt der Konstruktor keine andere Arbeit als die Initialisierung einer Instanz aus. Im Fall zwei über dem Algorithmus wird entweder durch einen Client, genau wie ein Iterator, zum Beispiel angetrieben, oder es wird durch den Event-Listening-Thread gesteuert.

Ich versuche generell, so etwas zu tun: Halten Sie den Status in der Instanz nicht fest, wenn er als Argument für Servicemethoden übergeben werden kann. Versuchen Sie, das Objekt mit unveränderlichem Status zu entwerfen. Das Definieren von Attributen, wie sie in equals und hashcode verwendet werden, sollte immer unveränderlich sein.

Konzeptionell ist ein Konstruktor eine Funktion, die eine Objektdarstellung auf das Objekt, das sie repräsentiert, abbildet.

Nach der obigen Definition ist Integer.valueOf (1) eigentlich mehr ein Konstruktor als new Integer (1), weil Integer.valueOf (1) == Integer.valueOf (1). , In beiden Fällen bedeutet dieses Konzept auch, dass alle Cosntructor-Argumente und nur das Konstruktorargument das Gleichheitsverhalten eines Objekts definieren sollten.

    
John Nilsson 06.11.2008 21:02
quelle
0

Ich würde definitiv die zweite machen.

Lange Aktionen in einem Konstruktor sind in Ordnung, wenn sie das Objekt tatsächlich so erstellen, dass es verwendbar ist.

Eine Sache, die Leute in Konstruktoren sehen, ist Call Virtual Methode. Das ist SCHLECHT, da sobald jemand Sie als Basisklasse benutzt und eine dieser Funktionen außer Kraft setzt, Sie die Basisklassenversion nicht die abgeleitete Klasse nennen werden, sobald Sie in Ihren Konstruktor gelangen.

    
user22367 07.11.2008 14:00
quelle
0

Ich glaube nicht, dass das Reden über abstrakte Begriffe wie "lang" etwas mit der Entscheidung zu tun hat, wenn man etwas in einen Konstruktor legt oder nicht.

Ein Konstruktor ist etwas, das verwendet werden sollte, um ein Objekt zu initialisieren, eine Methode sollte verwendet werden, "etwas zu tun", d. h. eine Funktion haben.

    
BlaM 07.11.2008 14:19
quelle

Tags und Links