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 kann bei Bedarf weitere Details angeben; Ich habe nur versucht, das einfachste Beispiel zu geben, das das Problem ausdrückt.
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.
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.
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.
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% 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.
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.
Tags und Links c# inheritance constructor virtual-method