Wie vermeidet man den Aufruf viritueller Methoden von einem Basiskonstruktor?

8

Ich habe eine abstrakte Klasse in einer Bibliothek. Ich versuche es so einfach wie möglich zu machen, um eine Ableitung dieser Klasse richtig zu implementieren. Das Problem ist, dass ich das Objekt in einem dreistufigen Prozess initialisieren muss: eine Datei abrufen, ein paar Zwischenschritte ausführen und dann mit der Datei arbeiten. Der erste und der letzte Schritt sind für die abgeleitete Klasse spezifisch. Hier ist ein abgespecktes Beispiel.

%Vor%

Das Problem ist natürlich der Aufruf der virtuellen Methode im Konstruktor. Ich befürchte, dass der Konsument der Bibliothek sich eingeschränkt fühlen wird, wenn er die Klasse benutzt, wenn er sich nicht darauf verlassen kann, dass die abgeleitete Klasse vollständig initialisiert ist.

Ich könnte die Logik aus dem Konstruktor in eine geschützte Initialize() -Methode ziehen, aber dann ruft der Implementierer möglicherweise direkt Step1() und Step3() auf, anstatt Initialize() aufzurufen. Der Kern des Problems ist, dass es keinen offensichtlichen Fehler geben würde, wenn Step2() übersprungen wird; nur schreckliche Leistung in bestimmten Situationen.

Ich habe das Gefühl, dass es auf jeden Fall ein ernstes und nicht offensichtliches "Gotcha" gibt, dass zukünftige Benutzer der Bibliothek umgehen müssen. Gibt es ein anderes Design, das ich verwenden sollte, um diese Art von Initialisierung zu erreichen?

Ich kann bei Bedarf weitere Details angeben; Ich habe nur versucht, das einfachste Beispiel zu geben, das das Problem ausdrückt.

    
WCWedin 20.07.2009, 19:04
quelle

8 Antworten

3

Das ist viel zu viel, um es im Konstruktor irgendeiner Klasse zu platzieren, geschweige denn einer Basisklasse. Ich schlage vor, dass Sie das in eine separate Methode Initialize einteilen.

    
John Saunders 20.07.2009, 19:06
quelle
10

Ich würde in Erwägung ziehen, eine abstrakte Factory zu erstellen, die für das Instanziieren und Initialisieren von Instanzen Ihrer abgeleiteten Klassen mit Vorlagenmethode für die Initialisierung.

Als ein Beispiel:

%Vor%

Dieser Fabrikcode könnte drastisch verbessert werden - besonders, wenn Sie bereit sind, einen IoC-Container zu verwenden, um dieser Verantwortung gerecht zu werden ...

Wenn Sie die abgeleiteten Klassen nicht kontrollieren können, können Sie möglicherweise nicht verhindern, dass sie einen öffentlichen Konstruktor anbieten, der aufgerufen werden kann - aber Sie können zumindest ein Nutzungsmuster festlegen, an das sich die Kunden halten können.

Es ist nicht immer möglich, zu verhindern, dass Benutzer Ihrer Klassen sich selbst in den Fuß schießen - aber Sie können eine Infrastruktur bereitstellen, die Verbrauchern hilft, Ihren Code korrekt zu verwenden, wenn sie sich mit dem Design vertraut machen.

    
LBushkin 20.07.2009 19:18
quelle
1

In vielen Fällen werden beim Initialisieren einige Eigenschaften zugewiesen. Es ist möglich, diese Eigenschaften selbst in abstract zu setzen und die abgeleitete Klasse zu überschreiben und einen Wert zurückzugeben, anstatt den Wert an den zu setzenden Basiskonstruktor zu übergeben. Ob diese Idee anwendbar ist, hängt natürlich von der Art Ihrer spezifischen Klasse ab. Wie auch immer, es ist übel, so viel Code im Konstruktor zu haben.

    
Mehrdad Afshari 20.07.2009 19:09
quelle
1

Auf den ersten Blick würde ich vorschlagen, diese Art von Logik auf die Methoden zu übertragen, die auf dieser Initialisierung beruhen. Etwas wie

%Vor%     
jeroenh 20.07.2009 19:12
quelle
1

Da Schritt 1 "eine Datei packt", kann es gut sein, Initialize (IBaseFile) zu haben und Schritt 1 zu überspringen. Auf diese Weise kann der Konsument die Datei bekommen, wie sie will - da sie sowieso abstrakt ist. Sie können immer noch eine 'StepOneGetFile ()' als Zusammenfassung anbieten, die die Datei zurückgibt, damit sie sie auf diese Weise implementieren können, wenn sie sich dafür entscheiden.

%Vor%     
s_hewitt 20.07.2009 19:22
quelle
1

Bearbeiten: Ich habe das aus irgendeinem Grund für C ++ beantwortet. Sorry. Für C # empfehle ich eine Create() -Methode - benutze den Konstruktor und stelle sicher, dass die Objekte von Anfang an in einem gültigen Zustand bleiben. C # ermöglicht virtuelle Aufrufe vom Konstruktor, und es ist in Ordnung, sie zu verwenden, wenn Sie die erwartete Funktion und die Vor- und Nachbedingungen sorgfältig dokumentieren. Ich habe C ++ das erste Mal durchgeleitet, weil es keine virtuellen Aufrufe vom Konstruktor erlaubt.

Nehmen Sie die einzelnen Initialisierungsfunktionen private vor. Das kann sowohl private als auch virtual sein. Dann bieten Sie eine öffentliche, nicht virtuelle Initialize() -Funktion, die sie in der richtigen Reihenfolge aufruft.

Wenn Sie sicherstellen möchten, dass alles beim Erstellen des Objekts passiert, erstellen Sie den Konstruktor protected und verwenden Sie eine statische Funktion Create() in Ihren Klassen, die Initialize() aufruft, bevor das neu erstellte Objekt zurückgegeben wird.

    
Sam Harwell 20.07.2009 19:09
quelle
1

Sie können den folgenden Trick anwenden, um sicherzustellen, dass die Initialisierung in der richtigen Reihenfolge ausgeführt wird. Vermutlich haben Sie einige andere Methoden ( DoActualWork ) in der Basisklasse implementiert, die auf der Initialisierung beruhen.

%Vor%     
VladV 20.07.2009 19:35
quelle
0

Ich würde das nicht tun. Ich finde im Allgemeinen, dass es eine schlechte Idee ist, in einem Konstruktor "echte" Arbeit zu verrichten.

Sie müssen mindestens eine separate Methode zum Laden der Daten aus einer Datei haben. Sie könnten ein Argument machen, um einen Schritt weiter zu gehen und ein separates Objekt zu haben, das für das Erstellen eines Ihrer Objekte aus der Datei verantwortlich ist. Dabei werden die Belange des "Ladens von der Platte" und der speicherinternen Operationen auf dem Objekt getrennt.

    
kyoryu 21.07.2009 05:46
quelle